blob: f114f6383baea8afd4663c7f30d105d46f506eb0 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/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"""
Tao Bao30df8b42018-04-23 15:32:53 -070018Given a target-files zipfile, produces an OTA package that installs that build.
19An incremental OTA is produced if -i is given, otherwise a full OTA is produced.
Doug Zongkereef39442009-04-02 12:14:19 -070020
Tao Bao30df8b42018-04-23 15:32:53 -070021Usage: ota_from_target_files [options] input_target_files output_ota_package
Doug Zongkereef39442009-04-02 12:14:19 -070022
Tao Bao30df8b42018-04-23 15:32:53 -070023Common options that apply to both of non-A/B and A/B OTAs
24
25 --downgrade
26 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070027 to an older one (e.g. downgrading from P preview back to O MR1).
28 "ota-downgrade=yes" will be set in the package metadata file. A data wipe
29 will always be enforced when using this flag, so "ota-wipe=yes" will also
30 be included in the metadata file. The update-binary in the source build
31 will be used in the OTA package, unless --binary flag is specified. Please
32 also check the comment for --override_timestamp below.
Tao Bao30df8b42018-04-23 15:32:53 -070033
34 -i (--incremental_from) <file>
35 Generate an incremental OTA using the given target-files zip as the
36 starting build.
37
38 -k (--package_key) <key>
39 Key to use to sign the package (default is the value of
40 default_system_dev_certificate from the input target-files's
Tao Bao59cf0c52019-06-25 10:04:24 -070041 META/misc_info.txt, or "build/make/target/product/security/testkey" if
42 that value is not specified).
Doug Zongkerafb32ea2011-09-22 10:28:04 -070043
44 For incremental OTAs, the default value is based on the source
45 target-file, not the target build.
Doug Zongkereef39442009-04-02 12:14:19 -070046
Tao Bao30df8b42018-04-23 15:32:53 -070047 --override_timestamp
48 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070049 to an older one (based on timestamp comparison), by setting the downgrade
50 flag in the package metadata. This differs from --downgrade flag, as we
51 don't enforce a data wipe with this flag. Because we know for sure this is
52 NOT an actual downgrade case, but two builds happen to be cut in a reverse
53 order (e.g. from two branches). A legit use case is that we cut a new
54 build C (after having A and B), but want to enfore an update path of A ->
55 C -> B. Specifying --downgrade may not help since that would enforce a
56 data wipe for C -> B update.
57
58 We used to set a fake timestamp in the package metadata for this flow. But
59 now we consolidate the two cases (i.e. an actual downgrade, or a downgrade
60 based on timestamp) with the same "ota-downgrade=yes" flag, with the
61 difference being whether "ota-wipe=yes" is set.
Doug Zongkereef39442009-04-02 12:14:19 -070062
Tao Bao30df8b42018-04-23 15:32:53 -070063 --wipe_user_data
64 Generate an OTA package that will wipe the user data partition when
65 installed.
66
Yifan Hong50e79542018-11-08 17:44:12 -080067 --retrofit_dynamic_partitions
68 Generates an OTA package that updates a device to support dynamic
69 partitions (default False). This flag is implied when generating
70 an incremental OTA where the base build does not support dynamic
71 partitions but the target build does. For A/B, when this flag is set,
72 --skip_postinstall is implied.
73
xunchangabfa2652019-02-19 16:27:10 -080074 --skip_compatibility_check
Yifan Hong9276cf02019-08-21 16:37:04 -070075 Skip checking compatibility of the input target files package.
xunchangabfa2652019-02-19 16:27:10 -080076
xunchang1cfe2512019-02-19 14:14:48 -080077 --output_metadata_path
78 Write a copy of the metadata to a separate file. Therefore, users can
79 read the post build fingerprint without extracting the OTA package.
80
Yifan Hong65afc072020-04-17 10:08:10 -070081 --force_non_ab
82 This flag can only be set on an A/B device that also supports non-A/B
83 updates. Implies --two_step.
84 If set, generate that non-A/B update package.
85 If not set, generates A/B package for A/B device and non-A/B package for
86 non-A/B device.
87
Tao Bao30df8b42018-04-23 15:32:53 -070088Non-A/B OTA specific options
89
90 -b (--binary) <file>
91 Use the given binary as the update-binary in the output package, instead
92 of the binary in the build's target_files. Use for development only.
93
94 --block
95 Generate a block-based OTA for non-A/B device. We have deprecated the
96 support for file-based OTA since O. Block-based OTA will be used by
97 default for all non-A/B devices. Keeping this flag here to not break
98 existing callers.
99
100 -e (--extra_script) <file>
101 Insert the contents of file at the end of the update script.
Tao Bao43078aa2015-04-21 14:32:35 -0700102
leozwangaa6c1a12015-08-14 10:57:58 -0700103 --full_bootloader
104 Similar to --full_radio. When generating an incremental OTA, always
105 include a full copy of bootloader image.
106
Tao Bao30df8b42018-04-23 15:32:53 -0700107 --full_radio
108 When generating an incremental OTA, always include a full copy of radio
109 image. This option is only meaningful when -i is specified, because a full
110 radio is always included in a full OTA if applicable.
Michael Runge63f01de2014-10-28 19:24:19 -0700111
Tao Bao30df8b42018-04-23 15:32:53 -0700112 --log_diff <file>
113 Generate a log file that shows the differences in the source and target
114 builds for an incremental package. This option is only meaningful when -i
115 is specified.
116
117 -o (--oem_settings) <main_file[,additional_files...]>
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800118 Comma seperated list of files used to specify the expected OEM-specific
Tao Bao481bab82017-12-21 11:23:09 -0800119 properties on the OEM partition of the intended device. Multiple expected
120 values can be used by providing multiple files. Only the first dict will
121 be used to compute fingerprint, while the rest will be used to assert
122 OEM-specific properties.
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800123
Tao Bao8608cde2016-02-25 19:49:55 -0800124 --oem_no_mount
Tao Bao30df8b42018-04-23 15:32:53 -0700125 For devices with OEM-specific properties but without an OEM partition, do
126 not mount the OEM partition in the updater-script. This should be very
127 rarely used, since it's expected to have a dedicated OEM partition for
128 OEM-specific properties. Only meaningful when -o is specified.
Tao Bao8608cde2016-02-25 19:49:55 -0800129
Tao Bao30df8b42018-04-23 15:32:53 -0700130 --stash_threshold <float>
131 Specify the threshold that will be used to compute the maximum allowed
132 stash size (defaults to 0.8).
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700133
Tao Bao30df8b42018-04-23 15:32:53 -0700134 -t (--worker_threads) <int>
135 Specify the number of worker-threads that will be used when generating
136 patches for incremental updates (defaults to 3).
Tao Bao3e6161a2017-02-28 11:48:48 -0800137
Tao Bao30df8b42018-04-23 15:32:53 -0700138 --verify
139 Verify the checksums of the updated system and vendor (if any) partitions.
140 Non-A/B incremental OTAs only.
Doug Zongker1c390a22009-05-14 19:06:36 -0700141
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800142 -2 (--two_step)
Tao Bao30df8b42018-04-23 15:32:53 -0700143 Generate a 'two-step' OTA package, where recovery is updated first, so
144 that any changes made to the system partition are done using the new
145 recovery (new kernel, etc.).
146
147A/B OTA specific options
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800148
Tianjie Xu1b079832019-08-28 12:19:23 -0700149 --disable_fec_computation
150 Disable the on device FEC data computation for incremental updates.
151
Tao Baof7140c02018-01-30 17:09:24 -0800152 --include_secondary
153 Additionally include the payload for secondary slot images (default:
154 False). Only meaningful when generating A/B OTAs.
155
156 By default, an A/B OTA package doesn't contain the images for the
157 secondary slot (e.g. system_other.img). Specifying this flag allows
158 generating a separate payload that will install secondary slot images.
159
160 Such a package needs to be applied in a two-stage manner, with a reboot
161 in-between. During the first stage, the updater applies the primary
162 payload only. Upon finishing, it reboots the device into the newly updated
163 slot. It then continues to install the secondary payload to the inactive
164 slot, but without switching the active slot at the end (needs the matching
165 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag).
166
167 Due to the special install procedure, the secondary payload will be always
168 generated as a full payload.
169
Tao Baodea0f8b2016-06-20 17:55:06 -0700170 --payload_signer <signer>
171 Specify the signer when signing the payload and metadata for A/B OTAs.
172 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
173 with the package private key. If the private key cannot be accessed
174 directly, a payload signer that knows how to do that should be specified.
175 The signer will be supplied with "-inkey <path_to_key>",
176 "-in <input_file>" and "-out <output_file>" parameters.
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700177
178 --payload_signer_args <args>
179 Specify the arguments needed for payload signer.
Tao Bao15a146a2018-02-21 16:06:59 -0800180
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700181 --payload_signer_maximum_signature_size <signature_size>
182 The maximum signature size (in bytes) that would be generated by the given
183 payload signer. Only meaningful when custom payload signer is specified
184 via '--payload_signer'.
185 If the signer uses a RSA key, this should be the number of bytes to
186 represent the modulus. If it uses an EC key, this is the size of a
187 DER-encoded ECDSA signature.
188
xunchang376cc7c2019-04-08 23:04:58 -0700189 --payload_signer_key_size <key_size>
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700190 Deprecated. Use the '--payload_signer_maximum_signature_size' instead.
xunchang376cc7c2019-04-08 23:04:58 -0700191
Tianjied6867162020-05-10 14:30:13 -0700192 --boot_variable_file <path>
193 A file that contains the possible values of ro.boot.* properties. It's
194 used to calculate the possible runtime fingerprints when some
195 ro.product.* properties are overridden by the 'import' statement.
196 The file expects one property per line, and each line has the following
197 format: 'prop_name=value1,value2'. e.g. 'ro.boot.product.sku=std,pro'
198
Tao Bao15a146a2018-02-21 16:06:59 -0800199 --skip_postinstall
200 Skip the postinstall hooks when generating an A/B OTA package (default:
201 False). Note that this discards ALL the hooks, including non-optional
202 ones. Should only be used if caller knows it's safe to do so (e.g. all the
203 postinstall work is to dexopt apps and a data wipe will happen immediately
204 after). Only meaningful when generating A/B OTAs.
Doug Zongkereef39442009-04-02 12:14:19 -0700205"""
206
Tao Bao89fbb0f2017-01-10 10:47:58 -0800207from __future__ import print_function
208
Tianjie Xuf67dd802019-05-20 17:50:36 -0700209import collections
Tianjie Xu9afb2212020-05-10 21:48:15 +0000210import copy
211import itertools
Tao Bao32fcdab2018-10-12 10:30:39 -0700212import logging
Doug Zongkerfc44a512014-08-26 13:10:25 -0700213import multiprocessing
Tao Bao2dd1c482017-02-03 16:49:39 -0800214import os.path
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700215import shlex
Tao Bao15a146a2018-02-21 16:06:59 -0800216import shutil
Tao Bao85f16982018-03-08 16:28:33 -0800217import struct
Tao Bao481bab82017-12-21 11:23:09 -0800218import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700219import zipfile
220
Yifan Hong9276cf02019-08-21 16:37:04 -0700221import check_target_files_vintf
Doug Zongkereef39442009-04-02 12:14:19 -0700222import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700223import edify_generator
Kelvin Zhang0876c412020-06-23 15:06:58 -0400224import target_files_diff
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700225import verity_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700226
Kelvin Zhang0876c412020-06-23 15:06:58 -0400227
Tao Bao481bab82017-12-21 11:23:09 -0800228if sys.hexversion < 0x02070000:
229 print("Python 2.7 or newer is required.", file=sys.stderr)
230 sys.exit(1)
231
Tao Bao32fcdab2018-10-12 10:30:39 -0700232logger = logging.getLogger(__name__)
Tao Bao481bab82017-12-21 11:23:09 -0800233
Doug Zongkereef39442009-04-02 12:14:19 -0700234OPTIONS = common.OPTIONS
Doug Zongkerafb32ea2011-09-22 10:28:04 -0700235OPTIONS.package_key = None
Doug Zongkereef39442009-04-02 12:14:19 -0700236OPTIONS.incremental_source = None
Michael Runge63f01de2014-10-28 19:24:19 -0700237OPTIONS.verify = False
Doug Zongkereef39442009-04-02 12:14:19 -0700238OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700239OPTIONS.wipe_user_data = False
Tao Bao5d182562016-02-23 11:38:39 -0800240OPTIONS.downgrade = False
Doug Zongker1c390a22009-05-14 19:06:36 -0700241OPTIONS.extra_script = None
Doug Zongkerfc44a512014-08-26 13:10:25 -0700242OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
243if OPTIONS.worker_threads == 0:
244 OPTIONS.worker_threads = 1
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800245OPTIONS.two_step = False
Tao Baof7140c02018-01-30 17:09:24 -0800246OPTIONS.include_secondary = False
Takeshi Kanemotoe153b342013-11-14 17:20:50 +0900247OPTIONS.no_signing = False
Tao Bao457cbf62017-03-06 09:56:01 -0800248OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -0800249OPTIONS.updater_binary = None
Tianjie Xu9afb2212020-05-10 21:48:15 +0000250OPTIONS.oem_dicts = None
Michael Runge6e836112014-04-15 17:40:21 -0700251OPTIONS.oem_source = None
Tao Bao8608cde2016-02-25 19:49:55 -0800252OPTIONS.oem_no_mount = False
Tao Bao43078aa2015-04-21 14:32:35 -0700253OPTIONS.full_radio = False
leozwangaa6c1a12015-08-14 10:57:58 -0700254OPTIONS.full_bootloader = False
Tao Baod47d8e12015-05-21 14:09:49 -0700255# Stash size cannot exceed cache_size * threshold.
256OPTIONS.cache_size = None
257OPTIONS.stash_threshold = 0.8
Tao Baod62c6032015-11-30 09:40:20 -0800258OPTIONS.log_diff = None
Tao Baodea0f8b2016-06-20 17:55:06 -0700259OPTIONS.payload_signer = None
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700260OPTIONS.payload_signer_args = []
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700261OPTIONS.payload_signer_maximum_signature_size = None
Tao Bao5f8ff932017-03-21 22:35:00 -0700262OPTIONS.extracted_input = None
Christian Oderf63e2cd2017-05-01 22:30:15 +0200263OPTIONS.key_passwords = []
Tao Bao15a146a2018-02-21 16:06:59 -0800264OPTIONS.skip_postinstall = False
Yifan Hong50e79542018-11-08 17:44:12 -0800265OPTIONS.retrofit_dynamic_partitions = False
xunchangabfa2652019-02-19 16:27:10 -0800266OPTIONS.skip_compatibility_check = False
xunchang1cfe2512019-02-19 14:14:48 -0800267OPTIONS.output_metadata_path = None
Tianjie Xu1b079832019-08-28 12:19:23 -0700268OPTIONS.disable_fec_computation = False
Yifan Hong65afc072020-04-17 10:08:10 -0700269OPTIONS.force_non_ab = False
Tianjied6867162020-05-10 14:30:13 -0700270OPTIONS.boot_variable_file = None
Tao Bao15a146a2018-02-21 16:06:59 -0800271
Tao Bao8dcf7382015-05-21 14:09:49 -0700272
Tao Bao2dd1c482017-02-03 16:49:39 -0800273METADATA_NAME = 'META-INF/com/android/metadata'
Tao Bao15a146a2018-02-21 16:06:59 -0800274POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
Yifan Hong50e79542018-11-08 17:44:12 -0800275DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
Yifan Hongb433eba2019-03-06 12:42:53 -0800276AB_PARTITIONS = 'META/ab_partitions.txt'
Tao Bao04808502019-07-25 23:11:41 -0700277UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Tao Baof0c4aa22018-04-30 20:29:30 -0700278# Files to be unzipped for target diffing purpose.
279TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
Yifan Hongcfb917a2020-05-07 14:58:20 -0700280 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*',
281 'VENDOR_DLKM/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800282RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tao Bao3e759462019-09-17 22:43:11 -0700283
284# Images to be excluded from secondary payload. We essentially only keep
285# 'system_other' and bootloader partitions.
286SECONDARY_PAYLOAD_SKIPPED_IMAGES = [
287 'boot', 'dtbo', 'modem', 'odm', 'product', 'radio', 'recovery',
Tianjiec3850642020-05-13 14:47:31 -0700288 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor',
Yifan Hongcfb917a2020-05-07 14:58:20 -0700289 'vendor_boot', 'vendor_dlkm']
Tao Bao6b0b2f92017-03-05 11:38:11 -0800290
Tao Bao2dd1c482017-02-03 16:49:39 -0800291
Tao Baofabe0832018-01-17 15:52:28 -0800292class PayloadSigner(object):
293 """A class that wraps the payload signing works.
294
295 When generating a Payload, hashes of the payload and metadata files will be
296 signed with the device key, either by calling an external payload signer or
297 by calling openssl with the package key. This class provides a unified
298 interface, so that callers can just call PayloadSigner.Sign().
299
300 If an external payload signer has been specified (OPTIONS.payload_signer), it
301 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
302 that the signing key should be provided as part of the payload_signer_args.
303 Otherwise without an external signer, it uses the package key
304 (OPTIONS.package_key) and calls openssl for the signing works.
305 """
306
307 def __init__(self):
308 if OPTIONS.payload_signer is None:
309 # Prepare the payload signing key.
310 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
311 pw = OPTIONS.key_passwords[OPTIONS.package_key]
312
313 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
314 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
315 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
316 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700317 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800318
319 self.signer = "openssl"
320 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
321 "-pkeyopt", "digest:sha256"]
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700322 self.maximum_signature_size = self._GetMaximumSignatureSizeInBytes(
323 signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800324 else:
325 self.signer = OPTIONS.payload_signer
326 self.signer_args = OPTIONS.payload_signer_args
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700327 if OPTIONS.payload_signer_maximum_signature_size:
328 self.maximum_signature_size = int(
329 OPTIONS.payload_signer_maximum_signature_size)
xunchang376cc7c2019-04-08 23:04:58 -0700330 else:
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700331 # The legacy config uses RSA2048 keys.
332 logger.warning("The maximum signature size for payload signer is not"
333 " set, default to 256 bytes.")
334 self.maximum_signature_size = 256
xunchang376cc7c2019-04-08 23:04:58 -0700335
336 @staticmethod
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700337 def _GetMaximumSignatureSizeInBytes(signing_key):
338 out_signature_size_file = common.MakeTempFile("signature_size")
339 cmd = ["delta_generator", "--out_maximum_signature_size_file={}".format(
340 out_signature_size_file), "--private_key={}".format(signing_key)]
341 common.RunAndCheckOutput(cmd)
342 with open(out_signature_size_file) as f:
343 signature_size = f.read().rstrip()
Luca Stefani88e1a142020-03-27 14:05:12 +0100344 logger.info("%s outputs the maximum signature size: %s", cmd[0],
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700345 signature_size)
346 return int(signature_size)
Tao Baofabe0832018-01-17 15:52:28 -0800347
348 def Sign(self, in_file):
349 """Signs the given input file. Returns the output filename."""
350 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
351 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700352 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800353 return out_file
354
355
Tao Bao40b18822018-01-30 18:19:04 -0800356class Payload(object):
357 """Manages the creation and the signing of an A/B OTA Payload."""
358
359 PAYLOAD_BIN = 'payload.bin'
360 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800361 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
362 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800363
Tao Bao667ff572018-02-10 00:02:40 -0800364 def __init__(self, secondary=False):
365 """Initializes a Payload instance.
366
367 Args:
368 secondary: Whether it's generating a secondary payload (default: False).
369 """
Tao Bao40b18822018-01-30 18:19:04 -0800370 self.payload_file = None
371 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800372 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800373
Tao Baof0c4aa22018-04-30 20:29:30 -0700374 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700375 # Don't pipe (buffer) the output if verbose is set. Let
376 # brillo_update_payload write to stdout/stderr directly, so its progress can
377 # be monitored.
378 if OPTIONS.verbose:
379 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
380 else:
381 common.RunAndCheckOutput(cmd)
382
Tao Bao40b18822018-01-30 18:19:04 -0800383 def Generate(self, target_file, source_file=None, additional_args=None):
384 """Generates a payload from the given target-files zip(s).
385
386 Args:
387 target_file: The filename of the target build target-files zip.
388 source_file: The filename of the source build target-files zip; or None if
389 generating a full OTA.
390 additional_args: A list of additional args that should be passed to
391 brillo_update_payload script; or None.
392 """
393 if additional_args is None:
394 additional_args = []
395
396 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
397 cmd = ["brillo_update_payload", "generate",
398 "--payload", payload_file,
399 "--target_image", target_file]
400 if source_file is not None:
401 cmd.extend(["--source_image", source_file])
Tianjie Xu1b079832019-08-28 12:19:23 -0700402 if OPTIONS.disable_fec_computation:
403 cmd.extend(["--disable_fec_computation", "true"])
Tao Bao40b18822018-01-30 18:19:04 -0800404 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700405 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800406
407 self.payload_file = payload_file
408 self.payload_properties = None
409
410 def Sign(self, payload_signer):
411 """Generates and signs the hashes of the payload and metadata.
412
413 Args:
414 payload_signer: A PayloadSigner() instance that serves the signing work.
415
416 Raises:
417 AssertionError: On any failure when calling brillo_update_payload script.
418 """
419 assert isinstance(payload_signer, PayloadSigner)
420
421 # 1. Generate hashes of the payload and metadata files.
422 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
423 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
424 cmd = ["brillo_update_payload", "hash",
425 "--unsigned_payload", self.payload_file,
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700426 "--signature_size", str(payload_signer.maximum_signature_size),
Tao Bao40b18822018-01-30 18:19:04 -0800427 "--metadata_hash_file", metadata_sig_file,
428 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700429 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800430
431 # 2. Sign the hashes.
432 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
433 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
434
435 # 3. Insert the signatures back into the payload file.
436 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
437 suffix=".bin")
438 cmd = ["brillo_update_payload", "sign",
439 "--unsigned_payload", self.payload_file,
440 "--payload", signed_payload_file,
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700441 "--signature_size", str(payload_signer.maximum_signature_size),
Tao Bao40b18822018-01-30 18:19:04 -0800442 "--metadata_signature_file", signed_metadata_sig_file,
443 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700444 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800445
446 # 4. Dump the signed payload properties.
447 properties_file = common.MakeTempFile(prefix="payload-properties-",
448 suffix=".txt")
449 cmd = ["brillo_update_payload", "properties",
450 "--payload", signed_payload_file,
451 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700452 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800453
Tao Bao667ff572018-02-10 00:02:40 -0800454 if self.secondary:
455 with open(properties_file, "a") as f:
456 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
457
Tao Bao40b18822018-01-30 18:19:04 -0800458 if OPTIONS.wipe_user_data:
459 with open(properties_file, "a") as f:
460 f.write("POWERWASH=1\n")
461
462 self.payload_file = signed_payload_file
463 self.payload_properties = properties_file
464
Tao Bao667ff572018-02-10 00:02:40 -0800465 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800466 """Writes the payload to the given zip.
467
468 Args:
469 output_zip: The output ZipFile instance.
470 """
471 assert self.payload_file is not None
472 assert self.payload_properties is not None
473
Tao Bao667ff572018-02-10 00:02:40 -0800474 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800475 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
476 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
477 else:
478 payload_arcname = Payload.PAYLOAD_BIN
479 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
480
Tao Bao40b18822018-01-30 18:19:04 -0800481 # Add the signed payload file and properties into the zip. In order to
482 # support streaming, we pack them as ZIP_STORED. So these entries can be
483 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800484 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800485 compress_type=zipfile.ZIP_STORED)
486 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800487 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800488 compress_type=zipfile.ZIP_STORED)
489
490
Doug Zongkereef39442009-04-02 12:14:19 -0700491def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200492 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700493
Doug Zongker951495f2009-08-14 12:44:19 -0700494 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
495 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700496
497
Tao Bao481bab82017-12-21 11:23:09 -0800498def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800499 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800500 if not oem_source:
501 return None
502
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800503 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800504 for oem_file in oem_source:
505 with open(oem_file) as fp:
506 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800507 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700508
Doug Zongkereef39442009-04-02 12:14:19 -0700509
Tao Baod42e97e2016-11-30 12:11:57 -0800510def _WriteRecoveryImageToBoot(script, output_zip):
511 """Find and write recovery image to /boot in two-step OTA.
512
513 In two-step OTAs, we write recovery image to /boot as the first step so that
514 we can reboot to there and install a new recovery image to /recovery.
515 A special "recovery-two-step.img" will be preferred, which encodes the correct
516 path of "/boot". Otherwise the device may show "device is corrupt" message
517 when booting into /boot.
518
519 Fall back to using the regular recovery.img if the two-step recovery image
520 doesn't exist. Note that rebuilding the special image at this point may be
521 infeasible, because we don't have the desired boot signer and keys when
522 calling ota_from_target_files.py.
523 """
524
525 recovery_two_step_img_name = "recovery-two-step.img"
526 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700527 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800528 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700529 common.ZipWrite(
530 output_zip,
531 recovery_two_step_img_path,
532 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700533 logger.info(
534 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800535 script.WriteRawImage("/boot", recovery_two_step_img_name)
536 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700537 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800538 # The "recovery.img" entry has been written into package earlier.
539 script.WriteRawImage("/boot", "recovery.img")
540
541
Bill Peckhame868aec2019-09-17 17:06:47 -0700542def HasRecoveryPatch(target_files_zip, info_dict):
543 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
544
545 if board_uses_vendorimage:
546 target_files_dir = "VENDOR"
547 else:
548 target_files_dir = "SYSTEM/vendor"
549
550 patch = "%s/recovery-from-boot.p" % target_files_dir
Kelvin Zhang0876c412020-06-23 15:06:58 -0400551 img = "%s/etc/recovery.img" % target_files_dir
Bill Peckhame868aec2019-09-17 17:06:47 -0700552
Kelvin Zhang0876c412020-06-23 15:06:58 -0400553 namelist = target_files_zip.namelist()
554 return patch in namelist or img in namelist
Doug Zongker73ef8252009-07-23 15:12:53 -0700555
Tao Bao457cbf62017-03-06 09:56:01 -0800556
Yifan Hong51d37562019-04-23 17:06:46 -0700557def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700558 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700559 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700560 return True
561 except KeyError:
562 return False
563
Tao Bao457cbf62017-03-06 09:56:01 -0800564
Yifan Hong9276cf02019-08-21 16:37:04 -0700565def HasTrebleEnabled(target_files, target_info):
566 def HasVendorPartition(target_files):
567 if os.path.isdir(target_files):
568 return os.path.isdir(os.path.join(target_files, "VENDOR"))
569 if zipfile.is_zipfile(target_files):
570 return HasPartition(zipfile.ZipFile(target_files), "vendor")
571 raise ValueError("Unknown target_files argument")
Yifan Hong51d37562019-04-23 17:06:46 -0700572
Yifan Hong9276cf02019-08-21 16:37:04 -0700573 return (HasVendorPartition(target_files) and
Tao Bao481bab82017-12-21 11:23:09 -0800574 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700575
576
Tao Bao481bab82017-12-21 11:23:09 -0800577def WriteFingerprintAssertion(script, target_info, source_info):
578 source_oem_props = source_info.oem_props
579 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700580
Tao Bao481bab82017-12-21 11:23:09 -0800581 if source_oem_props is None and target_oem_props is None:
582 script.AssertSomeFingerprint(
583 source_info.fingerprint, target_info.fingerprint)
584 elif source_oem_props is not None and target_oem_props is not None:
585 script.AssertSomeThumbprint(
586 target_info.GetBuildProp("ro.build.thumbprint"),
587 source_info.GetBuildProp("ro.build.thumbprint"))
588 elif source_oem_props is None and target_oem_props is not None:
589 script.AssertFingerprintOrThumbprint(
590 source_info.fingerprint,
591 target_info.GetBuildProp("ro.build.thumbprint"))
592 else:
593 script.AssertFingerprintOrThumbprint(
594 target_info.fingerprint,
595 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700596
Doug Zongkerfc44a512014-08-26 13:10:25 -0700597
Yifan Hong9276cf02019-08-21 16:37:04 -0700598def CheckVintfIfTrebleEnabled(target_files, target_info):
599 """Checks compatibility info of the input target files.
Tao Bao21803d32017-04-19 10:16:09 -0700600
Yifan Hong9276cf02019-08-21 16:37:04 -0700601 Metadata used for compatibility verification is retrieved from target_zip.
Tao Bao21803d32017-04-19 10:16:09 -0700602
Yifan Hong9276cf02019-08-21 16:37:04 -0700603 Compatibility should only be checked for devices that have enabled
Tao Baobcd1d162017-08-26 13:10:26 -0700604 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700605
606 Args:
Yifan Hong9276cf02019-08-21 16:37:04 -0700607 target_files: Path to zip file containing the source files to be included
608 for OTA. Can also be the path to extracted directory.
Tao Bao481bab82017-12-21 11:23:09 -0800609 target_info: The BuildInfo instance that holds the target build info.
Tao Bao21803d32017-04-19 10:16:09 -0700610 """
611
Tao Baobcd1d162017-08-26 13:10:26 -0700612 # Will only proceed if the target has enabled the Treble support (as well as
613 # having a /vendor partition).
Yifan Hong9276cf02019-08-21 16:37:04 -0700614 if not HasTrebleEnabled(target_files, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700615 return
616
xunchangabfa2652019-02-19 16:27:10 -0800617 # Skip adding the compatibility package as a workaround for b/114240221. The
618 # compatibility will always fail on devices without qualified kernels.
619 if OPTIONS.skip_compatibility_check:
620 return
621
Yifan Hong9276cf02019-08-21 16:37:04 -0700622 if not check_target_files_vintf.CheckVintf(target_files, target_info):
623 raise RuntimeError("VINTF compatibility check failed")
Tao Bao21803d32017-04-19 10:16:09 -0700624
625
Tianjie Xuf67dd802019-05-20 17:50:36 -0700626def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
627 device_specific):
628 """Returns a ordered dict of block differences with partition name as key."""
629
630 def GetIncrementalBlockDifferenceForPartition(name):
631 if not HasPartition(source_zip, name):
Kelvin Zhang0876c412020-06-23 15:06:58 -0400632 raise RuntimeError(
633 "can't generate incremental that adds {}".format(name))
Tianjie Xuf67dd802019-05-20 17:50:36 -0700634
635 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
636 info_dict=source_info,
637 allow_shared_blocks=allow_shared_blocks)
638
639 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
640 name, 4096, target_info)
641 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
642 info_dict=target_info,
643 allow_shared_blocks=allow_shared_blocks,
Kelvin Zhang0876c412020-06-23 15:06:58 -0400644 hashtree_info_generator=hashtree_info_generator)
Tianjie Xuf67dd802019-05-20 17:50:36 -0700645
646 # Check the first block of the source system partition for remount R/W only
647 # if the filesystem is ext4.
648 partition_source_info = source_info["fstab"]["/" + name]
649 check_first_block = partition_source_info.fs_type == "ext4"
650 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
651 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
652 # b) the blocks listed in block map may not contain all the bytes for a
653 # given file (because they're rounded to be 4K-aligned).
654 partition_target_info = target_info["fstab"]["/" + name]
655 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
656 partition_target_info.fs_type == "squashfs")
Xindong Xu2a7aaa62020-03-13 15:59:22 +0800657 return common.BlockDifference(name, partition_tgt, partition_src,
Tianjie Xuf67dd802019-05-20 17:50:36 -0700658 check_first_block,
659 version=blockimgdiff_version,
660 disable_imgdiff=disable_imgdiff)
661
662 if source_zip:
663 # See notes in common.GetUserImage()
664 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
665 target_info.get('ext4_share_dup_blocks') == "true")
666 blockimgdiff_version = max(
667 int(i) for i in target_info.get(
668 "blockimgdiff_versions", "1").split(","))
669 assert blockimgdiff_version >= 3
670
671 block_diff_dict = collections.OrderedDict()
Yifan Hongcfb917a2020-05-07 14:58:20 -0700672 partition_names = ["system", "vendor", "product", "odm", "system_ext",
673 "vendor_dlkm"]
Tianjie Xuf67dd802019-05-20 17:50:36 -0700674 for partition in partition_names:
675 if not HasPartition(target_zip, partition):
676 continue
677 # Full OTA update.
678 if not source_zip:
679 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
680 info_dict=target_info,
681 reset_file_map=True)
682 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
683 src=None)
684 # Incremental OTA update.
685 else:
686 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
687 partition)
688 assert "system" in block_diff_dict
689
690 # Get the block diffs from the device specific script. If there is a
691 # duplicate block diff for a partition, ignore the diff in the generic script
692 # and use the one in the device specific script instead.
693 if source_zip:
694 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
695 function_name = "IncrementalOTA_GetBlockDifferences"
696 else:
697 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
698 function_name = "FullOTA_GetBlockDifferences"
699
700 if device_specific_diffs:
701 assert all(isinstance(diff, common.BlockDifference)
702 for diff in device_specific_diffs), \
703 "{} is not returning a list of BlockDifference objects".format(
704 function_name)
705 for diff in device_specific_diffs:
706 if diff.partition in block_diff_dict:
707 logger.warning("Duplicate block difference found. Device specific block"
708 " diff for partition '%s' overrides the one in generic"
709 " script.", diff.partition)
710 block_diff_dict[diff.partition] = diff
711
712 return block_diff_dict
713
714
Tao Bao491d7e22018-02-21 13:17:22 -0800715def WriteFullOTAPackage(input_zip, output_file):
Tao Bao1c320f82019-10-04 23:25:12 -0700716 target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700717
Tao Bao481bab82017-12-21 11:23:09 -0800718 # We don't know what version it will be installed on top of. We expect the API
719 # just won't change very often. Similarly for fstab, it might have changed in
720 # the target build.
721 target_api_version = target_info["recovery_api_version"]
722 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700723
Tao Bao481bab82017-12-21 11:23:09 -0800724 if target_info.oem_props and not OPTIONS.oem_no_mount:
725 target_info.WriteMountOemScript(script)
726
Tao Baodf3a48b2018-01-10 16:30:43 -0800727 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700728
Tao Bao491d7e22018-02-21 13:17:22 -0800729 if not OPTIONS.no_signing:
730 staging_file = common.MakeTempFile(suffix='.zip')
731 else:
732 staging_file = output_file
733
734 output_zip = zipfile.ZipFile(
735 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
736
Doug Zongker05d3dea2009-06-22 11:32:31 -0700737 device_specific = common.DeviceSpecificParams(
738 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800739 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700740 output_zip=output_zip,
741 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700742 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700743 metadata=metadata,
744 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700745
Bill Peckhame868aec2019-09-17 17:06:47 -0700746 assert HasRecoveryPatch(input_zip, info_dict=OPTIONS.info_dict)
Doug Zongkerc9253822014-02-04 12:17:58 -0800747
Tao Bao481bab82017-12-21 11:23:09 -0800748 # Assertions (e.g. downgrade check, device properties check).
749 ts = target_info.GetBuildProp("ro.build.date.utc")
750 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700751 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700752
Tao Bao481bab82017-12-21 11:23:09 -0800753 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700754 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800755
Tianjie Xuf67dd802019-05-20 17:50:36 -0700756 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
757 target_info=target_info,
758 source_info=None,
759 device_specific=device_specific)
760
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800761 # Two-step package strategy (in chronological order, which is *not*
762 # the order in which the generated script has things):
763 #
764 # if stage is not "2/3" or "3/3":
765 # write recovery image to boot partition
766 # set stage to "2/3"
767 # reboot to boot partition and restart recovery
768 # else if stage is "2/3":
769 # write recovery image to recovery partition
770 # set stage to "3/3"
771 # reboot to recovery partition and restart recovery
772 # else:
773 # (stage must be "3/3")
774 # set stage to ""
775 # do normal full package installation:
776 # wipe and install system, boot image, etc.
777 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700778 # complete script normally
779 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800780
781 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
782 OPTIONS.input_tmp, "RECOVERY")
783 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800784 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800785 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800786 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800787 assert fs.fs_type.upper() == "EMMC", \
788 "two-step packages only supported on devices with EMMC /misc partitions"
789 bcb_dev = {"bcb_dev": fs.device}
790 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
791 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700792if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800793""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800794
795 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
796 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800797 script.WriteRawImage("/recovery", "recovery.img")
798 script.AppendExtra("""
799set_stage("%(bcb_dev)s", "3/3");
800reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700801else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800802""" % bcb_dev)
803
Tao Baod42e97e2016-11-30 12:11:57 -0800804 # Stage 3/3: Make changes.
805 script.Comment("Stage 3/3")
806
Tao Bao6c55a8a2015-04-08 15:30:27 -0700807 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800808 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -0700809
Doug Zongkere5ff5902012-01-17 10:55:37 -0800810 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700811
Tianjie Xuf67dd802019-05-20 17:50:36 -0700812 # All other partitions as well as the data wipe use 10% of the progress, and
813 # the update of the system partition takes the remaining progress.
814 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700815 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -0800816 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -0700817 progress_dict = {partition: 0.1 for partition in block_diff_dict}
818 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700819
Yifan Hong10c530d2018-12-27 17:34:18 -0800820 if target_info.get('use_dynamic_partitions') == "true":
821 # Use empty source_info_dict to indicate that all partitions / groups must
822 # be re-added.
823 dynamic_partitions_diff = common.DynamicPartitionsDifference(
824 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -0700825 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -0800826 progress_dict=progress_dict)
827 dynamic_partitions_diff.WriteScript(script, output_zip,
828 write_verify_script=OPTIONS.verify)
829 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -0700830 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -0800831 block_diff.WriteScript(script, output_zip,
832 progress=progress_dict.get(block_diff.partition),
833 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -0700834
Yifan Hong9276cf02019-08-21 16:37:04 -0700835 CheckVintfIfTrebleEnabled(OPTIONS.input_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -0700836
Yifan Hong10c530d2018-12-27 17:34:18 -0800837 boot_img = common.GetBootableImage(
838 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -0800839 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -0700840 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700841
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700842 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -0700843
Tianjie Xuf67dd802019-05-20 17:50:36 -0700844 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700845 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700846
Doug Zongker1c390a22009-05-14 19:06:36 -0700847 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700848 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700849
Doug Zongker14833602010-02-02 13:12:04 -0800850 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800851
Doug Zongker922206e2014-03-04 13:16:24 -0800852 if OPTIONS.wipe_user_data:
853 script.ShowProgress(0.1, 10)
854 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700855
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800856 if OPTIONS.two_step:
857 script.AppendExtra("""
858set_stage("%(bcb_dev)s", "");
859""" % bcb_dev)
860 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -0800861
862 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
863 script.Comment("Stage 1/3")
864 _WriteRecoveryImageToBoot(script, output_zip)
865
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800866 script.AppendExtra("""
867set_stage("%(bcb_dev)s", "2/3");
868reboot_now("%(bcb_dev)s", "");
869endif;
870endif;
871""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -0800872
Tao Bao5d182562016-02-23 11:38:39 -0800873 script.SetProgress(1)
874 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -0800875 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -0800876
877 # We haven't written the metadata entry, which will be done in
878 # FinalizeMetadata.
879 common.ZipClose(output_zip)
880
881 needed_property_files = (
882 NonAbOtaPropertyFiles(),
883 )
884 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -0700885
Doug Zongkerfc44a512014-08-26 13:10:25 -0700886
xunchang1cfe2512019-02-19 14:14:48 -0800887def WriteMetadata(metadata, output):
888 """Writes the metadata to the zip archive or a file.
889
890 Args:
891 metadata: The metadata dict for the package.
892 output: A ZipFile object or a string of the output file path.
893 """
894
Tao Bao59cf0c52019-06-25 10:04:24 -0700895 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -0800896 if isinstance(output, zipfile.ZipFile):
897 common.ZipWriteStr(output, METADATA_NAME, value,
898 compress_type=zipfile.ZIP_STORED)
899 return
900
901 with open(output, 'w') as f:
902 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -0700903
Doug Zongkerfc44a512014-08-26 13:10:25 -0700904
Tao Bao481bab82017-12-21 11:23:09 -0800905def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -0800906 # Only incremental OTAs are allowed to reach here.
907 assert OPTIONS.incremental_source is not None
908
Tao Bao481bab82017-12-21 11:23:09 -0800909 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
910 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -0700911 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -0800912
913 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -0800914 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -0700915 raise RuntimeError(
916 "--downgrade or --override_timestamp specified but no downgrade "
917 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -0800918 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -0800919 else:
920 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -0700921 raise RuntimeError(
922 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
923 "Need to specify --override_timestamp OR --downgrade to allow "
924 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -0800925
926
Tao Baodf3a48b2018-01-10 16:30:43 -0800927def GetPackageMetadata(target_info, source_info=None):
928 """Generates and returns the metadata dict.
929
930 It generates a dict() that contains the info to be written into an OTA
931 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -0700932 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -0800933
934 Args:
935 target_info: The BuildInfo instance that holds the target build info.
936 source_info: The BuildInfo instance that holds the source build info, or
937 None if generating full OTA.
938
939 Returns:
940 A dict to be written into package metadata entry.
941 """
Tao Bao1c320f82019-10-04 23:25:12 -0700942 assert isinstance(target_info, common.BuildInfo)
943 assert source_info is None or isinstance(source_info, common.BuildInfo)
Tao Baodf3a48b2018-01-10 16:30:43 -0800944
Tianjied6867162020-05-10 14:30:13 -0700945 separator = '|'
946
947 boot_variable_values = {}
948 if OPTIONS.boot_variable_file:
949 d = common.LoadDictionaryFromFile(OPTIONS.boot_variable_file)
950 for key, values in d.items():
951 boot_variable_values[key] = [val.strip() for val in values.split(',')]
952
953 post_build_devices, post_build_fingerprints = \
954 CalculateRuntimeDevicesAndFingerprints(target_info, boot_variable_values)
Tao Baodf3a48b2018-01-10 16:30:43 -0800955 metadata = {
Tianjied6867162020-05-10 14:30:13 -0700956 'post-build': separator.join(sorted(post_build_fingerprints)),
957 'post-build-incremental': target_info.GetBuildProp(
Tao Baodf3a48b2018-01-10 16:30:43 -0800958 'ro.build.version.incremental'),
Tianjied6867162020-05-10 14:30:13 -0700959 'post-sdk-level': target_info.GetBuildProp(
Tao Bao35dc2552018-02-01 13:18:00 -0800960 'ro.build.version.sdk'),
Tianjied6867162020-05-10 14:30:13 -0700961 'post-security-patch-level': target_info.GetBuildProp(
Tao Bao35dc2552018-02-01 13:18:00 -0800962 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -0800963 }
964
Yifan Hong65afc072020-04-17 10:08:10 -0700965 if target_info.is_ab and not OPTIONS.force_non_ab:
Tao Baodf3a48b2018-01-10 16:30:43 -0800966 metadata['ota-type'] = 'AB'
967 metadata['ota-required-cache'] = '0'
968 else:
969 metadata['ota-type'] = 'BLOCK'
970
971 if OPTIONS.wipe_user_data:
972 metadata['ota-wipe'] = 'yes'
973
Tao Bao393eeb42019-03-06 16:00:38 -0800974 if OPTIONS.retrofit_dynamic_partitions:
975 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
976
Tao Baodf3a48b2018-01-10 16:30:43 -0800977 is_incremental = source_info is not None
978 if is_incremental:
Tianjied6867162020-05-10 14:30:13 -0700979 pre_build_devices, pre_build_fingerprints = \
980 CalculateRuntimeDevicesAndFingerprints(source_info,
981 boot_variable_values)
982 metadata['pre-build'] = separator.join(sorted(pre_build_fingerprints))
Tao Baodf3a48b2018-01-10 16:30:43 -0800983 metadata['pre-build-incremental'] = source_info.GetBuildProp(
984 'ro.build.version.incremental')
Tianjied6867162020-05-10 14:30:13 -0700985 metadata['pre-device'] = separator.join(sorted(pre_build_devices))
Tao Baodf3a48b2018-01-10 16:30:43 -0800986 else:
Tianjied6867162020-05-10 14:30:13 -0700987 metadata['pre-device'] = separator.join(sorted(post_build_devices))
Tao Baodf3a48b2018-01-10 16:30:43 -0800988
Tao Baofaa8e0b2018-04-12 14:31:43 -0700989 # Use the actual post-timestamp, even for a downgrade case.
990 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
991
992 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -0800993 if is_incremental:
994 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -0800995
996 return metadata
997
998
Tao Baod3fc38a2018-03-08 16:09:01 -0800999class PropertyFiles(object):
1000 """A class that computes the property-files string for an OTA package.
1001
1002 A property-files string is a comma-separated string that contains the
1003 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1004 can be fetched directly with the package URL along with the offset/size info.
1005 These strings can be used for streaming A/B OTAs, or allowing an updater to
1006 download package metadata entry directly, without paying the cost of
1007 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001008
Tao Baocc8e2662018-03-01 19:30:00 -08001009 Computing the final property-files string requires two passes. Because doing
1010 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1011 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1012 values.
1013
1014 This class provides functions to be called for each pass. The general flow is
1015 as follows.
1016
Tao Baod3fc38a2018-03-08 16:09:01 -08001017 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001018 # The first pass, which writes placeholders before doing initial signing.
1019 property_files.Compute()
1020 SignOutput()
1021
1022 # The second pass, by replacing the placeholders with actual data.
1023 property_files.Finalize()
1024 SignOutput()
1025
1026 And the caller can additionally verify the final result.
1027
1028 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001029 """
1030
Tao Baocc8e2662018-03-01 19:30:00 -08001031 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001032 self.name = None
1033 self.required = ()
1034 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001035
Tao Baocc8e2662018-03-01 19:30:00 -08001036 def Compute(self, input_zip):
1037 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001038
Tao Baocc8e2662018-03-01 19:30:00 -08001039 We reserve extra space for the offset and size of the metadata entry itself,
1040 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001041
Tao Baocc8e2662018-03-01 19:30:00 -08001042 Args:
1043 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001044
Tao Baocc8e2662018-03-01 19:30:00 -08001045 Returns:
1046 A string with placeholders for the metadata offset/size info, e.g.
1047 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1048 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001049 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001050
Tao Baod2ce2ed2018-03-16 12:59:42 -07001051 class InsufficientSpaceException(Exception):
1052 pass
1053
Tao Baocc8e2662018-03-01 19:30:00 -08001054 def Finalize(self, input_zip, reserved_length):
1055 """Finalizes a property-files string with actual METADATA offset/size info.
1056
1057 The input ZIP file has been signed, with the ZIP entries in the desired
1058 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1059 the ZIP entry offsets and construct the property-files string with actual
1060 data. Note that during this process, we must pad the property-files string
1061 to the reserved length, so that the METADATA entry size remains the same.
1062 Otherwise the entries' offsets and sizes may change again.
1063
1064 Args:
1065 input_zip: The input ZIP file.
1066 reserved_length: The reserved length of the property-files string during
1067 the call to Compute(). The final string must be no more than this
1068 size.
1069
1070 Returns:
1071 A property-files string including the metadata offset/size info, e.g.
1072 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1073
1074 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001075 InsufficientSpaceException: If the reserved length is insufficient to hold
1076 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001077 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001078 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001079 if len(result) > reserved_length:
1080 raise self.InsufficientSpaceException(
1081 'Insufficient reserved space: reserved={}, actual={}'.format(
1082 reserved_length, len(result)))
1083
Tao Baocc8e2662018-03-01 19:30:00 -08001084 result += ' ' * (reserved_length - len(result))
1085 return result
1086
1087 def Verify(self, input_zip, expected):
1088 """Verifies the input ZIP file contains the expected property-files string.
1089
1090 Args:
1091 input_zip: The input ZIP file.
1092 expected: The property-files string that's computed from Finalize().
1093
1094 Raises:
1095 AssertionError: On finding a mismatch.
1096 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001097 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001098 assert actual == expected, \
1099 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1100
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001101 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1102 """
1103 Constructs the property-files string per request.
1104
1105 Args:
1106 zip_file: The input ZIP file.
1107 reserved_length: The reserved length of the property-files string.
1108
1109 Returns:
1110 A property-files string including the metadata offset/size info, e.g.
1111 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1112 """
Tao Baocc8e2662018-03-01 19:30:00 -08001113
1114 def ComputeEntryOffsetSize(name):
1115 """Computes the zip entry offset and size."""
1116 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001117 offset = info.header_offset
1118 offset += zipfile.sizeFileHeader
1119 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001120 size = info.file_size
1121 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1122
1123 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001124 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001125 for entry in self.required:
1126 tokens.append(ComputeEntryOffsetSize(entry))
1127 for entry in self.optional:
1128 if entry in zip_file.namelist():
1129 tokens.append(ComputeEntryOffsetSize(entry))
1130
1131 # 'META-INF/com/android/metadata' is required. We don't know its actual
1132 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001133 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1134 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1135 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1136 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001137 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001138 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001139 else:
1140 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1141
1142 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001143
Tao Bao85f16982018-03-08 16:28:33 -08001144 def _GetPrecomputed(self, input_zip):
1145 """Computes the additional tokens to be included into the property-files.
1146
1147 This applies to tokens without actual ZIP entries, such as
1148 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1149 that they can download the payload metadata directly with the info.
1150
1151 Args:
1152 input_zip: The input zip file.
1153
1154 Returns:
1155 A list of strings (tokens) to be added to the property-files string.
1156 """
1157 # pylint: disable=no-self-use
1158 # pylint: disable=unused-argument
1159 return []
1160
Tao Baofe5b69a2018-03-02 09:47:43 -08001161
Tao Baod3fc38a2018-03-08 16:09:01 -08001162class StreamingPropertyFiles(PropertyFiles):
1163 """A subclass for computing the property-files for streaming A/B OTAs."""
1164
1165 def __init__(self):
1166 super(StreamingPropertyFiles, self).__init__()
1167 self.name = 'ota-streaming-property-files'
1168 self.required = (
1169 # payload.bin and payload_properties.txt must exist.
1170 'payload.bin',
1171 'payload_properties.txt',
1172 )
1173 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001174 # care_map is available only if dm-verity is enabled.
1175 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001176 'care_map.txt',
1177 # compatibility.zip is available only if target supports Treble.
1178 'compatibility.zip',
1179 )
1180
1181
Tao Bao85f16982018-03-08 16:28:33 -08001182class AbOtaPropertyFiles(StreamingPropertyFiles):
1183 """The property-files for A/B OTA that includes payload_metadata.bin info.
1184
1185 Since P, we expose one more token (aka property-file), in addition to the ones
1186 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1187 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1188 doesn't exist as a separate ZIP entry, but can be used to verify if the
1189 payload can be applied on the given device.
1190
1191 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1192 and the newly added 'ota-property-files' in P. The new token will only be
1193 available in 'ota-property-files'.
1194 """
1195
1196 def __init__(self):
1197 super(AbOtaPropertyFiles, self).__init__()
1198 self.name = 'ota-property-files'
1199
1200 def _GetPrecomputed(self, input_zip):
1201 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1202 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1203
1204 @staticmethod
1205 def _GetPayloadMetadataOffsetAndSize(input_zip):
1206 """Computes the offset and size of the payload metadata for a given package.
1207
1208 (From system/update_engine/update_metadata.proto)
1209 A delta update file contains all the deltas needed to update a system from
1210 one specific version to another specific version. The update format is
1211 represented by this struct pseudocode:
1212
1213 struct delta_update_file {
1214 char magic[4] = "CrAU";
1215 uint64 file_format_version;
1216 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1217
1218 // Only present if format_version > 1:
1219 uint32 metadata_signature_size;
1220
1221 // The Bzip2 compressed DeltaArchiveManifest
1222 char manifest[metadata_signature_size];
1223
1224 // The signature of the metadata (from the beginning of the payload up to
1225 // this location, not including the signature itself). This is a
1226 // serialized Signatures message.
1227 char medatada_signature_message[metadata_signature_size];
1228
1229 // Data blobs for files, no specific format. The specific offset
1230 // and length of each data blob is recorded in the DeltaArchiveManifest.
1231 struct {
1232 char data[];
1233 } blobs[];
1234
1235 // These two are not signed:
1236 uint64 payload_signatures_message_size;
1237 char payload_signatures_message[];
1238 };
1239
1240 'payload-metadata.bin' contains all the bytes from the beginning of the
1241 payload, till the end of 'medatada_signature_message'.
1242 """
1243 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001244 payload_offset = payload_info.header_offset
1245 payload_offset += zipfile.sizeFileHeader
1246 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001247 payload_size = payload_info.file_size
1248
Tao Bao59cf0c52019-06-25 10:04:24 -07001249 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001250 header_bin = payload_fp.read(24)
1251
1252 # network byte order (big-endian)
1253 header = struct.unpack("!IQQL", header_bin)
1254
1255 # 'CrAU'
1256 magic = header[0]
1257 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1258
1259 manifest_size = header[2]
1260 metadata_signature_size = header[3]
1261 metadata_total = 24 + manifest_size + metadata_signature_size
1262 assert metadata_total < payload_size
1263
1264 return (payload_offset, metadata_total)
1265
1266
Tao Bao491d7e22018-02-21 13:17:22 -08001267class NonAbOtaPropertyFiles(PropertyFiles):
1268 """The property-files for non-A/B OTA.
1269
1270 For non-A/B OTA, the property-files string contains the info for METADATA
1271 entry, with which a system updater can be fetched the package metadata prior
1272 to downloading the entire package.
1273 """
1274
1275 def __init__(self):
1276 super(NonAbOtaPropertyFiles, self).__init__()
1277 self.name = 'ota-property-files'
1278
1279
Tao Baod3fc38a2018-03-08 16:09:01 -08001280def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001281 """Finalizes the metadata and signs an A/B OTA package.
1282
1283 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1284 that contains the offsets and sizes for the ZIP entries. An example
1285 property-files string is as follows.
1286
1287 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1288
1289 OTA server can pass down this string, in addition to the package URL, to the
1290 system update client. System update client can then fetch individual ZIP
1291 entries (ZIP_STORED) directly at the given offset of the URL.
1292
1293 Args:
1294 metadata: The metadata dict for the package.
1295 input_file: The input ZIP filename that doesn't contain the package METADATA
1296 entry yet.
1297 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001298 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001299 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001300
Tao Baod2ce2ed2018-03-16 12:59:42 -07001301 def ComputeAllPropertyFiles(input_file, needed_property_files):
1302 # Write the current metadata entry with placeholders.
1303 with zipfile.ZipFile(input_file) as input_zip:
1304 for property_files in needed_property_files:
1305 metadata[property_files.name] = property_files.Compute(input_zip)
1306 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001307
Tao Baod2ce2ed2018-03-16 12:59:42 -07001308 if METADATA_NAME in namelist:
1309 common.ZipDelete(input_file, METADATA_NAME)
1310 output_zip = zipfile.ZipFile(input_file, 'a')
1311 WriteMetadata(metadata, output_zip)
1312 common.ZipClose(output_zip)
1313
1314 if OPTIONS.no_signing:
1315 return input_file
1316
Tao Bao491d7e22018-02-21 13:17:22 -08001317 prelim_signing = common.MakeTempFile(suffix='.zip')
1318 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001319 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001320
Tao Baod2ce2ed2018-03-16 12:59:42 -07001321 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1322 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1323 for property_files in needed_property_files:
1324 metadata[property_files.name] = property_files.Finalize(
1325 prelim_signing_zip, len(metadata[property_files.name]))
1326
1327 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1328 # entries, as well as padding the entry headers. We do a preliminary signing
1329 # (with an incomplete metadata entry) to allow that to happen. Then compute
1330 # the ZIP entry offsets, write back the final metadata and do the final
1331 # signing.
1332 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1333 try:
1334 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1335 except PropertyFiles.InsufficientSpaceException:
1336 # Even with the preliminary signing, the entry orders may change
1337 # dramatically, which leads to insufficiently reserved space during the
1338 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1339 # preliminary signing works, based on the already ordered ZIP entries, to
1340 # address the issue.
1341 prelim_signing = ComputeAllPropertyFiles(
1342 prelim_signing, needed_property_files)
1343 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001344
1345 # Replace the METADATA entry.
1346 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001347 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001348 WriteMetadata(metadata, output_zip)
1349 common.ZipClose(output_zip)
1350
1351 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001352 if OPTIONS.no_signing:
1353 output_file = prelim_signing
1354 else:
1355 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001356
1357 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001358 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001359 for property_files in needed_property_files:
1360 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001361
xunchang1cfe2512019-02-19 14:14:48 -08001362 # If requested, dump the metadata to a separate file.
1363 output_metadata_path = OPTIONS.output_metadata_path
1364 if output_metadata_path:
1365 WriteMetadata(metadata, output_metadata_path)
1366
Tao Baofe5b69a2018-03-02 09:47:43 -08001367
Tao Bao491d7e22018-02-21 13:17:22 -08001368def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao1c320f82019-10-04 23:25:12 -07001369 target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1370 source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001371
Tao Bao481bab82017-12-21 11:23:09 -08001372 target_api_version = target_info["recovery_api_version"]
1373 source_api_version = source_info["recovery_api_version"]
1374 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001375 logger.warning(
1376 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001377
Tao Bao481bab82017-12-21 11:23:09 -08001378 script = edify_generator.EdifyGenerator(
1379 source_api_version, target_info, fstab=source_info["fstab"])
1380
1381 if target_info.oem_props or source_info.oem_props:
1382 if not OPTIONS.oem_no_mount:
1383 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001384
Tao Baodf3a48b2018-01-10 16:30:43 -08001385 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001386
Tao Bao491d7e22018-02-21 13:17:22 -08001387 if not OPTIONS.no_signing:
1388 staging_file = common.MakeTempFile(suffix='.zip')
1389 else:
1390 staging_file = output_file
1391
1392 output_zip = zipfile.ZipFile(
1393 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1394
Geremy Condra36bd3652014-02-06 19:45:10 -08001395 device_specific = common.DeviceSpecificParams(
1396 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001397 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001398 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001399 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001400 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001401 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001402 output_zip=output_zip,
1403 script=script,
1404 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001405 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001406
Geremy Condra36bd3652014-02-06 19:45:10 -08001407 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001408 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001409 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001410 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001411 updating_boot = (not OPTIONS.two_step and
1412 (source_boot.data != target_boot.data))
1413
Geremy Condra36bd3652014-02-06 19:45:10 -08001414 target_recovery = common.GetBootableImage(
1415 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001416
Tianjie Xuf67dd802019-05-20 17:50:36 -07001417 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1418 source_zip=source_zip,
1419 target_info=target_info,
1420 source_info=source_info,
1421 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001422
Yifan Hong9276cf02019-08-21 16:37:04 -07001423 CheckVintfIfTrebleEnabled(OPTIONS.target_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001424
Tao Bao481bab82017-12-21 11:23:09 -08001425 # Assertions (e.g. device properties check).
1426 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001427 device_specific.IncrementalOTA_Assertions()
1428
1429 # Two-step incremental package strategy (in chronological order,
1430 # which is *not* the order in which the generated script has
1431 # things):
1432 #
1433 # if stage is not "2/3" or "3/3":
1434 # do verification on current system
1435 # write recovery image to boot partition
1436 # set stage to "2/3"
1437 # reboot to boot partition and restart recovery
1438 # else if stage is "2/3":
1439 # write recovery image to recovery partition
1440 # set stage to "3/3"
1441 # reboot to recovery partition and restart recovery
1442 # else:
1443 # (stage must be "3/3")
1444 # perform update:
1445 # patch system files, etc.
1446 # force full install of new boot image
1447 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001448 # complete script normally
1449 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001450
1451 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001452 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001453 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001454 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001455 assert fs.fs_type.upper() == "EMMC", \
1456 "two-step packages only supported on devices with EMMC /misc partitions"
Kelvin Zhang0876c412020-06-23 15:06:58 -04001457 bcb_dev = {"bcb_dev": fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001458 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1459 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001460if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001461""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001462
1463 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1464 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001465 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001466 script.WriteRawImage("/recovery", "recovery.img")
1467 script.AppendExtra("""
1468set_stage("%(bcb_dev)s", "3/3");
1469reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001470else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001471""" % bcb_dev)
1472
Tao Baod42e97e2016-11-30 12:11:57 -08001473 # Stage 1/3: (a) Verify the current system.
1474 script.Comment("Stage 1/3")
1475
Tao Bao6c55a8a2015-04-08 15:30:27 -07001476 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001477 script.Print("Source: {}".format(source_info.fingerprint))
1478 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001479
Geremy Condra36bd3652014-02-06 19:45:10 -08001480 script.Print("Verifying current system...")
1481
1482 device_specific.IncrementalOTA_VerifyBegin()
1483
Tao Bao481bab82017-12-21 11:23:09 -08001484 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001485
Tao Baod8d14be2016-02-04 14:26:02 -08001486 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001487 required_cache_sizes = [diff.required_cache for diff in
1488 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001489 if updating_boot:
Yifan Hongbdb32012020-05-07 12:38:53 -07001490 boot_type, boot_device_expr = common.GetTypeAndDeviceExpr("/boot",
1491 source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001492 d = common.Difference(target_boot, source_boot)
1493 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001494 if d is None:
1495 include_full_boot = True
1496 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1497 else:
1498 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001499
Tao Bao32fcdab2018-10-12 10:30:39 -07001500 logger.info(
1501 "boot target: %d source: %d diff: %d", target_boot.size,
1502 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001503
Tao Bao51216552018-08-26 11:53:15 -07001504 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001505
Yifan Hongbdb32012020-05-07 12:38:53 -07001506 target_expr = 'concat("{}:",{},":{}:{}")'.format(
1507 boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
1508 source_expr = 'concat("{}:",{},":{}:{}")'.format(
1509 boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
1510 script.PatchPartitionExprCheck(target_expr, source_expr)
Tao Bao51216552018-08-26 11:53:15 -07001511
Tianjie Xuf67dd802019-05-20 17:50:36 -07001512 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001513
Tianjie Xuf67dd802019-05-20 17:50:36 -07001514 if required_cache_sizes:
1515 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1516
1517 # Verify the existing partitions.
1518 for diff in block_diff_dict.values():
1519 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001520
1521 device_specific.IncrementalOTA_VerifyEnd()
1522
1523 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001524 # Stage 1/3: (b) Write recovery image to /boot.
1525 _WriteRecoveryImageToBoot(script, output_zip)
1526
Geremy Condra36bd3652014-02-06 19:45:10 -08001527 script.AppendExtra("""
1528set_stage("%(bcb_dev)s", "2/3");
1529reboot_now("%(bcb_dev)s", "");
1530else
1531""" % bcb_dev)
1532
Tao Baod42e97e2016-11-30 12:11:57 -08001533 # Stage 3/3: Make changes.
1534 script.Comment("Stage 3/3")
1535
Geremy Condra36bd3652014-02-06 19:45:10 -08001536 script.Comment("---- start making changes here ----")
1537
1538 device_specific.IncrementalOTA_InstallBegin()
1539
Tianjie Xuf67dd802019-05-20 17:50:36 -07001540 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1541 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001542
1543 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1544 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1545 raise RuntimeError(
1546 "can't generate incremental that disables dynamic partitions")
1547 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1548 info_dict=OPTIONS.target_info_dict,
1549 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001550 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001551 progress_dict=progress_dict)
1552 dynamic_partitions_diff.WriteScript(
1553 script, output_zip, write_verify_script=OPTIONS.verify)
1554 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001555 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001556 block_diff.WriteScript(script, output_zip,
1557 progress=progress_dict.get(block_diff.partition),
1558 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001559
1560 if OPTIONS.two_step:
1561 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1562 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001563 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001564
1565 if not OPTIONS.two_step:
1566 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001567 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001568 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001569 script.Print("Installing boot image...")
1570 script.WriteRawImage("/boot", "boot.img")
1571 else:
1572 # Produce the boot image by applying a patch to the current
1573 # contents of the boot partition, and write it back to the
1574 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001575 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001576 script.Print("Patching boot image...")
1577 script.ShowProgress(0.1, 10)
Yifan Hongbdb32012020-05-07 12:38:53 -07001578 target_expr = 'concat("{}:",{},":{}:{}")'.format(
1579 boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
1580 source_expr = 'concat("{}:",{},":{}:{}")'.format(
1581 boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
1582 script.PatchPartitionExpr(target_expr, source_expr, '"boot.img.p"')
Geremy Condra36bd3652014-02-06 19:45:10 -08001583 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001584 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001585
1586 # Do device-specific installation (eg, write radio image).
1587 device_specific.IncrementalOTA_InstallEnd()
1588
1589 if OPTIONS.extra_script is not None:
1590 script.AppendExtra(OPTIONS.extra_script)
1591
Doug Zongker922206e2014-03-04 13:16:24 -08001592 if OPTIONS.wipe_user_data:
1593 script.Print("Erasing user data...")
1594 script.FormatPartition("/data")
1595
Geremy Condra36bd3652014-02-06 19:45:10 -08001596 if OPTIONS.two_step:
1597 script.AppendExtra("""
1598set_stage("%(bcb_dev)s", "");
1599endif;
1600endif;
1601""" % bcb_dev)
1602
1603 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001604 # For downgrade OTAs, we prefer to use the update-binary in the source
1605 # build that is actually newer than the one in the target build.
1606 if OPTIONS.downgrade:
1607 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1608 else:
1609 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001610 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001611
1612 # We haven't written the metadata entry yet, which will be handled in
1613 # FinalizeMetadata().
1614 common.ZipClose(output_zip)
1615
1616 # Sign the generated zip package unless no_signing is specified.
1617 needed_property_files = (
1618 NonAbOtaPropertyFiles(),
1619 )
1620 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001621
Doug Zongker32b527d2014-03-04 10:03:02 -08001622
Tao Bao15a146a2018-02-21 16:06:59 -08001623def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001624 """Returns a target-files.zip file for generating secondary payload.
1625
1626 Although the original target-files.zip already contains secondary slot
1627 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1628 ones without _other suffix. Note that we cannot instead modify the names in
1629 META/ab_partitions.txt, because there are no matching partitions on device.
1630
1631 For the partitions that don't have secondary images, the ones for primary
1632 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1633 bootloader images in the inactive slot.
1634
1635 Args:
1636 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001637 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001638
1639 Returns:
1640 The filename of the target-files.zip for generating secondary payload.
1641 """
Tianjie Xu1c808002019-09-11 00:29:26 -07001642
1643 def GetInfoForSecondaryImages(info_file):
1644 """Updates info file for secondary payload generation.
1645
1646 Scan each line in the info file, and remove the unwanted partitions from
1647 the dynamic partition list in the related properties. e.g.
1648 "super_google_dynamic_partitions_partition_list=system vendor product"
1649 will become "super_google_dynamic_partitions_partition_list=system".
1650
1651 Args:
1652 info_file: The input info file. e.g. misc_info.txt.
1653
1654 Returns:
1655 A string of the updated info content.
1656 """
1657
1658 output_list = []
1659 with open(info_file) as f:
1660 lines = f.read().splitlines()
1661
1662 # The suffix in partition_list variables that follows the name of the
1663 # partition group.
1664 LIST_SUFFIX = 'partition_list'
1665 for line in lines:
1666 if line.startswith('#') or '=' not in line:
1667 output_list.append(line)
1668 continue
1669 key, value = line.strip().split('=', 1)
1670 if key == 'dynamic_partition_list' or key.endswith(LIST_SUFFIX):
1671 partitions = value.split()
1672 partitions = [partition for partition in partitions if partition
Tao Bao3e759462019-09-17 22:43:11 -07001673 not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Tianjie Xu1c808002019-09-11 00:29:26 -07001674 output_list.append('{}={}'.format(key, ' '.join(partitions)))
Kelvin Zhang0876c412020-06-23 15:06:58 -04001675 elif key in ['virtual_ab', "virtual_ab_retrofit"]:
Yifan Hongfe073432019-11-01 12:28:31 -07001676 # Remove virtual_ab flag from secondary payload so that OTA client
1677 # don't use snapshots for secondary update
1678 pass
Tianjie Xu1c808002019-09-11 00:29:26 -07001679 else:
1680 output_list.append(line)
1681 return '\n'.join(output_list)
1682
Tao Baof7140c02018-01-30 17:09:24 -08001683 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1684 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1685
Tao Baodba59ee2018-01-09 13:21:02 -08001686 with zipfile.ZipFile(input_file, 'r') as input_zip:
1687 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001688
Tao Bao0ff15de2019-03-20 11:26:06 -07001689 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001690 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001691 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1692 if info.filename == 'IMAGES/system_other.img':
1693 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1694
1695 # Primary images and friends need to be skipped explicitly.
1696 elif info.filename in ('IMAGES/system.img',
1697 'IMAGES/system.map'):
1698 pass
Tao Bao3e759462019-09-17 22:43:11 -07001699
1700 # Copy images that are not in SECONDARY_PAYLOAD_SKIPPED_IMAGES.
1701 elif info.filename.startswith(('IMAGES/', 'RADIO/')):
1702 image_name = os.path.basename(info.filename)
1703 if image_name not in ['{}.img'.format(partition) for partition in
1704 SECONDARY_PAYLOAD_SKIPPED_IMAGES]:
1705 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001706
Tao Bao15a146a2018-02-21 16:06:59 -08001707 # Skip copying the postinstall config if requested.
1708 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1709 pass
1710
Tianjie Xu1c808002019-09-11 00:29:26 -07001711 elif info.filename.startswith('META/'):
1712 # Remove the unnecessary partitions for secondary images from the
1713 # ab_partitions file.
1714 if info.filename == AB_PARTITIONS:
1715 with open(unzipped_file) as f:
1716 partition_list = f.read().splitlines()
1717 partition_list = [partition for partition in partition_list if partition
Tao Bao3e759462019-09-17 22:43:11 -07001718 and partition not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001719 common.ZipWriteStr(target_zip, info.filename,
1720 '\n'.join(partition_list))
Tianjie Xu1c808002019-09-11 00:29:26 -07001721 # Remove the unnecessary partitions from the dynamic partitions list.
1722 elif (info.filename == 'META/misc_info.txt' or
1723 info.filename == DYNAMIC_PARTITION_INFO):
1724 modified_info = GetInfoForSecondaryImages(unzipped_file)
1725 common.ZipWriteStr(target_zip, info.filename, modified_info)
1726 else:
1727 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001728
Tao Baof7140c02018-01-30 17:09:24 -08001729 common.ZipClose(target_zip)
1730
1731 return target_file
1732
1733
Tao Bao15a146a2018-02-21 16:06:59 -08001734def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1735 """Returns a target-files.zip that's not containing postinstall_config.txt.
1736
1737 This allows brillo_update_payload script to skip writing all the postinstall
1738 hooks in the generated payload. The input target-files.zip file will be
1739 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1740 contain the postinstall_config.txt entry, the input file will be returned.
1741
1742 Args:
1743 input_file: The input target-files.zip filename.
1744
1745 Returns:
1746 The filename of target-files.zip that doesn't contain postinstall config.
1747 """
1748 # We should only make a copy if postinstall_config entry exists.
1749 with zipfile.ZipFile(input_file, 'r') as input_zip:
1750 if POSTINSTALL_CONFIG not in input_zip.namelist():
1751 return input_file
1752
1753 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1754 shutil.copyfile(input_file, target_file)
1755 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1756 return target_file
1757
1758
Yifan Hong50e79542018-11-08 17:44:12 -08001759def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001760 super_block_devices,
1761 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001762 """Returns a target-files.zip for retrofitting dynamic partitions.
1763
1764 This allows brillo_update_payload to generate an OTA based on the exact
1765 bits on the block devices. Postinstall is disabled.
1766
1767 Args:
1768 input_file: The input target-files.zip filename.
1769 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001770 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001771
1772 Returns:
1773 The filename of target-files.zip with *.img replaced with super_*.img for
1774 each block device in super_block_devices.
1775 """
1776 assert super_block_devices, "No super_block_devices are specified."
1777
1778 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001779 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001780
1781 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1782 shutil.copyfile(input_file, target_file)
1783
Tao Baoa3705452019-06-24 15:33:41 -07001784 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001785 namelist = input_zip.namelist()
1786
Yifan Hongb433eba2019-03-06 12:42:53 -08001787 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1788
1789 # Remove partitions from META/ab_partitions.txt that is in
1790 # dynamic_partition_list but not in super_block_devices so that
1791 # brillo_update_payload won't generate update for those logical partitions.
1792 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1793 with open(ab_partitions_file) as f:
1794 ab_partitions_lines = f.readlines()
1795 ab_partitions = [line.strip() for line in ab_partitions_lines]
1796 # Assert that all super_block_devices are in ab_partitions
1797 super_device_not_updated = [partition for partition in super_block_devices
1798 if partition not in ab_partitions]
1799 assert not super_device_not_updated, \
1800 "{} is in super_block_devices but not in {}".format(
1801 super_device_not_updated, AB_PARTITIONS)
1802 # ab_partitions -= (dynamic_partition_list - super_block_devices)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001803 new_ab_partitions = common.MakeTempFile(
1804 prefix="ab_partitions", suffix=".txt")
Yifan Hongb433eba2019-03-06 12:42:53 -08001805 with open(new_ab_partitions, 'w') as f:
1806 for partition in ab_partitions:
1807 if (partition in dynamic_partition_list and
1808 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001809 logger.info("Dropping %s from ab_partitions.txt", partition)
1810 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001811 f.write(partition + "\n")
1812 to_delete = [AB_PARTITIONS]
1813
Yifan Hong50e79542018-11-08 17:44:12 -08001814 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001815 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001816
1817 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1818 # is a regular update on devices without dynamic partitions support.
1819 to_delete += [DYNAMIC_PARTITION_INFO]
1820
Tao Bao03fecb62018-11-28 10:59:23 -08001821 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001822 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001823 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001824
1825 common.ZipDelete(target_file, to_delete)
1826
Yifan Hong50e79542018-11-08 17:44:12 -08001827 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1828
1829 # Write super_{foo}.img as {foo}.img.
1830 for src, dst in replace.items():
1831 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001832 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001833 unzipped_file = os.path.join(input_tmp, *src.split('/'))
1834 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
1835
Yifan Hongb433eba2019-03-06 12:42:53 -08001836 # Write new ab_partitions.txt file
1837 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
1838
Yifan Hong50e79542018-11-08 17:44:12 -08001839 common.ZipClose(target_zip)
1840
1841 return target_file
1842
1843
Tao Baof0c4aa22018-04-30 20:29:30 -07001844def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08001845 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07001846 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08001847 if not OPTIONS.no_signing:
1848 staging_file = common.MakeTempFile(suffix='.zip')
1849 else:
1850 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08001851 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08001852 compression=zipfile.ZIP_DEFLATED)
1853
Tao Bao481bab82017-12-21 11:23:09 -08001854 if source_file is not None:
Tao Bao1c320f82019-10-04 23:25:12 -07001855 target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1856 source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Tao Bao481bab82017-12-21 11:23:09 -08001857 else:
Tao Bao1c320f82019-10-04 23:25:12 -07001858 target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Tao Bao481bab82017-12-21 11:23:09 -08001859 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08001860
Tao Bao481bab82017-12-21 11:23:09 -08001861 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08001862 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08001863
Yifan Hong50e79542018-11-08 17:44:12 -08001864 if OPTIONS.retrofit_dynamic_partitions:
1865 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08001866 target_file, target_info.get("super_block_devices").strip().split(),
1867 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08001868 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08001869 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
1870
Tao Bao40b18822018-01-30 18:19:04 -08001871 # Generate payload.
1872 payload = Payload()
1873
1874 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07001875 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08001876 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07001877 else:
1878 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08001879 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08001880
Tao Bao40b18822018-01-30 18:19:04 -08001881 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08001882
Tao Bao40b18822018-01-30 18:19:04 -08001883 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08001884 payload_signer = PayloadSigner()
1885 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08001886
Tao Bao40b18822018-01-30 18:19:04 -08001887 # Write the payload into output zip.
1888 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08001889
Tao Baof7140c02018-01-30 17:09:24 -08001890 # Generate and include the secondary payload that installs secondary images
1891 # (e.g. system_other.img).
1892 if OPTIONS.include_secondary:
1893 # We always include a full payload for the secondary slot, even when
1894 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08001895 secondary_target_file = GetTargetFilesZipForSecondaryImages(
1896 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08001897 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08001898 secondary_payload.Generate(secondary_target_file,
1899 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08001900 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08001901 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001902
Tianjie Xucfa86222016-03-07 16:31:19 -08001903 # If dm-verity is supported for the device, copy contents of care_map
1904 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07001905 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08001906 if (target_info.get("verity") == "true" or
1907 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001908 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
1909 "META/" + x in target_zip.namelist()]
1910
1911 # Adds care_map if either the protobuf format or the plain text one exists.
1912 if care_map_list:
1913 care_map_name = care_map_list[0]
1914 care_map_data = target_zip.read("META/" + care_map_name)
1915 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08001916 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001917 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08001918 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08001919 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001920 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07001921
Tao Bao21803d32017-04-19 10:16:09 -07001922 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08001923
Yifan Hong9276cf02019-08-21 16:37:04 -07001924 CheckVintfIfTrebleEnabled(target_file, target_info)
1925
Tao Baofe5b69a2018-03-02 09:47:43 -08001926 # We haven't written the metadata entry yet, which will be handled in
1927 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08001928 common.ZipClose(output_zip)
1929
Tao Bao85f16982018-03-08 16:28:33 -08001930 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
1931 # all the info of the latter. However, system updaters and OTA servers need to
1932 # take time to switch to the new flag. We keep both of the flags for
1933 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08001934 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08001935 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08001936 StreamingPropertyFiles(),
1937 )
1938 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08001939
Tao Baoc098e9e2016-01-07 13:03:56 -08001940
Tao Baof0c4aa22018-04-30 20:29:30 -07001941def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
1942 """Generates a non-A/B OTA package."""
1943 # Sanity check the loaded info dicts first.
1944 if OPTIONS.info_dict.get("no_recovery") == "true":
1945 raise common.ExternalError(
1946 "--- target build has specified no recovery ---")
1947
1948 # Non-A/B OTAs rely on /cache partition to store temporary files.
1949 cache_size = OPTIONS.info_dict.get("cache_size")
1950 if cache_size is None:
1951 logger.warning("--- can't determine the cache partition size ---")
1952 OPTIONS.cache_size = cache_size
1953
1954 if OPTIONS.extra_script is not None:
1955 with open(OPTIONS.extra_script) as fp:
1956 OPTIONS.extra_script = fp.read()
1957
1958 if OPTIONS.extracted_input is not None:
1959 OPTIONS.input_tmp = OPTIONS.extracted_input
1960 else:
1961 logger.info("unzipping target target-files...")
1962 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
1963 OPTIONS.target_tmp = OPTIONS.input_tmp
1964
1965 # If the caller explicitly specified the device-specific extensions path via
1966 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
1967 # is present in the target target_files. Otherwise, take the path of the file
1968 # from 'tool_extensions' in the info dict and look for that in the local
1969 # filesystem, relative to the current directory.
1970 if OPTIONS.device_specific is None:
1971 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1972 if os.path.exists(from_input):
1973 logger.info("(using device-specific extensions from target_files)")
1974 OPTIONS.device_specific = from_input
1975 else:
1976 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
1977
1978 if OPTIONS.device_specific is not None:
1979 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1980
1981 # Generate a full OTA.
1982 if source_file is None:
1983 with zipfile.ZipFile(target_file) as input_zip:
1984 WriteFullOTAPackage(
1985 input_zip,
1986 output_file)
1987
1988 # Generate an incremental OTA.
1989 else:
1990 logger.info("unzipping source target-files...")
1991 OPTIONS.source_tmp = common.UnzipTemp(
1992 OPTIONS.incremental_source, UNZIP_PATTERN)
1993 with zipfile.ZipFile(target_file) as input_zip, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001994 zipfile.ZipFile(source_file) as source_zip:
Tao Baof0c4aa22018-04-30 20:29:30 -07001995 WriteBlockIncrementalOTAPackage(
1996 input_zip,
1997 source_zip,
1998 output_file)
1999
2000
Tianjied6867162020-05-10 14:30:13 -07002001def CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values):
2002 """Returns a tuple of sets for runtime devices and fingerprints"""
Tianjie Xu9afb2212020-05-10 21:48:15 +00002003
Tianjied6867162020-05-10 14:30:13 -07002004 device_names = {build_info.device}
Tianjie Xu9afb2212020-05-10 21:48:15 +00002005 fingerprints = {build_info.fingerprint}
2006
Tianjied6867162020-05-10 14:30:13 -07002007 if not boot_variable_values:
2008 return device_names, fingerprints
Tianjie Xu9afb2212020-05-10 21:48:15 +00002009
2010 # Calculate all possible combinations of the values for the boot variables.
Tianjied6867162020-05-10 14:30:13 -07002011 keys = boot_variable_values.keys()
2012 value_list = boot_variable_values.values()
Tianjie Xu9afb2212020-05-10 21:48:15 +00002013 combinations = [dict(zip(keys, values))
2014 for values in itertools.product(*value_list)]
2015 for placeholder_values in combinations:
2016 # Reload the info_dict as some build properties may change their values
2017 # based on the value of ro.boot* properties.
Tianjied6867162020-05-10 14:30:13 -07002018 info_dict = copy.deepcopy(build_info.info_dict)
Tianjie Xu9afb2212020-05-10 21:48:15 +00002019 for partition in common.PARTITIONS_WITH_CARE_MAP:
2020 partition_prop_key = "{}.build.prop".format(partition)
Tianjieeb06afb2020-06-11 22:51:07 -07002021 input_file = info_dict[partition_prop_key].input_file
2022 if isinstance(input_file, zipfile.ZipFile):
2023 with zipfile.ZipFile(input_file.filename) as input_zip:
2024 info_dict[partition_prop_key] = \
2025 common.PartitionBuildProps.FromInputFile(input_zip, partition,
2026 placeholder_values)
2027 else:
2028 info_dict[partition_prop_key] = \
2029 common.PartitionBuildProps.FromInputFile(input_file, partition,
2030 placeholder_values)
Tianjie Xu9afb2212020-05-10 21:48:15 +00002031 info_dict["build.prop"] = info_dict["system.build.prop"]
2032
Tianjied6867162020-05-10 14:30:13 -07002033 new_build_info = common.BuildInfo(info_dict, build_info.oem_dicts)
2034 device_names.add(new_build_info.device)
2035 fingerprints.add(new_build_info.fingerprint)
2036 return device_names, fingerprints
Tianjie Xu9afb2212020-05-10 21:48:15 +00002037
2038
Doug Zongkereef39442009-04-02 12:14:19 -07002039def main(argv):
2040
2041 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002042 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002043 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002044 elif o in ("-i", "--incremental_from"):
2045 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002046 elif o == "--full_radio":
2047 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002048 elif o == "--full_bootloader":
2049 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002050 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002051 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002052 elif o == "--downgrade":
2053 OPTIONS.downgrade = True
2054 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002055 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002056 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002057 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002058 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002059 elif o == "--oem_no_mount":
2060 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002061 elif o in ("-e", "--extra_script"):
2062 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002063 elif o in ("-t", "--worker_threads"):
2064 if a.isdigit():
2065 OPTIONS.worker_threads = int(a)
2066 else:
2067 raise ValueError("Cannot parse value %r for option %r - only "
2068 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002069 elif o in ("-2", "--two_step"):
2070 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002071 elif o == "--include_secondary":
2072 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002073 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002074 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002075 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002076 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002077 elif o == "--block":
2078 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002079 elif o in ("-b", "--binary"):
2080 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002081 elif o == "--stash_threshold":
2082 try:
2083 OPTIONS.stash_threshold = float(a)
2084 except ValueError:
2085 raise ValueError("Cannot parse value %r for option %r - expecting "
2086 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002087 elif o == "--log_diff":
2088 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002089 elif o == "--payload_signer":
2090 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002091 elif o == "--payload_signer_args":
2092 OPTIONS.payload_signer_args = shlex.split(a)
Tianjie Xu21e6deb2019-10-07 18:01:00 -07002093 elif o == "--payload_signer_maximum_signature_size":
2094 OPTIONS.payload_signer_maximum_signature_size = a
xunchang376cc7c2019-04-08 23:04:58 -07002095 elif o == "--payload_signer_key_size":
Tianjie Xu21e6deb2019-10-07 18:01:00 -07002096 # TODO(Xunchang) remove this option after cleaning up the callers.
2097 logger.warning("The option '--payload_signer_key_size' is deprecated."
2098 " Use '--payload_signer_maximum_signature_size' instead.")
2099 OPTIONS.payload_signer_maximum_signature_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002100 elif o == "--extracted_input_target_files":
2101 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002102 elif o == "--skip_postinstall":
2103 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002104 elif o == "--retrofit_dynamic_partitions":
2105 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002106 elif o == "--skip_compatibility_check":
2107 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002108 elif o == "--output_metadata_path":
2109 OPTIONS.output_metadata_path = a
Tianjie Xu1b079832019-08-28 12:19:23 -07002110 elif o == "--disable_fec_computation":
2111 OPTIONS.disable_fec_computation = True
Yifan Hong65afc072020-04-17 10:08:10 -07002112 elif o == "--force_non_ab":
2113 OPTIONS.force_non_ab = True
Tianjied6867162020-05-10 14:30:13 -07002114 elif o == "--boot_variable_file":
2115 OPTIONS.boot_variable_file = a
Doug Zongkereef39442009-04-02 12:14:19 -07002116 else:
2117 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002118 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002119
2120 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002121 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002122 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002123 "package_key=",
2124 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002125 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002126 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002127 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002128 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002129 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002130 "extra_script=",
2131 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002132 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002133 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002134 "no_signing",
2135 "block",
2136 "binary=",
2137 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002138 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002139 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002140 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002141 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002142 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002143 "payload_signer_args=",
Tianjie Xu21e6deb2019-10-07 18:01:00 -07002144 "payload_signer_maximum_signature_size=",
xunchang376cc7c2019-04-08 23:04:58 -07002145 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002146 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002147 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002148 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002149 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002150 "output_metadata_path=",
Tianjie Xu1b079832019-08-28 12:19:23 -07002151 "disable_fec_computation",
Yifan Hong65afc072020-04-17 10:08:10 -07002152 "force_non_ab",
Tianjied6867162020-05-10 14:30:13 -07002153 "boot_variable_file=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002154 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002155
2156 if len(args) != 2:
2157 common.Usage(__doc__)
2158 sys.exit(1)
2159
Tao Bao32fcdab2018-10-12 10:30:39 -07002160 common.InitLogging()
2161
Tao Bao5d182562016-02-23 11:38:39 -08002162 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002163 # We should only allow downgrading incrementals (as opposed to full).
2164 # Otherwise the device may go back from arbitrary build with this full
2165 # OTA package.
2166 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002167 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002168
Tao Bao2db13852018-01-08 22:28:57 -08002169 # Load the build info dicts from the zip directly or the extracted input
2170 # directory. We don't need to unzip the entire target-files zips, because they
2171 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2172 # When loading the info dicts, we don't need to provide the second parameter
2173 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2174 # some properties with their actual paths, such as 'selinux_fc',
2175 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002176 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002177 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002178 else:
Tao Bao2db13852018-01-08 22:28:57 -08002179 with zipfile.ZipFile(args[0], 'r') as input_zip:
2180 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002181
Tao Bao32fcdab2018-10-12 10:30:39 -07002182 logger.info("--- target info ---")
2183 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002184
2185 # Load the source build dict if applicable.
2186 if OPTIONS.incremental_source is not None:
2187 OPTIONS.target_info_dict = OPTIONS.info_dict
2188 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2189 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2190
Tao Bao32fcdab2018-10-12 10:30:39 -07002191 logger.info("--- source info ---")
2192 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002193
2194 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002195 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2196
Yifan Hong50e79542018-11-08 17:44:12 -08002197 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002198 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002199 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002200 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2201 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002202 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2203 raise common.ExternalError(
2204 "Expect to generate incremental OTA for retrofitting dynamic "
2205 "partitions, but dynamic_partition_retrofit is not set in target "
2206 "build.")
2207 logger.info("Implicitly generating retrofit incremental OTA.")
2208 OPTIONS.retrofit_dynamic_partitions = True
2209
2210 # Skip postinstall for retrofitting dynamic partitions.
2211 if OPTIONS.retrofit_dynamic_partitions:
2212 OPTIONS.skip_postinstall = True
2213
Tao Baoc098e9e2016-01-07 13:03:56 -08002214 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
Yifan Hong65afc072020-04-17 10:08:10 -07002215 allow_non_ab = OPTIONS.info_dict.get("allow_non_ab") == "true"
2216 if OPTIONS.force_non_ab:
2217 assert allow_non_ab, "--force_non_ab only allowed on devices that supports non-A/B"
2218 assert ab_update, "--force_non_ab only allowed on A/B devices"
2219
2220 generate_ab = not OPTIONS.force_non_ab and ab_update
Tao Baoc098e9e2016-01-07 13:03:56 -08002221
Christian Oderf63e2cd2017-05-01 22:30:15 +02002222 # Use the default key to sign the package if not specified with package_key.
2223 # package_keys are needed on ab_updates, so always define them if an
Yifan Hong65afc072020-04-17 10:08:10 -07002224 # A/B update is getting created.
2225 if not OPTIONS.no_signing or generate_ab:
Christian Oderf63e2cd2017-05-01 22:30:15 +02002226 if OPTIONS.package_key is None:
2227 OPTIONS.package_key = OPTIONS.info_dict.get(
2228 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002229 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002230 # Get signing keys
2231 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2232
Yifan Hong65afc072020-04-17 10:08:10 -07002233 if generate_ab:
Tao Baof0c4aa22018-04-30 20:29:30 -07002234 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002235 target_file=args[0],
2236 output_file=args[1],
2237 source_file=OPTIONS.incremental_source)
2238
Dan Willemsencea5cd22017-03-21 14:44:27 -07002239 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002240 GenerateNonAbOtaPackage(
2241 target_file=args[0],
2242 output_file=args[1],
2243 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002244
Tao Baof0c4aa22018-04-30 20:29:30 -07002245 # Post OTA generation works.
2246 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2247 logger.info("Generating diff logs...")
2248 logger.info("Unzipping target-files for diffing...")
2249 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2250 source_dir = common.UnzipTemp(
2251 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002252
Tao Baof0c4aa22018-04-30 20:29:30 -07002253 with open(OPTIONS.log_diff, 'w') as out_file:
Tao Baof0c4aa22018-04-30 20:29:30 -07002254 target_files_diff.recursiveDiff(
2255 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002256
Tao Bao32fcdab2018-10-12 10:30:39 -07002257 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002258
2259
2260if __name__ == '__main__':
2261 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002262 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002263 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002264 except common.ExternalError:
2265 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002266 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002267 finally:
2268 common.Cleanup()