blob: 21a27231ee71d886b95bf9b548264a7b3aeecea4 [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/*',
280 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800281RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tao Bao3e759462019-09-17 22:43:11 -0700282
283# Images to be excluded from secondary payload. We essentially only keep
284# 'system_other' and bootloader partitions.
285SECONDARY_PAYLOAD_SKIPPED_IMAGES = [
286 'boot', 'dtbo', 'modem', 'odm', 'product', 'radio', 'recovery',
Tianjiec3850642020-05-13 14:47:31 -0700287 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor',
288 'vendor_boot']
Tao Bao6b0b2f92017-03-05 11:38:11 -0800289
Tao Bao2dd1c482017-02-03 16:49:39 -0800290
Tao Baofabe0832018-01-17 15:52:28 -0800291class PayloadSigner(object):
292 """A class that wraps the payload signing works.
293
294 When generating a Payload, hashes of the payload and metadata files will be
295 signed with the device key, either by calling an external payload signer or
296 by calling openssl with the package key. This class provides a unified
297 interface, so that callers can just call PayloadSigner.Sign().
298
299 If an external payload signer has been specified (OPTIONS.payload_signer), it
300 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
301 that the signing key should be provided as part of the payload_signer_args.
302 Otherwise without an external signer, it uses the package key
303 (OPTIONS.package_key) and calls openssl for the signing works.
304 """
305
306 def __init__(self):
307 if OPTIONS.payload_signer is None:
308 # Prepare the payload signing key.
309 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
310 pw = OPTIONS.key_passwords[OPTIONS.package_key]
311
312 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
313 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
314 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
315 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700316 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800317
318 self.signer = "openssl"
319 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
320 "-pkeyopt", "digest:sha256"]
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700321 self.maximum_signature_size = self._GetMaximumSignatureSizeInBytes(
322 signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800323 else:
324 self.signer = OPTIONS.payload_signer
325 self.signer_args = OPTIONS.payload_signer_args
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700326 if OPTIONS.payload_signer_maximum_signature_size:
327 self.maximum_signature_size = int(
328 OPTIONS.payload_signer_maximum_signature_size)
xunchang376cc7c2019-04-08 23:04:58 -0700329 else:
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700330 # The legacy config uses RSA2048 keys.
331 logger.warning("The maximum signature size for payload signer is not"
332 " set, default to 256 bytes.")
333 self.maximum_signature_size = 256
xunchang376cc7c2019-04-08 23:04:58 -0700334
335 @staticmethod
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700336 def _GetMaximumSignatureSizeInBytes(signing_key):
337 out_signature_size_file = common.MakeTempFile("signature_size")
338 cmd = ["delta_generator", "--out_maximum_signature_size_file={}".format(
339 out_signature_size_file), "--private_key={}".format(signing_key)]
340 common.RunAndCheckOutput(cmd)
341 with open(out_signature_size_file) as f:
342 signature_size = f.read().rstrip()
Luca Stefani88e1a142020-03-27 14:05:12 +0100343 logger.info("%s outputs the maximum signature size: %s", cmd[0],
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700344 signature_size)
345 return int(signature_size)
Tao Baofabe0832018-01-17 15:52:28 -0800346
347 def Sign(self, in_file):
348 """Signs the given input file. Returns the output filename."""
349 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
350 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700351 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800352 return out_file
353
354
Tao Bao40b18822018-01-30 18:19:04 -0800355class Payload(object):
356 """Manages the creation and the signing of an A/B OTA Payload."""
357
358 PAYLOAD_BIN = 'payload.bin'
359 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800360 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
361 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800362
Tao Bao667ff572018-02-10 00:02:40 -0800363 def __init__(self, secondary=False):
364 """Initializes a Payload instance.
365
366 Args:
367 secondary: Whether it's generating a secondary payload (default: False).
368 """
Tao Bao40b18822018-01-30 18:19:04 -0800369 self.payload_file = None
370 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800371 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800372
Tao Baof0c4aa22018-04-30 20:29:30 -0700373 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700374 # Don't pipe (buffer) the output if verbose is set. Let
375 # brillo_update_payload write to stdout/stderr directly, so its progress can
376 # be monitored.
377 if OPTIONS.verbose:
378 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
379 else:
380 common.RunAndCheckOutput(cmd)
381
Tao Bao40b18822018-01-30 18:19:04 -0800382 def Generate(self, target_file, source_file=None, additional_args=None):
383 """Generates a payload from the given target-files zip(s).
384
385 Args:
386 target_file: The filename of the target build target-files zip.
387 source_file: The filename of the source build target-files zip; or None if
388 generating a full OTA.
389 additional_args: A list of additional args that should be passed to
390 brillo_update_payload script; or None.
391 """
392 if additional_args is None:
393 additional_args = []
394
395 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
396 cmd = ["brillo_update_payload", "generate",
397 "--payload", payload_file,
398 "--target_image", target_file]
399 if source_file is not None:
400 cmd.extend(["--source_image", source_file])
Tianjie Xu1b079832019-08-28 12:19:23 -0700401 if OPTIONS.disable_fec_computation:
402 cmd.extend(["--disable_fec_computation", "true"])
Tao Bao40b18822018-01-30 18:19:04 -0800403 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700404 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800405
406 self.payload_file = payload_file
407 self.payload_properties = None
408
409 def Sign(self, payload_signer):
410 """Generates and signs the hashes of the payload and metadata.
411
412 Args:
413 payload_signer: A PayloadSigner() instance that serves the signing work.
414
415 Raises:
416 AssertionError: On any failure when calling brillo_update_payload script.
417 """
418 assert isinstance(payload_signer, PayloadSigner)
419
420 # 1. Generate hashes of the payload and metadata files.
421 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
422 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
423 cmd = ["brillo_update_payload", "hash",
424 "--unsigned_payload", self.payload_file,
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700425 "--signature_size", str(payload_signer.maximum_signature_size),
Tao Bao40b18822018-01-30 18:19:04 -0800426 "--metadata_hash_file", metadata_sig_file,
427 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700428 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800429
430 # 2. Sign the hashes.
431 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
432 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
433
434 # 3. Insert the signatures back into the payload file.
435 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
436 suffix=".bin")
437 cmd = ["brillo_update_payload", "sign",
438 "--unsigned_payload", self.payload_file,
439 "--payload", signed_payload_file,
Tianjie Xu21e6deb2019-10-07 18:01:00 -0700440 "--signature_size", str(payload_signer.maximum_signature_size),
Tao Bao40b18822018-01-30 18:19:04 -0800441 "--metadata_signature_file", signed_metadata_sig_file,
442 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700443 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800444
445 # 4. Dump the signed payload properties.
446 properties_file = common.MakeTempFile(prefix="payload-properties-",
447 suffix=".txt")
448 cmd = ["brillo_update_payload", "properties",
449 "--payload", signed_payload_file,
450 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700451 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800452
Tao Bao667ff572018-02-10 00:02:40 -0800453 if self.secondary:
454 with open(properties_file, "a") as f:
455 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
456
Tao Bao40b18822018-01-30 18:19:04 -0800457 if OPTIONS.wipe_user_data:
458 with open(properties_file, "a") as f:
459 f.write("POWERWASH=1\n")
460
461 self.payload_file = signed_payload_file
462 self.payload_properties = properties_file
463
Tao Bao667ff572018-02-10 00:02:40 -0800464 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800465 """Writes the payload to the given zip.
466
467 Args:
468 output_zip: The output ZipFile instance.
469 """
470 assert self.payload_file is not None
471 assert self.payload_properties is not None
472
Tao Bao667ff572018-02-10 00:02:40 -0800473 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800474 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
475 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
476 else:
477 payload_arcname = Payload.PAYLOAD_BIN
478 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
479
Tao Bao40b18822018-01-30 18:19:04 -0800480 # Add the signed payload file and properties into the zip. In order to
481 # support streaming, we pack them as ZIP_STORED. So these entries can be
482 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800483 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800484 compress_type=zipfile.ZIP_STORED)
485 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800486 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800487 compress_type=zipfile.ZIP_STORED)
488
489
Doug Zongkereef39442009-04-02 12:14:19 -0700490def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200491 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700492
Doug Zongker951495f2009-08-14 12:44:19 -0700493 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
494 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700495
496
Tao Bao481bab82017-12-21 11:23:09 -0800497def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800498 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800499 if not oem_source:
500 return None
501
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800502 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800503 for oem_file in oem_source:
504 with open(oem_file) as fp:
505 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800506 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700507
Doug Zongkereef39442009-04-02 12:14:19 -0700508
Tao Baod42e97e2016-11-30 12:11:57 -0800509def _WriteRecoveryImageToBoot(script, output_zip):
510 """Find and write recovery image to /boot in two-step OTA.
511
512 In two-step OTAs, we write recovery image to /boot as the first step so that
513 we can reboot to there and install a new recovery image to /recovery.
514 A special "recovery-two-step.img" will be preferred, which encodes the correct
515 path of "/boot". Otherwise the device may show "device is corrupt" message
516 when booting into /boot.
517
518 Fall back to using the regular recovery.img if the two-step recovery image
519 doesn't exist. Note that rebuilding the special image at this point may be
520 infeasible, because we don't have the desired boot signer and keys when
521 calling ota_from_target_files.py.
522 """
523
524 recovery_two_step_img_name = "recovery-two-step.img"
525 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700526 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800527 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700528 common.ZipWrite(
529 output_zip,
530 recovery_two_step_img_path,
531 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700532 logger.info(
533 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800534 script.WriteRawImage("/boot", recovery_two_step_img_name)
535 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700536 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800537 # The "recovery.img" entry has been written into package earlier.
538 script.WriteRawImage("/boot", "recovery.img")
539
540
Bill Peckhame868aec2019-09-17 17:06:47 -0700541def HasRecoveryPatch(target_files_zip, info_dict):
542 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
543
544 if board_uses_vendorimage:
545 target_files_dir = "VENDOR"
546 else:
547 target_files_dir = "SYSTEM/vendor"
548
549 patch = "%s/recovery-from-boot.p" % target_files_dir
Kelvin Zhang0876c412020-06-23 15:06:58 -0400550 img = "%s/etc/recovery.img" % target_files_dir
Bill Peckhame868aec2019-09-17 17:06:47 -0700551
Kelvin Zhang0876c412020-06-23 15:06:58 -0400552 namelist = target_files_zip.namelist()
553 return patch in namelist or img in namelist
Doug Zongker73ef8252009-07-23 15:12:53 -0700554
Tao Bao457cbf62017-03-06 09:56:01 -0800555
Yifan Hong51d37562019-04-23 17:06:46 -0700556def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700557 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700558 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700559 return True
560 except KeyError:
561 return False
562
Tao Bao457cbf62017-03-06 09:56:01 -0800563
Yifan Hong9276cf02019-08-21 16:37:04 -0700564def HasTrebleEnabled(target_files, target_info):
565 def HasVendorPartition(target_files):
566 if os.path.isdir(target_files):
567 return os.path.isdir(os.path.join(target_files, "VENDOR"))
568 if zipfile.is_zipfile(target_files):
569 return HasPartition(zipfile.ZipFile(target_files), "vendor")
570 raise ValueError("Unknown target_files argument")
Yifan Hong51d37562019-04-23 17:06:46 -0700571
Yifan Hong9276cf02019-08-21 16:37:04 -0700572 return (HasVendorPartition(target_files) and
Tao Bao481bab82017-12-21 11:23:09 -0800573 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700574
575
Tao Bao481bab82017-12-21 11:23:09 -0800576def WriteFingerprintAssertion(script, target_info, source_info):
577 source_oem_props = source_info.oem_props
578 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700579
Tao Bao481bab82017-12-21 11:23:09 -0800580 if source_oem_props is None and target_oem_props is None:
581 script.AssertSomeFingerprint(
582 source_info.fingerprint, target_info.fingerprint)
583 elif source_oem_props is not None and target_oem_props is not None:
584 script.AssertSomeThumbprint(
585 target_info.GetBuildProp("ro.build.thumbprint"),
586 source_info.GetBuildProp("ro.build.thumbprint"))
587 elif source_oem_props is None and target_oem_props is not None:
588 script.AssertFingerprintOrThumbprint(
589 source_info.fingerprint,
590 target_info.GetBuildProp("ro.build.thumbprint"))
591 else:
592 script.AssertFingerprintOrThumbprint(
593 target_info.fingerprint,
594 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700595
Doug Zongkerfc44a512014-08-26 13:10:25 -0700596
Yifan Hong9276cf02019-08-21 16:37:04 -0700597def CheckVintfIfTrebleEnabled(target_files, target_info):
598 """Checks compatibility info of the input target files.
Tao Bao21803d32017-04-19 10:16:09 -0700599
Yifan Hong9276cf02019-08-21 16:37:04 -0700600 Metadata used for compatibility verification is retrieved from target_zip.
Tao Bao21803d32017-04-19 10:16:09 -0700601
Yifan Hong9276cf02019-08-21 16:37:04 -0700602 Compatibility should only be checked for devices that have enabled
Tao Baobcd1d162017-08-26 13:10:26 -0700603 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700604
605 Args:
Yifan Hong9276cf02019-08-21 16:37:04 -0700606 target_files: Path to zip file containing the source files to be included
607 for OTA. Can also be the path to extracted directory.
Tao Bao481bab82017-12-21 11:23:09 -0800608 target_info: The BuildInfo instance that holds the target build info.
Tao Bao21803d32017-04-19 10:16:09 -0700609 """
610
Tao Baobcd1d162017-08-26 13:10:26 -0700611 # Will only proceed if the target has enabled the Treble support (as well as
612 # having a /vendor partition).
Yifan Hong9276cf02019-08-21 16:37:04 -0700613 if not HasTrebleEnabled(target_files, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700614 return
615
xunchangabfa2652019-02-19 16:27:10 -0800616 # Skip adding the compatibility package as a workaround for b/114240221. The
617 # compatibility will always fail on devices without qualified kernels.
618 if OPTIONS.skip_compatibility_check:
619 return
620
Yifan Hong9276cf02019-08-21 16:37:04 -0700621 if not check_target_files_vintf.CheckVintf(target_files, target_info):
622 raise RuntimeError("VINTF compatibility check failed")
Tao Bao21803d32017-04-19 10:16:09 -0700623
624
Tianjie Xuf67dd802019-05-20 17:50:36 -0700625def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
626 device_specific):
627 """Returns a ordered dict of block differences with partition name as key."""
628
629 def GetIncrementalBlockDifferenceForPartition(name):
630 if not HasPartition(source_zip, name):
Kelvin Zhang0876c412020-06-23 15:06:58 -0400631 raise RuntimeError(
632 "can't generate incremental that adds {}".format(name))
Tianjie Xuf67dd802019-05-20 17:50:36 -0700633
634 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
635 info_dict=source_info,
636 allow_shared_blocks=allow_shared_blocks)
637
638 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
639 name, 4096, target_info)
640 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
641 info_dict=target_info,
642 allow_shared_blocks=allow_shared_blocks,
Kelvin Zhang0876c412020-06-23 15:06:58 -0400643 hashtree_info_generator=hashtree_info_generator)
Tianjie Xuf67dd802019-05-20 17:50:36 -0700644
645 # Check the first block of the source system partition for remount R/W only
646 # if the filesystem is ext4.
647 partition_source_info = source_info["fstab"]["/" + name]
648 check_first_block = partition_source_info.fs_type == "ext4"
649 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
650 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
651 # b) the blocks listed in block map may not contain all the bytes for a
652 # given file (because they're rounded to be 4K-aligned).
653 partition_target_info = target_info["fstab"]["/" + name]
654 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
655 partition_target_info.fs_type == "squashfs")
Xindong Xu2a7aaa62020-03-13 15:59:22 +0800656 return common.BlockDifference(name, partition_tgt, partition_src,
Tianjie Xuf67dd802019-05-20 17:50:36 -0700657 check_first_block,
658 version=blockimgdiff_version,
659 disable_imgdiff=disable_imgdiff)
660
661 if source_zip:
662 # See notes in common.GetUserImage()
663 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
664 target_info.get('ext4_share_dup_blocks') == "true")
665 blockimgdiff_version = max(
666 int(i) for i in target_info.get(
667 "blockimgdiff_versions", "1").split(","))
668 assert blockimgdiff_version >= 3
669
670 block_diff_dict = collections.OrderedDict()
671 partition_names = ["system", "vendor", "product", "odm", "system_ext"]
672 for partition in partition_names:
673 if not HasPartition(target_zip, partition):
674 continue
675 # Full OTA update.
676 if not source_zip:
677 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
678 info_dict=target_info,
679 reset_file_map=True)
680 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
681 src=None)
682 # Incremental OTA update.
683 else:
684 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
685 partition)
686 assert "system" in block_diff_dict
687
688 # Get the block diffs from the device specific script. If there is a
689 # duplicate block diff for a partition, ignore the diff in the generic script
690 # and use the one in the device specific script instead.
691 if source_zip:
692 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
693 function_name = "IncrementalOTA_GetBlockDifferences"
694 else:
695 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
696 function_name = "FullOTA_GetBlockDifferences"
697
698 if device_specific_diffs:
699 assert all(isinstance(diff, common.BlockDifference)
700 for diff in device_specific_diffs), \
701 "{} is not returning a list of BlockDifference objects".format(
702 function_name)
703 for diff in device_specific_diffs:
704 if diff.partition in block_diff_dict:
705 logger.warning("Duplicate block difference found. Device specific block"
706 " diff for partition '%s' overrides the one in generic"
707 " script.", diff.partition)
708 block_diff_dict[diff.partition] = diff
709
710 return block_diff_dict
711
712
Tao Bao491d7e22018-02-21 13:17:22 -0800713def WriteFullOTAPackage(input_zip, output_file):
Tao Bao1c320f82019-10-04 23:25:12 -0700714 target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700715
Tao Bao481bab82017-12-21 11:23:09 -0800716 # We don't know what version it will be installed on top of. We expect the API
717 # just won't change very often. Similarly for fstab, it might have changed in
718 # the target build.
719 target_api_version = target_info["recovery_api_version"]
720 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700721
Tao Bao481bab82017-12-21 11:23:09 -0800722 if target_info.oem_props and not OPTIONS.oem_no_mount:
723 target_info.WriteMountOemScript(script)
724
Tao Baodf3a48b2018-01-10 16:30:43 -0800725 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700726
Tao Bao491d7e22018-02-21 13:17:22 -0800727 if not OPTIONS.no_signing:
728 staging_file = common.MakeTempFile(suffix='.zip')
729 else:
730 staging_file = output_file
731
732 output_zip = zipfile.ZipFile(
733 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
734
Doug Zongker05d3dea2009-06-22 11:32:31 -0700735 device_specific = common.DeviceSpecificParams(
736 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800737 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700738 output_zip=output_zip,
739 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700740 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700741 metadata=metadata,
742 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700743
Bill Peckhame868aec2019-09-17 17:06:47 -0700744 assert HasRecoveryPatch(input_zip, info_dict=OPTIONS.info_dict)
Doug Zongkerc9253822014-02-04 12:17:58 -0800745
Tao Bao481bab82017-12-21 11:23:09 -0800746 # Assertions (e.g. downgrade check, device properties check).
747 ts = target_info.GetBuildProp("ro.build.date.utc")
748 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700749 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700750
Tao Bao481bab82017-12-21 11:23:09 -0800751 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700752 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800753
Tianjie Xuf67dd802019-05-20 17:50:36 -0700754 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
755 target_info=target_info,
756 source_info=None,
757 device_specific=device_specific)
758
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800759 # Two-step package strategy (in chronological order, which is *not*
760 # the order in which the generated script has things):
761 #
762 # if stage is not "2/3" or "3/3":
763 # write recovery image to boot partition
764 # set stage to "2/3"
765 # reboot to boot partition and restart recovery
766 # else if stage is "2/3":
767 # write recovery image to recovery partition
768 # set stage to "3/3"
769 # reboot to recovery partition and restart recovery
770 # else:
771 # (stage must be "3/3")
772 # set stage to ""
773 # do normal full package installation:
774 # wipe and install system, boot image, etc.
775 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700776 # complete script normally
777 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800778
779 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
780 OPTIONS.input_tmp, "RECOVERY")
781 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800782 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800783 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800784 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800785 assert fs.fs_type.upper() == "EMMC", \
786 "two-step packages only supported on devices with EMMC /misc partitions"
787 bcb_dev = {"bcb_dev": fs.device}
788 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
789 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700790if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800791""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800792
793 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
794 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800795 script.WriteRawImage("/recovery", "recovery.img")
796 script.AppendExtra("""
797set_stage("%(bcb_dev)s", "3/3");
798reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700799else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800800""" % bcb_dev)
801
Tao Baod42e97e2016-11-30 12:11:57 -0800802 # Stage 3/3: Make changes.
803 script.Comment("Stage 3/3")
804
Tao Bao6c55a8a2015-04-08 15:30:27 -0700805 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800806 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -0700807
Doug Zongkere5ff5902012-01-17 10:55:37 -0800808 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700809
Tianjie Xuf67dd802019-05-20 17:50:36 -0700810 # All other partitions as well as the data wipe use 10% of the progress, and
811 # the update of the system partition takes the remaining progress.
812 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700813 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -0800814 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -0700815 progress_dict = {partition: 0.1 for partition in block_diff_dict}
816 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700817
Yifan Hong10c530d2018-12-27 17:34:18 -0800818 if target_info.get('use_dynamic_partitions') == "true":
819 # Use empty source_info_dict to indicate that all partitions / groups must
820 # be re-added.
821 dynamic_partitions_diff = common.DynamicPartitionsDifference(
822 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -0700823 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -0800824 progress_dict=progress_dict)
825 dynamic_partitions_diff.WriteScript(script, output_zip,
826 write_verify_script=OPTIONS.verify)
827 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -0700828 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -0800829 block_diff.WriteScript(script, output_zip,
830 progress=progress_dict.get(block_diff.partition),
831 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -0700832
Yifan Hong9276cf02019-08-21 16:37:04 -0700833 CheckVintfIfTrebleEnabled(OPTIONS.input_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -0700834
Yifan Hong10c530d2018-12-27 17:34:18 -0800835 boot_img = common.GetBootableImage(
836 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -0800837 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -0700838 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700839
Doug Zongker9ce0fb62010-09-20 18:04:41 -0700840 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -0700841
Tianjie Xuf67dd802019-05-20 17:50:36 -0700842 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700843 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -0700844
Doug Zongker1c390a22009-05-14 19:06:36 -0700845 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700846 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -0700847
Doug Zongker14833602010-02-02 13:12:04 -0800848 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800849
Doug Zongker922206e2014-03-04 13:16:24 -0800850 if OPTIONS.wipe_user_data:
851 script.ShowProgress(0.1, 10)
852 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700853
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800854 if OPTIONS.two_step:
855 script.AppendExtra("""
856set_stage("%(bcb_dev)s", "");
857""" % bcb_dev)
858 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -0800859
860 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
861 script.Comment("Stage 1/3")
862 _WriteRecoveryImageToBoot(script, output_zip)
863
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800864 script.AppendExtra("""
865set_stage("%(bcb_dev)s", "2/3");
866reboot_now("%(bcb_dev)s", "");
867endif;
868endif;
869""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -0800870
Tao Bao5d182562016-02-23 11:38:39 -0800871 script.SetProgress(1)
872 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -0800873 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -0800874
875 # We haven't written the metadata entry, which will be done in
876 # FinalizeMetadata.
877 common.ZipClose(output_zip)
878
879 needed_property_files = (
880 NonAbOtaPropertyFiles(),
881 )
882 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -0700883
Doug Zongkerfc44a512014-08-26 13:10:25 -0700884
xunchang1cfe2512019-02-19 14:14:48 -0800885def WriteMetadata(metadata, output):
886 """Writes the metadata to the zip archive or a file.
887
888 Args:
889 metadata: The metadata dict for the package.
890 output: A ZipFile object or a string of the output file path.
891 """
892
Tao Bao59cf0c52019-06-25 10:04:24 -0700893 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -0800894 if isinstance(output, zipfile.ZipFile):
895 common.ZipWriteStr(output, METADATA_NAME, value,
896 compress_type=zipfile.ZIP_STORED)
897 return
898
899 with open(output, 'w') as f:
900 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -0700901
Doug Zongkerfc44a512014-08-26 13:10:25 -0700902
Tao Bao481bab82017-12-21 11:23:09 -0800903def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -0800904 # Only incremental OTAs are allowed to reach here.
905 assert OPTIONS.incremental_source is not None
906
Tao Bao481bab82017-12-21 11:23:09 -0800907 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
908 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -0700909 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -0800910
911 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -0800912 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -0700913 raise RuntimeError(
914 "--downgrade or --override_timestamp specified but no downgrade "
915 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -0800916 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -0800917 else:
918 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -0700919 raise RuntimeError(
920 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
921 "Need to specify --override_timestamp OR --downgrade to allow "
922 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -0800923
924
Tao Baodf3a48b2018-01-10 16:30:43 -0800925def GetPackageMetadata(target_info, source_info=None):
926 """Generates and returns the metadata dict.
927
928 It generates a dict() that contains the info to be written into an OTA
929 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -0700930 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -0800931
932 Args:
933 target_info: The BuildInfo instance that holds the target build info.
934 source_info: The BuildInfo instance that holds the source build info, or
935 None if generating full OTA.
936
937 Returns:
938 A dict to be written into package metadata entry.
939 """
Tao Bao1c320f82019-10-04 23:25:12 -0700940 assert isinstance(target_info, common.BuildInfo)
941 assert source_info is None or isinstance(source_info, common.BuildInfo)
Tao Baodf3a48b2018-01-10 16:30:43 -0800942
Tianjied6867162020-05-10 14:30:13 -0700943 separator = '|'
944
945 boot_variable_values = {}
946 if OPTIONS.boot_variable_file:
947 d = common.LoadDictionaryFromFile(OPTIONS.boot_variable_file)
948 for key, values in d.items():
949 boot_variable_values[key] = [val.strip() for val in values.split(',')]
950
951 post_build_devices, post_build_fingerprints = \
952 CalculateRuntimeDevicesAndFingerprints(target_info, boot_variable_values)
Tao Baodf3a48b2018-01-10 16:30:43 -0800953 metadata = {
Tianjied6867162020-05-10 14:30:13 -0700954 'post-build': separator.join(sorted(post_build_fingerprints)),
955 'post-build-incremental': target_info.GetBuildProp(
Tao Baodf3a48b2018-01-10 16:30:43 -0800956 'ro.build.version.incremental'),
Tianjied6867162020-05-10 14:30:13 -0700957 'post-sdk-level': target_info.GetBuildProp(
Tao Bao35dc2552018-02-01 13:18:00 -0800958 'ro.build.version.sdk'),
Tianjied6867162020-05-10 14:30:13 -0700959 'post-security-patch-level': target_info.GetBuildProp(
Tao Bao35dc2552018-02-01 13:18:00 -0800960 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -0800961 }
962
Yifan Hong65afc072020-04-17 10:08:10 -0700963 if target_info.is_ab and not OPTIONS.force_non_ab:
Tao Baodf3a48b2018-01-10 16:30:43 -0800964 metadata['ota-type'] = 'AB'
965 metadata['ota-required-cache'] = '0'
966 else:
967 metadata['ota-type'] = 'BLOCK'
968
969 if OPTIONS.wipe_user_data:
970 metadata['ota-wipe'] = 'yes'
971
Tao Bao393eeb42019-03-06 16:00:38 -0800972 if OPTIONS.retrofit_dynamic_partitions:
973 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
974
Tao Baodf3a48b2018-01-10 16:30:43 -0800975 is_incremental = source_info is not None
976 if is_incremental:
Tianjied6867162020-05-10 14:30:13 -0700977 pre_build_devices, pre_build_fingerprints = \
978 CalculateRuntimeDevicesAndFingerprints(source_info,
979 boot_variable_values)
980 metadata['pre-build'] = separator.join(sorted(pre_build_fingerprints))
Tao Baodf3a48b2018-01-10 16:30:43 -0800981 metadata['pre-build-incremental'] = source_info.GetBuildProp(
982 'ro.build.version.incremental')
Tianjied6867162020-05-10 14:30:13 -0700983 metadata['pre-device'] = separator.join(sorted(pre_build_devices))
Tao Baodf3a48b2018-01-10 16:30:43 -0800984 else:
Tianjied6867162020-05-10 14:30:13 -0700985 metadata['pre-device'] = separator.join(sorted(post_build_devices))
Tao Baodf3a48b2018-01-10 16:30:43 -0800986
Tao Baofaa8e0b2018-04-12 14:31:43 -0700987 # Use the actual post-timestamp, even for a downgrade case.
988 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
989
990 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -0800991 if is_incremental:
992 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -0800993
994 return metadata
995
996
Tao Baod3fc38a2018-03-08 16:09:01 -0800997class PropertyFiles(object):
998 """A class that computes the property-files string for an OTA package.
999
1000 A property-files string is a comma-separated string that contains the
1001 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1002 can be fetched directly with the package URL along with the offset/size info.
1003 These strings can be used for streaming A/B OTAs, or allowing an updater to
1004 download package metadata entry directly, without paying the cost of
1005 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001006
Tao Baocc8e2662018-03-01 19:30:00 -08001007 Computing the final property-files string requires two passes. Because doing
1008 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1009 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1010 values.
1011
1012 This class provides functions to be called for each pass. The general flow is
1013 as follows.
1014
Tao Baod3fc38a2018-03-08 16:09:01 -08001015 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001016 # The first pass, which writes placeholders before doing initial signing.
1017 property_files.Compute()
1018 SignOutput()
1019
1020 # The second pass, by replacing the placeholders with actual data.
1021 property_files.Finalize()
1022 SignOutput()
1023
1024 And the caller can additionally verify the final result.
1025
1026 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001027 """
1028
Tao Baocc8e2662018-03-01 19:30:00 -08001029 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001030 self.name = None
1031 self.required = ()
1032 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001033
Tao Baocc8e2662018-03-01 19:30:00 -08001034 def Compute(self, input_zip):
1035 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001036
Tao Baocc8e2662018-03-01 19:30:00 -08001037 We reserve extra space for the offset and size of the metadata entry itself,
1038 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001039
Tao Baocc8e2662018-03-01 19:30:00 -08001040 Args:
1041 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001042
Tao Baocc8e2662018-03-01 19:30:00 -08001043 Returns:
1044 A string with placeholders for the metadata offset/size info, e.g.
1045 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1046 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001047 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001048
Tao Baod2ce2ed2018-03-16 12:59:42 -07001049 class InsufficientSpaceException(Exception):
1050 pass
1051
Tao Baocc8e2662018-03-01 19:30:00 -08001052 def Finalize(self, input_zip, reserved_length):
1053 """Finalizes a property-files string with actual METADATA offset/size info.
1054
1055 The input ZIP file has been signed, with the ZIP entries in the desired
1056 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1057 the ZIP entry offsets and construct the property-files string with actual
1058 data. Note that during this process, we must pad the property-files string
1059 to the reserved length, so that the METADATA entry size remains the same.
1060 Otherwise the entries' offsets and sizes may change again.
1061
1062 Args:
1063 input_zip: The input ZIP file.
1064 reserved_length: The reserved length of the property-files string during
1065 the call to Compute(). The final string must be no more than this
1066 size.
1067
1068 Returns:
1069 A property-files string including the metadata offset/size info, e.g.
1070 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1071
1072 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001073 InsufficientSpaceException: If the reserved length is insufficient to hold
1074 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001075 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001076 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001077 if len(result) > reserved_length:
1078 raise self.InsufficientSpaceException(
1079 'Insufficient reserved space: reserved={}, actual={}'.format(
1080 reserved_length, len(result)))
1081
Tao Baocc8e2662018-03-01 19:30:00 -08001082 result += ' ' * (reserved_length - len(result))
1083 return result
1084
1085 def Verify(self, input_zip, expected):
1086 """Verifies the input ZIP file contains the expected property-files string.
1087
1088 Args:
1089 input_zip: The input ZIP file.
1090 expected: The property-files string that's computed from Finalize().
1091
1092 Raises:
1093 AssertionError: On finding a mismatch.
1094 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001095 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001096 assert actual == expected, \
1097 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1098
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001099 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1100 """
1101 Constructs the property-files string per request.
1102
1103 Args:
1104 zip_file: The input ZIP file.
1105 reserved_length: The reserved length of the property-files string.
1106
1107 Returns:
1108 A property-files string including the metadata offset/size info, e.g.
1109 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1110 """
Tao Baocc8e2662018-03-01 19:30:00 -08001111
1112 def ComputeEntryOffsetSize(name):
1113 """Computes the zip entry offset and size."""
1114 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001115 offset = info.header_offset
1116 offset += zipfile.sizeFileHeader
1117 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001118 size = info.file_size
1119 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1120
1121 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001122 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001123 for entry in self.required:
1124 tokens.append(ComputeEntryOffsetSize(entry))
1125 for entry in self.optional:
1126 if entry in zip_file.namelist():
1127 tokens.append(ComputeEntryOffsetSize(entry))
1128
1129 # 'META-INF/com/android/metadata' is required. We don't know its actual
1130 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001131 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1132 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1133 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1134 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001135 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001136 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001137 else:
1138 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1139
1140 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001141
Tao Bao85f16982018-03-08 16:28:33 -08001142 def _GetPrecomputed(self, input_zip):
1143 """Computes the additional tokens to be included into the property-files.
1144
1145 This applies to tokens without actual ZIP entries, such as
1146 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1147 that they can download the payload metadata directly with the info.
1148
1149 Args:
1150 input_zip: The input zip file.
1151
1152 Returns:
1153 A list of strings (tokens) to be added to the property-files string.
1154 """
1155 # pylint: disable=no-self-use
1156 # pylint: disable=unused-argument
1157 return []
1158
Tao Baofe5b69a2018-03-02 09:47:43 -08001159
Tao Baod3fc38a2018-03-08 16:09:01 -08001160class StreamingPropertyFiles(PropertyFiles):
1161 """A subclass for computing the property-files for streaming A/B OTAs."""
1162
1163 def __init__(self):
1164 super(StreamingPropertyFiles, self).__init__()
1165 self.name = 'ota-streaming-property-files'
1166 self.required = (
1167 # payload.bin and payload_properties.txt must exist.
1168 'payload.bin',
1169 'payload_properties.txt',
1170 )
1171 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001172 # care_map is available only if dm-verity is enabled.
1173 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001174 'care_map.txt',
1175 # compatibility.zip is available only if target supports Treble.
1176 'compatibility.zip',
1177 )
1178
1179
Tao Bao85f16982018-03-08 16:28:33 -08001180class AbOtaPropertyFiles(StreamingPropertyFiles):
1181 """The property-files for A/B OTA that includes payload_metadata.bin info.
1182
1183 Since P, we expose one more token (aka property-file), in addition to the ones
1184 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1185 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1186 doesn't exist as a separate ZIP entry, but can be used to verify if the
1187 payload can be applied on the given device.
1188
1189 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1190 and the newly added 'ota-property-files' in P. The new token will only be
1191 available in 'ota-property-files'.
1192 """
1193
1194 def __init__(self):
1195 super(AbOtaPropertyFiles, self).__init__()
1196 self.name = 'ota-property-files'
1197
1198 def _GetPrecomputed(self, input_zip):
1199 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1200 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1201
1202 @staticmethod
1203 def _GetPayloadMetadataOffsetAndSize(input_zip):
1204 """Computes the offset and size of the payload metadata for a given package.
1205
1206 (From system/update_engine/update_metadata.proto)
1207 A delta update file contains all the deltas needed to update a system from
1208 one specific version to another specific version. The update format is
1209 represented by this struct pseudocode:
1210
1211 struct delta_update_file {
1212 char magic[4] = "CrAU";
1213 uint64 file_format_version;
1214 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1215
1216 // Only present if format_version > 1:
1217 uint32 metadata_signature_size;
1218
1219 // The Bzip2 compressed DeltaArchiveManifest
1220 char manifest[metadata_signature_size];
1221
1222 // The signature of the metadata (from the beginning of the payload up to
1223 // this location, not including the signature itself). This is a
1224 // serialized Signatures message.
1225 char medatada_signature_message[metadata_signature_size];
1226
1227 // Data blobs for files, no specific format. The specific offset
1228 // and length of each data blob is recorded in the DeltaArchiveManifest.
1229 struct {
1230 char data[];
1231 } blobs[];
1232
1233 // These two are not signed:
1234 uint64 payload_signatures_message_size;
1235 char payload_signatures_message[];
1236 };
1237
1238 'payload-metadata.bin' contains all the bytes from the beginning of the
1239 payload, till the end of 'medatada_signature_message'.
1240 """
1241 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001242 payload_offset = payload_info.header_offset
1243 payload_offset += zipfile.sizeFileHeader
1244 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001245 payload_size = payload_info.file_size
1246
Tao Bao59cf0c52019-06-25 10:04:24 -07001247 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001248 header_bin = payload_fp.read(24)
1249
1250 # network byte order (big-endian)
1251 header = struct.unpack("!IQQL", header_bin)
1252
1253 # 'CrAU'
1254 magic = header[0]
1255 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1256
1257 manifest_size = header[2]
1258 metadata_signature_size = header[3]
1259 metadata_total = 24 + manifest_size + metadata_signature_size
1260 assert metadata_total < payload_size
1261
1262 return (payload_offset, metadata_total)
1263
1264
Tao Bao491d7e22018-02-21 13:17:22 -08001265class NonAbOtaPropertyFiles(PropertyFiles):
1266 """The property-files for non-A/B OTA.
1267
1268 For non-A/B OTA, the property-files string contains the info for METADATA
1269 entry, with which a system updater can be fetched the package metadata prior
1270 to downloading the entire package.
1271 """
1272
1273 def __init__(self):
1274 super(NonAbOtaPropertyFiles, self).__init__()
1275 self.name = 'ota-property-files'
1276
1277
Tao Baod3fc38a2018-03-08 16:09:01 -08001278def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001279 """Finalizes the metadata and signs an A/B OTA package.
1280
1281 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1282 that contains the offsets and sizes for the ZIP entries. An example
1283 property-files string is as follows.
1284
1285 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1286
1287 OTA server can pass down this string, in addition to the package URL, to the
1288 system update client. System update client can then fetch individual ZIP
1289 entries (ZIP_STORED) directly at the given offset of the URL.
1290
1291 Args:
1292 metadata: The metadata dict for the package.
1293 input_file: The input ZIP filename that doesn't contain the package METADATA
1294 entry yet.
1295 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001296 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001297 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001298
Tao Baod2ce2ed2018-03-16 12:59:42 -07001299 def ComputeAllPropertyFiles(input_file, needed_property_files):
1300 # Write the current metadata entry with placeholders.
1301 with zipfile.ZipFile(input_file) as input_zip:
1302 for property_files in needed_property_files:
1303 metadata[property_files.name] = property_files.Compute(input_zip)
1304 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001305
Tao Baod2ce2ed2018-03-16 12:59:42 -07001306 if METADATA_NAME in namelist:
1307 common.ZipDelete(input_file, METADATA_NAME)
1308 output_zip = zipfile.ZipFile(input_file, 'a')
1309 WriteMetadata(metadata, output_zip)
1310 common.ZipClose(output_zip)
1311
1312 if OPTIONS.no_signing:
1313 return input_file
1314
Tao Bao491d7e22018-02-21 13:17:22 -08001315 prelim_signing = common.MakeTempFile(suffix='.zip')
1316 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001317 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001318
Tao Baod2ce2ed2018-03-16 12:59:42 -07001319 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1320 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1321 for property_files in needed_property_files:
1322 metadata[property_files.name] = property_files.Finalize(
1323 prelim_signing_zip, len(metadata[property_files.name]))
1324
1325 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1326 # entries, as well as padding the entry headers. We do a preliminary signing
1327 # (with an incomplete metadata entry) to allow that to happen. Then compute
1328 # the ZIP entry offsets, write back the final metadata and do the final
1329 # signing.
1330 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1331 try:
1332 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1333 except PropertyFiles.InsufficientSpaceException:
1334 # Even with the preliminary signing, the entry orders may change
1335 # dramatically, which leads to insufficiently reserved space during the
1336 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1337 # preliminary signing works, based on the already ordered ZIP entries, to
1338 # address the issue.
1339 prelim_signing = ComputeAllPropertyFiles(
1340 prelim_signing, needed_property_files)
1341 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001342
1343 # Replace the METADATA entry.
1344 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001345 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001346 WriteMetadata(metadata, output_zip)
1347 common.ZipClose(output_zip)
1348
1349 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001350 if OPTIONS.no_signing:
1351 output_file = prelim_signing
1352 else:
1353 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001354
1355 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001356 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001357 for property_files in needed_property_files:
1358 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001359
xunchang1cfe2512019-02-19 14:14:48 -08001360 # If requested, dump the metadata to a separate file.
1361 output_metadata_path = OPTIONS.output_metadata_path
1362 if output_metadata_path:
1363 WriteMetadata(metadata, output_metadata_path)
1364
Tao Baofe5b69a2018-03-02 09:47:43 -08001365
Tao Bao491d7e22018-02-21 13:17:22 -08001366def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao1c320f82019-10-04 23:25:12 -07001367 target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1368 source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001369
Tao Bao481bab82017-12-21 11:23:09 -08001370 target_api_version = target_info["recovery_api_version"]
1371 source_api_version = source_info["recovery_api_version"]
1372 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001373 logger.warning(
1374 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001375
Tao Bao481bab82017-12-21 11:23:09 -08001376 script = edify_generator.EdifyGenerator(
1377 source_api_version, target_info, fstab=source_info["fstab"])
1378
1379 if target_info.oem_props or source_info.oem_props:
1380 if not OPTIONS.oem_no_mount:
1381 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001382
Tao Baodf3a48b2018-01-10 16:30:43 -08001383 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001384
Tao Bao491d7e22018-02-21 13:17:22 -08001385 if not OPTIONS.no_signing:
1386 staging_file = common.MakeTempFile(suffix='.zip')
1387 else:
1388 staging_file = output_file
1389
1390 output_zip = zipfile.ZipFile(
1391 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1392
Geremy Condra36bd3652014-02-06 19:45:10 -08001393 device_specific = common.DeviceSpecificParams(
1394 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001395 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001396 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001397 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001398 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001399 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001400 output_zip=output_zip,
1401 script=script,
1402 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001403 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001404
Geremy Condra36bd3652014-02-06 19:45:10 -08001405 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001406 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001407 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001408 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001409 updating_boot = (not OPTIONS.two_step and
1410 (source_boot.data != target_boot.data))
1411
Geremy Condra36bd3652014-02-06 19:45:10 -08001412 target_recovery = common.GetBootableImage(
1413 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001414
Tianjie Xuf67dd802019-05-20 17:50:36 -07001415 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1416 source_zip=source_zip,
1417 target_info=target_info,
1418 source_info=source_info,
1419 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001420
Yifan Hong9276cf02019-08-21 16:37:04 -07001421 CheckVintfIfTrebleEnabled(OPTIONS.target_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001422
Tao Bao481bab82017-12-21 11:23:09 -08001423 # Assertions (e.g. device properties check).
1424 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001425 device_specific.IncrementalOTA_Assertions()
1426
1427 # Two-step incremental package strategy (in chronological order,
1428 # which is *not* the order in which the generated script has
1429 # things):
1430 #
1431 # if stage is not "2/3" or "3/3":
1432 # do verification on current system
1433 # write recovery image to boot partition
1434 # set stage to "2/3"
1435 # reboot to boot partition and restart recovery
1436 # else if stage is "2/3":
1437 # write recovery image to recovery partition
1438 # set stage to "3/3"
1439 # reboot to recovery partition and restart recovery
1440 # else:
1441 # (stage must be "3/3")
1442 # perform update:
1443 # patch system files, etc.
1444 # force full install of new boot image
1445 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001446 # complete script normally
1447 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001448
1449 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001450 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001451 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001452 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001453 assert fs.fs_type.upper() == "EMMC", \
1454 "two-step packages only supported on devices with EMMC /misc partitions"
Kelvin Zhang0876c412020-06-23 15:06:58 -04001455 bcb_dev = {"bcb_dev": fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001456 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1457 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001458if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001459""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001460
1461 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1462 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001463 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001464 script.WriteRawImage("/recovery", "recovery.img")
1465 script.AppendExtra("""
1466set_stage("%(bcb_dev)s", "3/3");
1467reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001468else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001469""" % bcb_dev)
1470
Tao Baod42e97e2016-11-30 12:11:57 -08001471 # Stage 1/3: (a) Verify the current system.
1472 script.Comment("Stage 1/3")
1473
Tao Bao6c55a8a2015-04-08 15:30:27 -07001474 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001475 script.Print("Source: {}".format(source_info.fingerprint))
1476 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001477
Geremy Condra36bd3652014-02-06 19:45:10 -08001478 script.Print("Verifying current system...")
1479
1480 device_specific.IncrementalOTA_VerifyBegin()
1481
Tao Bao481bab82017-12-21 11:23:09 -08001482 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001483
Tao Baod8d14be2016-02-04 14:26:02 -08001484 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001485 required_cache_sizes = [diff.required_cache for diff in
1486 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001487 if updating_boot:
Yifan Hongbdb32012020-05-07 12:38:53 -07001488 boot_type, boot_device_expr = common.GetTypeAndDeviceExpr("/boot",
1489 source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001490 d = common.Difference(target_boot, source_boot)
1491 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001492 if d is None:
1493 include_full_boot = True
1494 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1495 else:
1496 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001497
Tao Bao32fcdab2018-10-12 10:30:39 -07001498 logger.info(
1499 "boot target: %d source: %d diff: %d", target_boot.size,
1500 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001501
Tao Bao51216552018-08-26 11:53:15 -07001502 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001503
Yifan Hongbdb32012020-05-07 12:38:53 -07001504 target_expr = 'concat("{}:",{},":{}:{}")'.format(
1505 boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
1506 source_expr = 'concat("{}:",{},":{}:{}")'.format(
1507 boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
1508 script.PatchPartitionExprCheck(target_expr, source_expr)
Tao Bao51216552018-08-26 11:53:15 -07001509
Tianjie Xuf67dd802019-05-20 17:50:36 -07001510 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001511
Tianjie Xuf67dd802019-05-20 17:50:36 -07001512 if required_cache_sizes:
1513 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1514
1515 # Verify the existing partitions.
1516 for diff in block_diff_dict.values():
1517 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001518
1519 device_specific.IncrementalOTA_VerifyEnd()
1520
1521 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001522 # Stage 1/3: (b) Write recovery image to /boot.
1523 _WriteRecoveryImageToBoot(script, output_zip)
1524
Geremy Condra36bd3652014-02-06 19:45:10 -08001525 script.AppendExtra("""
1526set_stage("%(bcb_dev)s", "2/3");
1527reboot_now("%(bcb_dev)s", "");
1528else
1529""" % bcb_dev)
1530
Tao Baod42e97e2016-11-30 12:11:57 -08001531 # Stage 3/3: Make changes.
1532 script.Comment("Stage 3/3")
1533
Geremy Condra36bd3652014-02-06 19:45:10 -08001534 script.Comment("---- start making changes here ----")
1535
1536 device_specific.IncrementalOTA_InstallBegin()
1537
Tianjie Xuf67dd802019-05-20 17:50:36 -07001538 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1539 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001540
1541 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1542 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1543 raise RuntimeError(
1544 "can't generate incremental that disables dynamic partitions")
1545 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1546 info_dict=OPTIONS.target_info_dict,
1547 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001548 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001549 progress_dict=progress_dict)
1550 dynamic_partitions_diff.WriteScript(
1551 script, output_zip, write_verify_script=OPTIONS.verify)
1552 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001553 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001554 block_diff.WriteScript(script, output_zip,
1555 progress=progress_dict.get(block_diff.partition),
1556 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001557
1558 if OPTIONS.two_step:
1559 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1560 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001561 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001562
1563 if not OPTIONS.two_step:
1564 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001565 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001566 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001567 script.Print("Installing boot image...")
1568 script.WriteRawImage("/boot", "boot.img")
1569 else:
1570 # Produce the boot image by applying a patch to the current
1571 # contents of the boot partition, and write it back to the
1572 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001573 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001574 script.Print("Patching boot image...")
1575 script.ShowProgress(0.1, 10)
Yifan Hongbdb32012020-05-07 12:38:53 -07001576 target_expr = 'concat("{}:",{},":{}:{}")'.format(
1577 boot_type, boot_device_expr, target_boot.size, target_boot.sha1)
1578 source_expr = 'concat("{}:",{},":{}:{}")'.format(
1579 boot_type, boot_device_expr, source_boot.size, source_boot.sha1)
1580 script.PatchPartitionExpr(target_expr, source_expr, '"boot.img.p"')
Geremy Condra36bd3652014-02-06 19:45:10 -08001581 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001582 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001583
1584 # Do device-specific installation (eg, write radio image).
1585 device_specific.IncrementalOTA_InstallEnd()
1586
1587 if OPTIONS.extra_script is not None:
1588 script.AppendExtra(OPTIONS.extra_script)
1589
Doug Zongker922206e2014-03-04 13:16:24 -08001590 if OPTIONS.wipe_user_data:
1591 script.Print("Erasing user data...")
1592 script.FormatPartition("/data")
1593
Geremy Condra36bd3652014-02-06 19:45:10 -08001594 if OPTIONS.two_step:
1595 script.AppendExtra("""
1596set_stage("%(bcb_dev)s", "");
1597endif;
1598endif;
1599""" % bcb_dev)
1600
1601 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001602 # For downgrade OTAs, we prefer to use the update-binary in the source
1603 # build that is actually newer than the one in the target build.
1604 if OPTIONS.downgrade:
1605 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1606 else:
1607 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001608 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001609
1610 # We haven't written the metadata entry yet, which will be handled in
1611 # FinalizeMetadata().
1612 common.ZipClose(output_zip)
1613
1614 # Sign the generated zip package unless no_signing is specified.
1615 needed_property_files = (
1616 NonAbOtaPropertyFiles(),
1617 )
1618 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001619
Doug Zongker32b527d2014-03-04 10:03:02 -08001620
Tao Bao15a146a2018-02-21 16:06:59 -08001621def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001622 """Returns a target-files.zip file for generating secondary payload.
1623
1624 Although the original target-files.zip already contains secondary slot
1625 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1626 ones without _other suffix. Note that we cannot instead modify the names in
1627 META/ab_partitions.txt, because there are no matching partitions on device.
1628
1629 For the partitions that don't have secondary images, the ones for primary
1630 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1631 bootloader images in the inactive slot.
1632
1633 Args:
1634 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001635 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001636
1637 Returns:
1638 The filename of the target-files.zip for generating secondary payload.
1639 """
Tianjie Xu1c808002019-09-11 00:29:26 -07001640
1641 def GetInfoForSecondaryImages(info_file):
1642 """Updates info file for secondary payload generation.
1643
1644 Scan each line in the info file, and remove the unwanted partitions from
1645 the dynamic partition list in the related properties. e.g.
1646 "super_google_dynamic_partitions_partition_list=system vendor product"
1647 will become "super_google_dynamic_partitions_partition_list=system".
1648
1649 Args:
1650 info_file: The input info file. e.g. misc_info.txt.
1651
1652 Returns:
1653 A string of the updated info content.
1654 """
1655
1656 output_list = []
1657 with open(info_file) as f:
1658 lines = f.read().splitlines()
1659
1660 # The suffix in partition_list variables that follows the name of the
1661 # partition group.
1662 LIST_SUFFIX = 'partition_list'
1663 for line in lines:
1664 if line.startswith('#') or '=' not in line:
1665 output_list.append(line)
1666 continue
1667 key, value = line.strip().split('=', 1)
1668 if key == 'dynamic_partition_list' or key.endswith(LIST_SUFFIX):
1669 partitions = value.split()
1670 partitions = [partition for partition in partitions if partition
Tao Bao3e759462019-09-17 22:43:11 -07001671 not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Tianjie Xu1c808002019-09-11 00:29:26 -07001672 output_list.append('{}={}'.format(key, ' '.join(partitions)))
Kelvin Zhang0876c412020-06-23 15:06:58 -04001673 elif key in ['virtual_ab', "virtual_ab_retrofit"]:
Yifan Hongfe073432019-11-01 12:28:31 -07001674 # Remove virtual_ab flag from secondary payload so that OTA client
1675 # don't use snapshots for secondary update
1676 pass
Tianjie Xu1c808002019-09-11 00:29:26 -07001677 else:
1678 output_list.append(line)
1679 return '\n'.join(output_list)
1680
Tao Baof7140c02018-01-30 17:09:24 -08001681 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1682 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1683
Tao Baodba59ee2018-01-09 13:21:02 -08001684 with zipfile.ZipFile(input_file, 'r') as input_zip:
1685 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001686
Tao Bao0ff15de2019-03-20 11:26:06 -07001687 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001688 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001689 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1690 if info.filename == 'IMAGES/system_other.img':
1691 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1692
1693 # Primary images and friends need to be skipped explicitly.
1694 elif info.filename in ('IMAGES/system.img',
1695 'IMAGES/system.map'):
1696 pass
Tao Bao3e759462019-09-17 22:43:11 -07001697
1698 # Copy images that are not in SECONDARY_PAYLOAD_SKIPPED_IMAGES.
1699 elif info.filename.startswith(('IMAGES/', 'RADIO/')):
1700 image_name = os.path.basename(info.filename)
1701 if image_name not in ['{}.img'.format(partition) for partition in
1702 SECONDARY_PAYLOAD_SKIPPED_IMAGES]:
1703 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001704
Tao Bao15a146a2018-02-21 16:06:59 -08001705 # Skip copying the postinstall config if requested.
1706 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1707 pass
1708
Tianjie Xu1c808002019-09-11 00:29:26 -07001709 elif info.filename.startswith('META/'):
1710 # Remove the unnecessary partitions for secondary images from the
1711 # ab_partitions file.
1712 if info.filename == AB_PARTITIONS:
1713 with open(unzipped_file) as f:
1714 partition_list = f.read().splitlines()
1715 partition_list = [partition for partition in partition_list if partition
Tao Bao3e759462019-09-17 22:43:11 -07001716 and partition not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Kelvin Zhang0876c412020-06-23 15:06:58 -04001717 common.ZipWriteStr(target_zip, info.filename,
1718 '\n'.join(partition_list))
Tianjie Xu1c808002019-09-11 00:29:26 -07001719 # Remove the unnecessary partitions from the dynamic partitions list.
1720 elif (info.filename == 'META/misc_info.txt' or
1721 info.filename == DYNAMIC_PARTITION_INFO):
1722 modified_info = GetInfoForSecondaryImages(unzipped_file)
1723 common.ZipWriteStr(target_zip, info.filename, modified_info)
1724 else:
1725 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001726
Tao Baof7140c02018-01-30 17:09:24 -08001727 common.ZipClose(target_zip)
1728
1729 return target_file
1730
1731
Tao Bao15a146a2018-02-21 16:06:59 -08001732def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1733 """Returns a target-files.zip that's not containing postinstall_config.txt.
1734
1735 This allows brillo_update_payload script to skip writing all the postinstall
1736 hooks in the generated payload. The input target-files.zip file will be
1737 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1738 contain the postinstall_config.txt entry, the input file will be returned.
1739
1740 Args:
1741 input_file: The input target-files.zip filename.
1742
1743 Returns:
1744 The filename of target-files.zip that doesn't contain postinstall config.
1745 """
1746 # We should only make a copy if postinstall_config entry exists.
1747 with zipfile.ZipFile(input_file, 'r') as input_zip:
1748 if POSTINSTALL_CONFIG not in input_zip.namelist():
1749 return input_file
1750
1751 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1752 shutil.copyfile(input_file, target_file)
1753 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1754 return target_file
1755
1756
Yifan Hong50e79542018-11-08 17:44:12 -08001757def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001758 super_block_devices,
1759 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001760 """Returns a target-files.zip for retrofitting dynamic partitions.
1761
1762 This allows brillo_update_payload to generate an OTA based on the exact
1763 bits on the block devices. Postinstall is disabled.
1764
1765 Args:
1766 input_file: The input target-files.zip filename.
1767 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001768 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001769
1770 Returns:
1771 The filename of target-files.zip with *.img replaced with super_*.img for
1772 each block device in super_block_devices.
1773 """
1774 assert super_block_devices, "No super_block_devices are specified."
1775
1776 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001777 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001778
1779 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1780 shutil.copyfile(input_file, target_file)
1781
Tao Baoa3705452019-06-24 15:33:41 -07001782 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001783 namelist = input_zip.namelist()
1784
Yifan Hongb433eba2019-03-06 12:42:53 -08001785 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1786
1787 # Remove partitions from META/ab_partitions.txt that is in
1788 # dynamic_partition_list but not in super_block_devices so that
1789 # brillo_update_payload won't generate update for those logical partitions.
1790 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1791 with open(ab_partitions_file) as f:
1792 ab_partitions_lines = f.readlines()
1793 ab_partitions = [line.strip() for line in ab_partitions_lines]
1794 # Assert that all super_block_devices are in ab_partitions
1795 super_device_not_updated = [partition for partition in super_block_devices
1796 if partition not in ab_partitions]
1797 assert not super_device_not_updated, \
1798 "{} is in super_block_devices but not in {}".format(
1799 super_device_not_updated, AB_PARTITIONS)
1800 # ab_partitions -= (dynamic_partition_list - super_block_devices)
Kelvin Zhang0876c412020-06-23 15:06:58 -04001801 new_ab_partitions = common.MakeTempFile(
1802 prefix="ab_partitions", suffix=".txt")
Yifan Hongb433eba2019-03-06 12:42:53 -08001803 with open(new_ab_partitions, 'w') as f:
1804 for partition in ab_partitions:
1805 if (partition in dynamic_partition_list and
1806 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001807 logger.info("Dropping %s from ab_partitions.txt", partition)
1808 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001809 f.write(partition + "\n")
1810 to_delete = [AB_PARTITIONS]
1811
Yifan Hong50e79542018-11-08 17:44:12 -08001812 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001813 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001814
1815 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1816 # is a regular update on devices without dynamic partitions support.
1817 to_delete += [DYNAMIC_PARTITION_INFO]
1818
Tao Bao03fecb62018-11-28 10:59:23 -08001819 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001820 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001821 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001822
1823 common.ZipDelete(target_file, to_delete)
1824
Yifan Hong50e79542018-11-08 17:44:12 -08001825 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1826
1827 # Write super_{foo}.img as {foo}.img.
1828 for src, dst in replace.items():
1829 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001830 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001831 unzipped_file = os.path.join(input_tmp, *src.split('/'))
1832 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
1833
Yifan Hongb433eba2019-03-06 12:42:53 -08001834 # Write new ab_partitions.txt file
1835 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
1836
Yifan Hong50e79542018-11-08 17:44:12 -08001837 common.ZipClose(target_zip)
1838
1839 return target_file
1840
1841
Tao Baof0c4aa22018-04-30 20:29:30 -07001842def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08001843 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07001844 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08001845 if not OPTIONS.no_signing:
1846 staging_file = common.MakeTempFile(suffix='.zip')
1847 else:
1848 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08001849 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08001850 compression=zipfile.ZIP_DEFLATED)
1851
Tao Bao481bab82017-12-21 11:23:09 -08001852 if source_file is not None:
Tao Bao1c320f82019-10-04 23:25:12 -07001853 target_info = common.BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1854 source_info = common.BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Tao Bao481bab82017-12-21 11:23:09 -08001855 else:
Tao Bao1c320f82019-10-04 23:25:12 -07001856 target_info = common.BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Tao Bao481bab82017-12-21 11:23:09 -08001857 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08001858
Tao Bao481bab82017-12-21 11:23:09 -08001859 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08001860 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08001861
Yifan Hong50e79542018-11-08 17:44:12 -08001862 if OPTIONS.retrofit_dynamic_partitions:
1863 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08001864 target_file, target_info.get("super_block_devices").strip().split(),
1865 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08001866 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08001867 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
1868
Tao Bao40b18822018-01-30 18:19:04 -08001869 # Generate payload.
1870 payload = Payload()
1871
1872 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07001873 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08001874 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07001875 else:
1876 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08001877 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08001878
Tao Bao40b18822018-01-30 18:19:04 -08001879 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08001880
Tao Bao40b18822018-01-30 18:19:04 -08001881 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08001882 payload_signer = PayloadSigner()
1883 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08001884
Tao Bao40b18822018-01-30 18:19:04 -08001885 # Write the payload into output zip.
1886 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08001887
Tao Baof7140c02018-01-30 17:09:24 -08001888 # Generate and include the secondary payload that installs secondary images
1889 # (e.g. system_other.img).
1890 if OPTIONS.include_secondary:
1891 # We always include a full payload for the secondary slot, even when
1892 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08001893 secondary_target_file = GetTargetFilesZipForSecondaryImages(
1894 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08001895 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08001896 secondary_payload.Generate(secondary_target_file,
1897 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08001898 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08001899 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08001900
Tianjie Xucfa86222016-03-07 16:31:19 -08001901 # If dm-verity is supported for the device, copy contents of care_map
1902 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07001903 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08001904 if (target_info.get("verity") == "true" or
1905 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001906 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
1907 "META/" + x in target_zip.namelist()]
1908
1909 # Adds care_map if either the protobuf format or the plain text one exists.
1910 if care_map_list:
1911 care_map_name = care_map_list[0]
1912 care_map_data = target_zip.read("META/" + care_map_name)
1913 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08001914 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001915 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08001916 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08001917 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001918 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07001919
Tao Bao21803d32017-04-19 10:16:09 -07001920 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08001921
Yifan Hong9276cf02019-08-21 16:37:04 -07001922 CheckVintfIfTrebleEnabled(target_file, target_info)
1923
Tao Baofe5b69a2018-03-02 09:47:43 -08001924 # We haven't written the metadata entry yet, which will be handled in
1925 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08001926 common.ZipClose(output_zip)
1927
Tao Bao85f16982018-03-08 16:28:33 -08001928 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
1929 # all the info of the latter. However, system updaters and OTA servers need to
1930 # take time to switch to the new flag. We keep both of the flags for
1931 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08001932 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08001933 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08001934 StreamingPropertyFiles(),
1935 )
1936 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08001937
Tao Baoc098e9e2016-01-07 13:03:56 -08001938
Tao Baof0c4aa22018-04-30 20:29:30 -07001939def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
1940 """Generates a non-A/B OTA package."""
1941 # Sanity check the loaded info dicts first.
1942 if OPTIONS.info_dict.get("no_recovery") == "true":
1943 raise common.ExternalError(
1944 "--- target build has specified no recovery ---")
1945
1946 # Non-A/B OTAs rely on /cache partition to store temporary files.
1947 cache_size = OPTIONS.info_dict.get("cache_size")
1948 if cache_size is None:
1949 logger.warning("--- can't determine the cache partition size ---")
1950 OPTIONS.cache_size = cache_size
1951
1952 if OPTIONS.extra_script is not None:
1953 with open(OPTIONS.extra_script) as fp:
1954 OPTIONS.extra_script = fp.read()
1955
1956 if OPTIONS.extracted_input is not None:
1957 OPTIONS.input_tmp = OPTIONS.extracted_input
1958 else:
1959 logger.info("unzipping target target-files...")
1960 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
1961 OPTIONS.target_tmp = OPTIONS.input_tmp
1962
1963 # If the caller explicitly specified the device-specific extensions path via
1964 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
1965 # is present in the target target_files. Otherwise, take the path of the file
1966 # from 'tool_extensions' in the info dict and look for that in the local
1967 # filesystem, relative to the current directory.
1968 if OPTIONS.device_specific is None:
1969 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
1970 if os.path.exists(from_input):
1971 logger.info("(using device-specific extensions from target_files)")
1972 OPTIONS.device_specific = from_input
1973 else:
1974 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
1975
1976 if OPTIONS.device_specific is not None:
1977 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
1978
1979 # Generate a full OTA.
1980 if source_file is None:
1981 with zipfile.ZipFile(target_file) as input_zip:
1982 WriteFullOTAPackage(
1983 input_zip,
1984 output_file)
1985
1986 # Generate an incremental OTA.
1987 else:
1988 logger.info("unzipping source target-files...")
1989 OPTIONS.source_tmp = common.UnzipTemp(
1990 OPTIONS.incremental_source, UNZIP_PATTERN)
1991 with zipfile.ZipFile(target_file) as input_zip, \
Kelvin Zhang0876c412020-06-23 15:06:58 -04001992 zipfile.ZipFile(source_file) as source_zip:
Tao Baof0c4aa22018-04-30 20:29:30 -07001993 WriteBlockIncrementalOTAPackage(
1994 input_zip,
1995 source_zip,
1996 output_file)
1997
1998
Tianjied6867162020-05-10 14:30:13 -07001999def CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values):
2000 """Returns a tuple of sets for runtime devices and fingerprints"""
Tianjie Xu9afb2212020-05-10 21:48:15 +00002001
Tianjied6867162020-05-10 14:30:13 -07002002 device_names = {build_info.device}
Tianjie Xu9afb2212020-05-10 21:48:15 +00002003 fingerprints = {build_info.fingerprint}
2004
Tianjied6867162020-05-10 14:30:13 -07002005 if not boot_variable_values:
2006 return device_names, fingerprints
Tianjie Xu9afb2212020-05-10 21:48:15 +00002007
2008 # Calculate all possible combinations of the values for the boot variables.
Tianjied6867162020-05-10 14:30:13 -07002009 keys = boot_variable_values.keys()
2010 value_list = boot_variable_values.values()
Tianjie Xu9afb2212020-05-10 21:48:15 +00002011 combinations = [dict(zip(keys, values))
2012 for values in itertools.product(*value_list)]
2013 for placeholder_values in combinations:
2014 # Reload the info_dict as some build properties may change their values
2015 # based on the value of ro.boot* properties.
Tianjied6867162020-05-10 14:30:13 -07002016 info_dict = copy.deepcopy(build_info.info_dict)
Tianjie Xu9afb2212020-05-10 21:48:15 +00002017 for partition in common.PARTITIONS_WITH_CARE_MAP:
2018 partition_prop_key = "{}.build.prop".format(partition)
2019 old_props = info_dict[partition_prop_key]
2020 info_dict[partition_prop_key] = common.PartitionBuildProps.FromInputFile(
2021 old_props.input_file, partition, placeholder_values)
2022 info_dict["build.prop"] = info_dict["system.build.prop"]
2023
Tianjied6867162020-05-10 14:30:13 -07002024 new_build_info = common.BuildInfo(info_dict, build_info.oem_dicts)
2025 device_names.add(new_build_info.device)
2026 fingerprints.add(new_build_info.fingerprint)
2027 return device_names, fingerprints
Tianjie Xu9afb2212020-05-10 21:48:15 +00002028
2029
Doug Zongkereef39442009-04-02 12:14:19 -07002030def main(argv):
2031
2032 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002033 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002034 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002035 elif o in ("-i", "--incremental_from"):
2036 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002037 elif o == "--full_radio":
2038 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002039 elif o == "--full_bootloader":
2040 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002041 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002042 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002043 elif o == "--downgrade":
2044 OPTIONS.downgrade = True
2045 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002046 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002047 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002048 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002049 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002050 elif o == "--oem_no_mount":
2051 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002052 elif o in ("-e", "--extra_script"):
2053 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002054 elif o in ("-t", "--worker_threads"):
2055 if a.isdigit():
2056 OPTIONS.worker_threads = int(a)
2057 else:
2058 raise ValueError("Cannot parse value %r for option %r - only "
2059 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002060 elif o in ("-2", "--two_step"):
2061 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002062 elif o == "--include_secondary":
2063 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002064 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002065 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002066 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002067 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002068 elif o == "--block":
2069 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002070 elif o in ("-b", "--binary"):
2071 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002072 elif o == "--stash_threshold":
2073 try:
2074 OPTIONS.stash_threshold = float(a)
2075 except ValueError:
2076 raise ValueError("Cannot parse value %r for option %r - expecting "
2077 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002078 elif o == "--log_diff":
2079 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002080 elif o == "--payload_signer":
2081 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002082 elif o == "--payload_signer_args":
2083 OPTIONS.payload_signer_args = shlex.split(a)
Tianjie Xu21e6deb2019-10-07 18:01:00 -07002084 elif o == "--payload_signer_maximum_signature_size":
2085 OPTIONS.payload_signer_maximum_signature_size = a
xunchang376cc7c2019-04-08 23:04:58 -07002086 elif o == "--payload_signer_key_size":
Tianjie Xu21e6deb2019-10-07 18:01:00 -07002087 # TODO(Xunchang) remove this option after cleaning up the callers.
2088 logger.warning("The option '--payload_signer_key_size' is deprecated."
2089 " Use '--payload_signer_maximum_signature_size' instead.")
2090 OPTIONS.payload_signer_maximum_signature_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002091 elif o == "--extracted_input_target_files":
2092 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002093 elif o == "--skip_postinstall":
2094 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002095 elif o == "--retrofit_dynamic_partitions":
2096 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002097 elif o == "--skip_compatibility_check":
2098 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002099 elif o == "--output_metadata_path":
2100 OPTIONS.output_metadata_path = a
Tianjie Xu1b079832019-08-28 12:19:23 -07002101 elif o == "--disable_fec_computation":
2102 OPTIONS.disable_fec_computation = True
Yifan Hong65afc072020-04-17 10:08:10 -07002103 elif o == "--force_non_ab":
2104 OPTIONS.force_non_ab = True
Tianjied6867162020-05-10 14:30:13 -07002105 elif o == "--boot_variable_file":
2106 OPTIONS.boot_variable_file = a
Doug Zongkereef39442009-04-02 12:14:19 -07002107 else:
2108 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002109 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002110
2111 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002112 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002113 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002114 "package_key=",
2115 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002116 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002117 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002118 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002119 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002120 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002121 "extra_script=",
2122 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002123 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002124 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002125 "no_signing",
2126 "block",
2127 "binary=",
2128 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002129 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002130 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002131 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002132 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002133 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002134 "payload_signer_args=",
Tianjie Xu21e6deb2019-10-07 18:01:00 -07002135 "payload_signer_maximum_signature_size=",
xunchang376cc7c2019-04-08 23:04:58 -07002136 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002137 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002138 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002139 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002140 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002141 "output_metadata_path=",
Tianjie Xu1b079832019-08-28 12:19:23 -07002142 "disable_fec_computation",
Yifan Hong65afc072020-04-17 10:08:10 -07002143 "force_non_ab",
Tianjied6867162020-05-10 14:30:13 -07002144 "boot_variable_file=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002145 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002146
2147 if len(args) != 2:
2148 common.Usage(__doc__)
2149 sys.exit(1)
2150
Tao Bao32fcdab2018-10-12 10:30:39 -07002151 common.InitLogging()
2152
Tao Bao5d182562016-02-23 11:38:39 -08002153 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002154 # We should only allow downgrading incrementals (as opposed to full).
2155 # Otherwise the device may go back from arbitrary build with this full
2156 # OTA package.
2157 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002158 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002159
Tao Bao2db13852018-01-08 22:28:57 -08002160 # Load the build info dicts from the zip directly or the extracted input
2161 # directory. We don't need to unzip the entire target-files zips, because they
2162 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2163 # When loading the info dicts, we don't need to provide the second parameter
2164 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2165 # some properties with their actual paths, such as 'selinux_fc',
2166 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002167 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002168 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002169 else:
Tao Bao2db13852018-01-08 22:28:57 -08002170 with zipfile.ZipFile(args[0], 'r') as input_zip:
2171 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002172
Tao Bao32fcdab2018-10-12 10:30:39 -07002173 logger.info("--- target info ---")
2174 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002175
2176 # Load the source build dict if applicable.
2177 if OPTIONS.incremental_source is not None:
2178 OPTIONS.target_info_dict = OPTIONS.info_dict
2179 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2180 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2181
Tao Bao32fcdab2018-10-12 10:30:39 -07002182 logger.info("--- source info ---")
2183 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002184
2185 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002186 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2187
Yifan Hong50e79542018-11-08 17:44:12 -08002188 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002189 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002190 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002191 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2192 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002193 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2194 raise common.ExternalError(
2195 "Expect to generate incremental OTA for retrofitting dynamic "
2196 "partitions, but dynamic_partition_retrofit is not set in target "
2197 "build.")
2198 logger.info("Implicitly generating retrofit incremental OTA.")
2199 OPTIONS.retrofit_dynamic_partitions = True
2200
2201 # Skip postinstall for retrofitting dynamic partitions.
2202 if OPTIONS.retrofit_dynamic_partitions:
2203 OPTIONS.skip_postinstall = True
2204
Tao Baoc098e9e2016-01-07 13:03:56 -08002205 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
Yifan Hong65afc072020-04-17 10:08:10 -07002206 allow_non_ab = OPTIONS.info_dict.get("allow_non_ab") == "true"
2207 if OPTIONS.force_non_ab:
2208 assert allow_non_ab, "--force_non_ab only allowed on devices that supports non-A/B"
2209 assert ab_update, "--force_non_ab only allowed on A/B devices"
2210
2211 generate_ab = not OPTIONS.force_non_ab and ab_update
Tao Baoc098e9e2016-01-07 13:03:56 -08002212
Christian Oderf63e2cd2017-05-01 22:30:15 +02002213 # Use the default key to sign the package if not specified with package_key.
2214 # package_keys are needed on ab_updates, so always define them if an
Yifan Hong65afc072020-04-17 10:08:10 -07002215 # A/B update is getting created.
2216 if not OPTIONS.no_signing or generate_ab:
Christian Oderf63e2cd2017-05-01 22:30:15 +02002217 if OPTIONS.package_key is None:
2218 OPTIONS.package_key = OPTIONS.info_dict.get(
2219 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002220 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002221 # Get signing keys
2222 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2223
Yifan Hong65afc072020-04-17 10:08:10 -07002224 if generate_ab:
Tao Baof0c4aa22018-04-30 20:29:30 -07002225 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002226 target_file=args[0],
2227 output_file=args[1],
2228 source_file=OPTIONS.incremental_source)
2229
Dan Willemsencea5cd22017-03-21 14:44:27 -07002230 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002231 GenerateNonAbOtaPackage(
2232 target_file=args[0],
2233 output_file=args[1],
2234 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002235
Tao Baof0c4aa22018-04-30 20:29:30 -07002236 # Post OTA generation works.
2237 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2238 logger.info("Generating diff logs...")
2239 logger.info("Unzipping target-files for diffing...")
2240 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2241 source_dir = common.UnzipTemp(
2242 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002243
Tao Baof0c4aa22018-04-30 20:29:30 -07002244 with open(OPTIONS.log_diff, 'w') as out_file:
Tao Baof0c4aa22018-04-30 20:29:30 -07002245 target_files_diff.recursiveDiff(
2246 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002247
Tao Bao32fcdab2018-10-12 10:30:39 -07002248 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002249
2250
2251if __name__ == '__main__':
2252 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002253 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002254 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002255 except common.ExternalError:
2256 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002257 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002258 finally:
2259 common.Cleanup()