blob: 24d0b2e31facdd821c98fcb9386dbf4e6559bae9 [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
75 Skip adding the compatibility package to the generated OTA package.
76
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
Tao Bao30df8b42018-04-23 15:32:53 -070081Non-A/B OTA specific options
82
83 -b (--binary) <file>
84 Use the given binary as the update-binary in the output package, instead
85 of the binary in the build's target_files. Use for development only.
86
87 --block
88 Generate a block-based OTA for non-A/B device. We have deprecated the
89 support for file-based OTA since O. Block-based OTA will be used by
90 default for all non-A/B devices. Keeping this flag here to not break
91 existing callers.
92
93 -e (--extra_script) <file>
94 Insert the contents of file at the end of the update script.
Tao Bao43078aa2015-04-21 14:32:35 -070095
leozwangaa6c1a12015-08-14 10:57:58 -070096 --full_bootloader
97 Similar to --full_radio. When generating an incremental OTA, always
98 include a full copy of bootloader image.
99
Tao Bao30df8b42018-04-23 15:32:53 -0700100 --full_radio
101 When generating an incremental OTA, always include a full copy of radio
102 image. This option is only meaningful when -i is specified, because a full
103 radio is always included in a full OTA if applicable.
Michael Runge63f01de2014-10-28 19:24:19 -0700104
Tao Bao30df8b42018-04-23 15:32:53 -0700105 --log_diff <file>
106 Generate a log file that shows the differences in the source and target
107 builds for an incremental package. This option is only meaningful when -i
108 is specified.
109
110 -o (--oem_settings) <main_file[,additional_files...]>
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800111 Comma seperated list of files used to specify the expected OEM-specific
Tao Bao481bab82017-12-21 11:23:09 -0800112 properties on the OEM partition of the intended device. Multiple expected
113 values can be used by providing multiple files. Only the first dict will
114 be used to compute fingerprint, while the rest will be used to assert
115 OEM-specific properties.
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800116
Tao Bao8608cde2016-02-25 19:49:55 -0800117 --oem_no_mount
Tao Bao30df8b42018-04-23 15:32:53 -0700118 For devices with OEM-specific properties but without an OEM partition, do
119 not mount the OEM partition in the updater-script. This should be very
120 rarely used, since it's expected to have a dedicated OEM partition for
121 OEM-specific properties. Only meaningful when -o is specified.
Tao Bao8608cde2016-02-25 19:49:55 -0800122
Tao Bao30df8b42018-04-23 15:32:53 -0700123 --stash_threshold <float>
124 Specify the threshold that will be used to compute the maximum allowed
125 stash size (defaults to 0.8).
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700126
Tao Bao30df8b42018-04-23 15:32:53 -0700127 -t (--worker_threads) <int>
128 Specify the number of worker-threads that will be used when generating
129 patches for incremental updates (defaults to 3).
Tao Bao3e6161a2017-02-28 11:48:48 -0800130
Tao Bao30df8b42018-04-23 15:32:53 -0700131 --verify
132 Verify the checksums of the updated system and vendor (if any) partitions.
133 Non-A/B incremental OTAs only.
Doug Zongker1c390a22009-05-14 19:06:36 -0700134
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800135 -2 (--two_step)
Tao Bao30df8b42018-04-23 15:32:53 -0700136 Generate a 'two-step' OTA package, where recovery is updated first, so
137 that any changes made to the system partition are done using the new
138 recovery (new kernel, etc.).
139
140A/B OTA specific options
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800141
Tianjie Xu1b079832019-08-28 12:19:23 -0700142 --disable_fec_computation
143 Disable the on device FEC data computation for incremental updates.
144
Tao Baof7140c02018-01-30 17:09:24 -0800145 --include_secondary
146 Additionally include the payload for secondary slot images (default:
147 False). Only meaningful when generating A/B OTAs.
148
149 By default, an A/B OTA package doesn't contain the images for the
150 secondary slot (e.g. system_other.img). Specifying this flag allows
151 generating a separate payload that will install secondary slot images.
152
153 Such a package needs to be applied in a two-stage manner, with a reboot
154 in-between. During the first stage, the updater applies the primary
155 payload only. Upon finishing, it reboots the device into the newly updated
156 slot. It then continues to install the secondary payload to the inactive
157 slot, but without switching the active slot at the end (needs the matching
158 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag).
159
160 Due to the special install procedure, the secondary payload will be always
161 generated as a full payload.
162
Tao Baodea0f8b2016-06-20 17:55:06 -0700163 --payload_signer <signer>
164 Specify the signer when signing the payload and metadata for A/B OTAs.
165 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
166 with the package private key. If the private key cannot be accessed
167 directly, a payload signer that knows how to do that should be specified.
168 The signer will be supplied with "-inkey <path_to_key>",
169 "-in <input_file>" and "-out <output_file>" parameters.
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700170
171 --payload_signer_args <args>
172 Specify the arguments needed for payload signer.
Tao Bao15a146a2018-02-21 16:06:59 -0800173
xunchang376cc7c2019-04-08 23:04:58 -0700174 --payload_signer_key_size <key_size>
175 Specify the key size in bytes of the payload signer.
176
Tao Bao15a146a2018-02-21 16:06:59 -0800177 --skip_postinstall
178 Skip the postinstall hooks when generating an A/B OTA package (default:
179 False). Note that this discards ALL the hooks, including non-optional
180 ones. Should only be used if caller knows it's safe to do so (e.g. all the
181 postinstall work is to dexopt apps and a data wipe will happen immediately
182 after). Only meaningful when generating A/B OTAs.
Doug Zongkereef39442009-04-02 12:14:19 -0700183"""
184
Tao Bao89fbb0f2017-01-10 10:47:58 -0800185from __future__ import print_function
186
Tianjie Xuf67dd802019-05-20 17:50:36 -0700187import collections
Tao Bao32fcdab2018-10-12 10:30:39 -0700188import logging
Doug Zongkerfc44a512014-08-26 13:10:25 -0700189import multiprocessing
Tao Bao2dd1c482017-02-03 16:49:39 -0800190import os.path
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700191import shlex
Tao Bao15a146a2018-02-21 16:06:59 -0800192import shutil
Tao Bao85f16982018-03-08 16:28:33 -0800193import struct
Tao Bao481bab82017-12-21 11:23:09 -0800194import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700195import tempfile
Doug Zongkereef39442009-04-02 12:14:19 -0700196import zipfile
197
198import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700199import edify_generator
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700200import verity_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700201
Tao Bao481bab82017-12-21 11:23:09 -0800202if sys.hexversion < 0x02070000:
203 print("Python 2.7 or newer is required.", file=sys.stderr)
204 sys.exit(1)
205
Tao Bao32fcdab2018-10-12 10:30:39 -0700206logger = logging.getLogger(__name__)
Tao Bao481bab82017-12-21 11:23:09 -0800207
Doug Zongkereef39442009-04-02 12:14:19 -0700208OPTIONS = common.OPTIONS
Doug Zongkerafb32ea2011-09-22 10:28:04 -0700209OPTIONS.package_key = None
Doug Zongkereef39442009-04-02 12:14:19 -0700210OPTIONS.incremental_source = None
Michael Runge63f01de2014-10-28 19:24:19 -0700211OPTIONS.verify = False
Doug Zongkereef39442009-04-02 12:14:19 -0700212OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700213OPTIONS.wipe_user_data = False
Tao Bao5d182562016-02-23 11:38:39 -0800214OPTIONS.downgrade = False
Doug Zongker1c390a22009-05-14 19:06:36 -0700215OPTIONS.extra_script = None
Doug Zongkerfc44a512014-08-26 13:10:25 -0700216OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
217if OPTIONS.worker_threads == 0:
218 OPTIONS.worker_threads = 1
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800219OPTIONS.two_step = False
Tao Baof7140c02018-01-30 17:09:24 -0800220OPTIONS.include_secondary = False
Takeshi Kanemotoe153b342013-11-14 17:20:50 +0900221OPTIONS.no_signing = False
Tao Bao457cbf62017-03-06 09:56:01 -0800222OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -0800223OPTIONS.updater_binary = None
Michael Runge6e836112014-04-15 17:40:21 -0700224OPTIONS.oem_source = None
Tao Bao8608cde2016-02-25 19:49:55 -0800225OPTIONS.oem_no_mount = False
Tao Bao43078aa2015-04-21 14:32:35 -0700226OPTIONS.full_radio = False
leozwangaa6c1a12015-08-14 10:57:58 -0700227OPTIONS.full_bootloader = False
Tao Baod47d8e12015-05-21 14:09:49 -0700228# Stash size cannot exceed cache_size * threshold.
229OPTIONS.cache_size = None
230OPTIONS.stash_threshold = 0.8
Tao Baod62c6032015-11-30 09:40:20 -0800231OPTIONS.log_diff = None
Tao Baodea0f8b2016-06-20 17:55:06 -0700232OPTIONS.payload_signer = None
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700233OPTIONS.payload_signer_args = []
xunchang376cc7c2019-04-08 23:04:58 -0700234OPTIONS.payload_signer_key_size = None
Tao Bao5f8ff932017-03-21 22:35:00 -0700235OPTIONS.extracted_input = None
Christian Oderf63e2cd2017-05-01 22:30:15 +0200236OPTIONS.key_passwords = []
Tao Bao15a146a2018-02-21 16:06:59 -0800237OPTIONS.skip_postinstall = False
Yifan Hong50e79542018-11-08 17:44:12 -0800238OPTIONS.retrofit_dynamic_partitions = False
xunchangabfa2652019-02-19 16:27:10 -0800239OPTIONS.skip_compatibility_check = False
xunchang1cfe2512019-02-19 14:14:48 -0800240OPTIONS.output_metadata_path = None
Tianjie Xu1b079832019-08-28 12:19:23 -0700241OPTIONS.disable_fec_computation = False
Tao Bao15a146a2018-02-21 16:06:59 -0800242
Tao Bao8dcf7382015-05-21 14:09:49 -0700243
Tao Bao2dd1c482017-02-03 16:49:39 -0800244METADATA_NAME = 'META-INF/com/android/metadata'
Tao Bao15a146a2018-02-21 16:06:59 -0800245POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
Yifan Hong50e79542018-11-08 17:44:12 -0800246DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
Yifan Hongb433eba2019-03-06 12:42:53 -0800247AB_PARTITIONS = 'META/ab_partitions.txt'
Tao Bao04808502019-07-25 23:11:41 -0700248UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Tao Baof0c4aa22018-04-30 20:29:30 -0700249# Files to be unzipped for target diffing purpose.
250TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
251 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800252RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tao Bao6b0b2f92017-03-05 11:38:11 -0800253
Tao Bao2dd1c482017-02-03 16:49:39 -0800254
Tao Bao481bab82017-12-21 11:23:09 -0800255class BuildInfo(object):
256 """A class that holds the information for a given build.
257
258 This class wraps up the property querying for a given source or target build.
259 It abstracts away the logic of handling OEM-specific properties, and caches
260 the commonly used properties such as fingerprint.
261
262 There are two types of info dicts: a) build-time info dict, which is generated
263 at build time (i.e. included in a target_files zip); b) OEM info dict that is
264 specified at package generation time (via command line argument
265 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
266 having "oem_fingerprint_properties" in build-time info dict), all the queries
267 would be answered based on build-time info dict only. Otherwise if using
268 OEM-specific properties, some of them will be calculated from two info dicts.
269
270 Users can query properties similarly as using a dict() (e.g. info['fstab']),
271 or to query build properties via GetBuildProp() or GetVendorBuildProp().
272
273 Attributes:
274 info_dict: The build-time info dict.
275 is_ab: Whether it's a build that uses A/B OTA.
276 oem_dicts: A list of OEM dicts.
277 oem_props: A list of OEM properties that should be read from OEM dicts; None
278 if the build doesn't use any OEM-specific property.
279 fingerprint: The fingerprint of the build, which would be calculated based
280 on OEM properties if applicable.
281 device: The device name, which could come from OEM dicts if applicable.
282 """
283
Steven Laver9e73e822019-01-29 20:20:08 -0800284 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
285 "ro.product.manufacturer", "ro.product.model",
286 "ro.product.name"]
Justin Yun6151e3f2019-06-25 15:58:13 +0900287 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
288 "system_ext", "system"]
Steven Laver9e73e822019-01-29 20:20:08 -0800289
Tao Bao481bab82017-12-21 11:23:09 -0800290 def __init__(self, info_dict, oem_dicts):
291 """Initializes a BuildInfo instance with the given dicts.
292
Tao Bao667c7532018-07-06 10:13:59 -0700293 Note that it only wraps up the given dicts, without making copies.
294
Tao Bao481bab82017-12-21 11:23:09 -0800295 Arguments:
296 info_dict: The build-time info dict.
297 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
298 that it always uses the first dict to calculate the fingerprint or the
299 device name. The rest would be used for asserting OEM properties only
Tao Bao667c7532018-07-06 10:13:59 -0700300 (e.g. one package can be installed on one of these devices).
Tao Bao481bab82017-12-21 11:23:09 -0800301 """
302 self.info_dict = info_dict
303 self.oem_dicts = oem_dicts
304
305 self._is_ab = info_dict.get("ab_update") == "true"
306 self._oem_props = info_dict.get("oem_fingerprint_properties")
307
308 if self._oem_props:
309 assert oem_dicts, "OEM source required for this build"
310
311 # These two should be computed only after setting self._oem_props.
312 self._device = self.GetOemProperty("ro.product.device")
313 self._fingerprint = self.CalculateFingerprint()
314
315 @property
316 def is_ab(self):
317 return self._is_ab
318
319 @property
320 def device(self):
321 return self._device
322
323 @property
324 def fingerprint(self):
325 return self._fingerprint
326
327 @property
Tao Baoea6cbd02018-09-05 13:06:37 -0700328 def vendor_fingerprint(self):
Yifan Hong51d37562019-04-23 17:06:46 -0700329 return self._fingerprint_of("vendor")
330
331 @property
332 def product_fingerprint(self):
333 return self._fingerprint_of("product")
334
335 @property
336 def odm_fingerprint(self):
337 return self._fingerprint_of("odm")
338
339 def _fingerprint_of(self, partition):
340 if partition + ".build.prop" not in self.info_dict:
Tao Baoea6cbd02018-09-05 13:06:37 -0700341 return None
Yifan Hong51d37562019-04-23 17:06:46 -0700342 build_prop = self.info_dict[partition + ".build.prop"]
343 if "ro." + partition + ".build.fingerprint" in build_prop:
344 return build_prop["ro." + partition + ".build.fingerprint"]
345 if "ro." + partition + ".build.thumbprint" in build_prop:
346 return build_prop["ro." + partition + ".build.thumbprint"]
Tao Baoea6cbd02018-09-05 13:06:37 -0700347 return None
348
349 @property
Tao Bao481bab82017-12-21 11:23:09 -0800350 def oem_props(self):
351 return self._oem_props
352
353 def __getitem__(self, key):
354 return self.info_dict[key]
355
Tao Bao667c7532018-07-06 10:13:59 -0700356 def __setitem__(self, key, value):
357 self.info_dict[key] = value
358
Tao Bao481bab82017-12-21 11:23:09 -0800359 def get(self, key, default=None):
360 return self.info_dict.get(key, default)
361
Tao Bao667c7532018-07-06 10:13:59 -0700362 def items(self):
363 return self.info_dict.items()
364
Tao Bao481bab82017-12-21 11:23:09 -0800365 def GetBuildProp(self, prop):
366 """Returns the inquired build property."""
Steven Laver9e73e822019-01-29 20:20:08 -0800367 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
368 return self._ResolveRoProductBuildProp(prop)
369
Tao Bao481bab82017-12-21 11:23:09 -0800370 try:
371 return self.info_dict.get("build.prop", {})[prop]
372 except KeyError:
373 raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
374
Steven Laver9e73e822019-01-29 20:20:08 -0800375 def _ResolveRoProductBuildProp(self, prop):
376 """Resolves the inquired ro.product.* build property"""
377 prop_val = self.info_dict.get("build.prop", {}).get(prop)
378 if prop_val:
379 return prop_val
380
381 source_order_val = self.info_dict.get("build.prop", {}).get(
Tao Bao59cf0c52019-06-25 10:04:24 -0700382 "ro.product.property_source_order")
Steven Laver9e73e822019-01-29 20:20:08 -0800383 if source_order_val:
384 source_order = source_order_val.split(",")
385 else:
386 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
387
388 # Check that all sources in ro.product.property_source_order are valid
389 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
390 for x in source_order]):
391 raise common.ExternalError(
Tao Bao59cf0c52019-06-25 10:04:24 -0700392 "Invalid ro.product.property_source_order '{}'".format(source_order))
Steven Laver9e73e822019-01-29 20:20:08 -0800393
394 for source in source_order:
Tao Bao59cf0c52019-06-25 10:04:24 -0700395 source_prop = prop.replace(
396 "ro.product", "ro.product.{}".format(source), 1)
397 prop_val = self.info_dict.get(
398 "{}.build.prop".format(source), {}).get(source_prop)
Steven Laver9e73e822019-01-29 20:20:08 -0800399 if prop_val:
400 return prop_val
401
402 raise common.ExternalError("couldn't resolve {}".format(prop))
403
Tao Bao481bab82017-12-21 11:23:09 -0800404 def GetVendorBuildProp(self, prop):
405 """Returns the inquired vendor build property."""
406 try:
407 return self.info_dict.get("vendor.build.prop", {})[prop]
408 except KeyError:
409 raise common.ExternalError(
410 "couldn't find %s in vendor.build.prop" % (prop,))
411
412 def GetOemProperty(self, key):
413 if self.oem_props is not None and key in self.oem_props:
414 return self.oem_dicts[0][key]
415 return self.GetBuildProp(key)
416
417 def CalculateFingerprint(self):
418 if self.oem_props is None:
Steven Laver9e73e822019-01-29 20:20:08 -0800419 try:
420 return self.GetBuildProp("ro.build.fingerprint")
421 except common.ExternalError:
422 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
Tao Bao59cf0c52019-06-25 10:04:24 -0700423 self.GetBuildProp("ro.product.brand"),
424 self.GetBuildProp("ro.product.name"),
425 self.GetBuildProp("ro.product.device"),
426 self.GetBuildProp("ro.build.version.release"),
427 self.GetBuildProp("ro.build.id"),
428 self.GetBuildProp("ro.build.version.incremental"),
429 self.GetBuildProp("ro.build.type"),
430 self.GetBuildProp("ro.build.tags"))
Tao Bao481bab82017-12-21 11:23:09 -0800431 return "%s/%s/%s:%s" % (
432 self.GetOemProperty("ro.product.brand"),
433 self.GetOemProperty("ro.product.name"),
434 self.GetOemProperty("ro.product.device"),
435 self.GetBuildProp("ro.build.thumbprint"))
436
437 def WriteMountOemScript(self, script):
438 assert self.oem_props is not None
439 recovery_mount_options = self.info_dict.get("recovery_mount_options")
440 script.Mount("/oem", recovery_mount_options)
441
442 def WriteDeviceAssertions(self, script, oem_no_mount):
443 # Read the property directly if not using OEM properties.
444 if not self.oem_props:
445 script.AssertDevice(self.device)
446 return
447
448 # Otherwise assert OEM properties.
449 if not self.oem_dicts:
450 raise common.ExternalError(
451 "No OEM file provided to answer expected assertions")
452
453 for prop in self.oem_props.split():
454 values = []
455 for oem_dict in self.oem_dicts:
456 if prop in oem_dict:
457 values.append(oem_dict[prop])
458 if not values:
459 raise common.ExternalError(
460 "The OEM file is missing the property %s" % (prop,))
461 script.AssertOemProperty(prop, values, oem_no_mount)
462
463
Tao Baofabe0832018-01-17 15:52:28 -0800464class PayloadSigner(object):
465 """A class that wraps the payload signing works.
466
467 When generating a Payload, hashes of the payload and metadata files will be
468 signed with the device key, either by calling an external payload signer or
469 by calling openssl with the package key. This class provides a unified
470 interface, so that callers can just call PayloadSigner.Sign().
471
472 If an external payload signer has been specified (OPTIONS.payload_signer), it
473 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
474 that the signing key should be provided as part of the payload_signer_args.
475 Otherwise without an external signer, it uses the package key
476 (OPTIONS.package_key) and calls openssl for the signing works.
477 """
478
479 def __init__(self):
480 if OPTIONS.payload_signer is None:
481 # Prepare the payload signing key.
482 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
483 pw = OPTIONS.key_passwords[OPTIONS.package_key]
484
485 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
486 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
487 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
488 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700489 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800490
491 self.signer = "openssl"
492 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
493 "-pkeyopt", "digest:sha256"]
xunchang376cc7c2019-04-08 23:04:58 -0700494 self.key_size = self._GetKeySizeInBytes(signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800495 else:
496 self.signer = OPTIONS.payload_signer
497 self.signer_args = OPTIONS.payload_signer_args
xunchang376cc7c2019-04-08 23:04:58 -0700498 if OPTIONS.payload_signer_key_size:
499 self.key_size = int(OPTIONS.payload_signer_key_size)
500 assert self.key_size == 256 or self.key_size == 512, \
501 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size)
502 else:
503 self.key_size = 256
504
505 @staticmethod
506 def _GetKeySizeInBytes(signing_key):
507 modulus_file = common.MakeTempFile(prefix="modulus-")
508 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus",
509 "-noout", "-out", modulus_file]
510 common.RunAndCheckOutput(cmd, verbose=False)
511
512 with open(modulus_file) as f:
513 modulus_string = f.read()
514 # The modulus string has the format "Modulus=$data", where $data is the
515 # concatenation of hex dump of the modulus.
516 MODULUS_PREFIX = "Modulus="
517 assert modulus_string.startswith(MODULUS_PREFIX)
518 modulus_string = modulus_string[len(MODULUS_PREFIX):]
Tao Bao59cf0c52019-06-25 10:04:24 -0700519 key_size = len(modulus_string) // 2
xunchang376cc7c2019-04-08 23:04:58 -0700520 assert key_size == 256 or key_size == 512, \
521 "Unsupported key size {}".format(key_size)
522 return key_size
Tao Baofabe0832018-01-17 15:52:28 -0800523
524 def Sign(self, in_file):
525 """Signs the given input file. Returns the output filename."""
526 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
527 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700528 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800529 return out_file
530
531
Tao Bao40b18822018-01-30 18:19:04 -0800532class Payload(object):
533 """Manages the creation and the signing of an A/B OTA Payload."""
534
535 PAYLOAD_BIN = 'payload.bin'
536 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800537 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
538 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800539
Tao Bao667ff572018-02-10 00:02:40 -0800540 def __init__(self, secondary=False):
541 """Initializes a Payload instance.
542
543 Args:
544 secondary: Whether it's generating a secondary payload (default: False).
545 """
Tao Bao40b18822018-01-30 18:19:04 -0800546 self.payload_file = None
547 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800548 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800549
Tao Baof0c4aa22018-04-30 20:29:30 -0700550 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700551 # Don't pipe (buffer) the output if verbose is set. Let
552 # brillo_update_payload write to stdout/stderr directly, so its progress can
553 # be monitored.
554 if OPTIONS.verbose:
555 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
556 else:
557 common.RunAndCheckOutput(cmd)
558
Tao Bao40b18822018-01-30 18:19:04 -0800559 def Generate(self, target_file, source_file=None, additional_args=None):
560 """Generates a payload from the given target-files zip(s).
561
562 Args:
563 target_file: The filename of the target build target-files zip.
564 source_file: The filename of the source build target-files zip; or None if
565 generating a full OTA.
566 additional_args: A list of additional args that should be passed to
567 brillo_update_payload script; or None.
568 """
569 if additional_args is None:
570 additional_args = []
571
572 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
573 cmd = ["brillo_update_payload", "generate",
574 "--payload", payload_file,
575 "--target_image", target_file]
576 if source_file is not None:
577 cmd.extend(["--source_image", source_file])
Tianjie Xu1b079832019-08-28 12:19:23 -0700578 if OPTIONS.disable_fec_computation:
579 cmd.extend(["--disable_fec_computation", "true"])
Tao Bao40b18822018-01-30 18:19:04 -0800580 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700581 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800582
583 self.payload_file = payload_file
584 self.payload_properties = None
585
586 def Sign(self, payload_signer):
587 """Generates and signs the hashes of the payload and metadata.
588
589 Args:
590 payload_signer: A PayloadSigner() instance that serves the signing work.
591
592 Raises:
593 AssertionError: On any failure when calling brillo_update_payload script.
594 """
595 assert isinstance(payload_signer, PayloadSigner)
596
597 # 1. Generate hashes of the payload and metadata files.
598 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
599 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
600 cmd = ["brillo_update_payload", "hash",
601 "--unsigned_payload", self.payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700602 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800603 "--metadata_hash_file", metadata_sig_file,
604 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700605 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800606
607 # 2. Sign the hashes.
608 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
609 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
610
611 # 3. Insert the signatures back into the payload file.
612 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
613 suffix=".bin")
614 cmd = ["brillo_update_payload", "sign",
615 "--unsigned_payload", self.payload_file,
616 "--payload", signed_payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700617 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800618 "--metadata_signature_file", signed_metadata_sig_file,
619 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700620 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800621
622 # 4. Dump the signed payload properties.
623 properties_file = common.MakeTempFile(prefix="payload-properties-",
624 suffix=".txt")
625 cmd = ["brillo_update_payload", "properties",
626 "--payload", signed_payload_file,
627 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700628 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800629
Tao Bao667ff572018-02-10 00:02:40 -0800630 if self.secondary:
631 with open(properties_file, "a") as f:
632 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
633
Tao Bao40b18822018-01-30 18:19:04 -0800634 if OPTIONS.wipe_user_data:
635 with open(properties_file, "a") as f:
636 f.write("POWERWASH=1\n")
637
638 self.payload_file = signed_payload_file
639 self.payload_properties = properties_file
640
Tao Bao667ff572018-02-10 00:02:40 -0800641 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800642 """Writes the payload to the given zip.
643
644 Args:
645 output_zip: The output ZipFile instance.
646 """
647 assert self.payload_file is not None
648 assert self.payload_properties is not None
649
Tao Bao667ff572018-02-10 00:02:40 -0800650 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800651 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
652 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
653 else:
654 payload_arcname = Payload.PAYLOAD_BIN
655 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
656
Tao Bao40b18822018-01-30 18:19:04 -0800657 # Add the signed payload file and properties into the zip. In order to
658 # support streaming, we pack them as ZIP_STORED. So these entries can be
659 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800660 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800661 compress_type=zipfile.ZIP_STORED)
662 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800663 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800664 compress_type=zipfile.ZIP_STORED)
665
666
Doug Zongkereef39442009-04-02 12:14:19 -0700667def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200668 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700669
Doug Zongker951495f2009-08-14 12:44:19 -0700670 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
671 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700672
673
Tao Bao481bab82017-12-21 11:23:09 -0800674def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800675 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800676 if not oem_source:
677 return None
678
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800679 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800680 for oem_file in oem_source:
681 with open(oem_file) as fp:
682 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800683 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700684
Doug Zongkereef39442009-04-02 12:14:19 -0700685
Tao Baod42e97e2016-11-30 12:11:57 -0800686def _WriteRecoveryImageToBoot(script, output_zip):
687 """Find and write recovery image to /boot in two-step OTA.
688
689 In two-step OTAs, we write recovery image to /boot as the first step so that
690 we can reboot to there and install a new recovery image to /recovery.
691 A special "recovery-two-step.img" will be preferred, which encodes the correct
692 path of "/boot". Otherwise the device may show "device is corrupt" message
693 when booting into /boot.
694
695 Fall back to using the regular recovery.img if the two-step recovery image
696 doesn't exist. Note that rebuilding the special image at this point may be
697 infeasible, because we don't have the desired boot signer and keys when
698 calling ota_from_target_files.py.
699 """
700
701 recovery_two_step_img_name = "recovery-two-step.img"
702 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700703 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800704 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700705 common.ZipWrite(
706 output_zip,
707 recovery_two_step_img_path,
708 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700709 logger.info(
710 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800711 script.WriteRawImage("/boot", recovery_two_step_img_name)
712 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700713 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800714 # The "recovery.img" entry has been written into package earlier.
715 script.WriteRawImage("/boot", "recovery.img")
716
717
Doug Zongkerc9253822014-02-04 12:17:58 -0800718def HasRecoveryPatch(target_files_zip):
Tao Baof2cffbd2015-07-22 12:33:18 -0700719 namelist = [name for name in target_files_zip.namelist()]
720 return ("SYSTEM/recovery-from-boot.p" in namelist or
721 "SYSTEM/etc/recovery.img" in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700722
Tao Bao457cbf62017-03-06 09:56:01 -0800723
Yifan Hong51d37562019-04-23 17:06:46 -0700724def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700725 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700726 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700727 return True
728 except KeyError:
729 return False
730
Tao Bao457cbf62017-03-06 09:56:01 -0800731
Yifan Hong51d37562019-04-23 17:06:46 -0700732def HasVendorPartition(target_files_zip):
733 return HasPartition(target_files_zip, "vendor")
734
735
736def HasProductPartition(target_files_zip):
737 return HasPartition(target_files_zip, "product")
738
739
740def HasOdmPartition(target_files_zip):
741 return HasPartition(target_files_zip, "odm")
742
743
Tao Bao481bab82017-12-21 11:23:09 -0800744def HasTrebleEnabled(target_files_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700745 return (HasVendorPartition(target_files_zip) and
Tao Bao481bab82017-12-21 11:23:09 -0800746 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700747
748
Tao Bao481bab82017-12-21 11:23:09 -0800749def WriteFingerprintAssertion(script, target_info, source_info):
750 source_oem_props = source_info.oem_props
751 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700752
Tao Bao481bab82017-12-21 11:23:09 -0800753 if source_oem_props is None and target_oem_props is None:
754 script.AssertSomeFingerprint(
755 source_info.fingerprint, target_info.fingerprint)
756 elif source_oem_props is not None and target_oem_props is not None:
757 script.AssertSomeThumbprint(
758 target_info.GetBuildProp("ro.build.thumbprint"),
759 source_info.GetBuildProp("ro.build.thumbprint"))
760 elif source_oem_props is None and target_oem_props is not None:
761 script.AssertFingerprintOrThumbprint(
762 source_info.fingerprint,
763 target_info.GetBuildProp("ro.build.thumbprint"))
764 else:
765 script.AssertFingerprintOrThumbprint(
766 target_info.fingerprint,
767 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700768
Doug Zongkerfc44a512014-08-26 13:10:25 -0700769
Tao Bao481bab82017-12-21 11:23:09 -0800770def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info,
771 source_info=None):
Tao Baobcd1d162017-08-26 13:10:26 -0700772 """Adds compatibility info into the output zip if it's Treble-enabled target.
Tao Bao21803d32017-04-19 10:16:09 -0700773
774 Metadata used for on-device compatibility verification is retrieved from
775 target_zip then added to compatibility.zip which is added to the output_zip
776 archive.
777
Tao Baobcd1d162017-08-26 13:10:26 -0700778 Compatibility archive should only be included for devices that have enabled
779 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700780
781 Args:
782 target_zip: Zip file containing the source files to be included for OTA.
783 output_zip: Zip file that will be sent for OTA.
Tao Bao481bab82017-12-21 11:23:09 -0800784 target_info: The BuildInfo instance that holds the target build info.
785 source_info: The BuildInfo instance that holds the source build info, if
786 generating an incremental OTA; None otherwise.
Tao Bao21803d32017-04-19 10:16:09 -0700787 """
788
Yifan Hong51d37562019-04-23 17:06:46 -0700789 def AddCompatibilityArchive(framework_updated, device_updated):
790 """Adds compatibility info based on update status of both sides of Treble
791 boundary.
Tao Bao21803d32017-04-19 10:16:09 -0700792
Tao Baobcd1d162017-08-26 13:10:26 -0700793 Args:
Yifan Hong51d37562019-04-23 17:06:46 -0700794 framework_updated: If True, the system / product image will be updated
795 and therefore their metadata should be included.
796 device_updated: If True, the vendor / odm image will be updated and
797 therefore their metadata should be included.
Tao Baobcd1d162017-08-26 13:10:26 -0700798 """
799 # Determine what metadata we need. Files are names relative to META/.
800 compatibility_files = []
Yifan Hong51d37562019-04-23 17:06:46 -0700801 device_metadata = ("vendor_manifest.xml", "vendor_matrix.xml")
802 framework_metadata = ("system_manifest.xml", "system_matrix.xml")
803 if device_updated:
804 compatibility_files += device_metadata
805 if framework_updated:
806 compatibility_files += framework_metadata
Tao Bao21803d32017-04-19 10:16:09 -0700807
Tao Baobcd1d162017-08-26 13:10:26 -0700808 # Create new archive.
809 compatibility_archive = tempfile.NamedTemporaryFile()
Tao Bao481bab82017-12-21 11:23:09 -0800810 compatibility_archive_zip = zipfile.ZipFile(
811 compatibility_archive, "w", compression=zipfile.ZIP_DEFLATED)
Tao Bao21803d32017-04-19 10:16:09 -0700812
Tao Baobcd1d162017-08-26 13:10:26 -0700813 # Add metadata.
814 for file_name in compatibility_files:
815 target_file_name = "META/" + file_name
Tao Bao21803d32017-04-19 10:16:09 -0700816
Tao Baobcd1d162017-08-26 13:10:26 -0700817 if target_file_name in target_zip.namelist():
818 data = target_zip.read(target_file_name)
819 common.ZipWriteStr(compatibility_archive_zip, file_name, data)
Tao Bao21803d32017-04-19 10:16:09 -0700820
Tao Baobcd1d162017-08-26 13:10:26 -0700821 # Ensure files are written before we copy into output_zip.
822 compatibility_archive_zip.close()
823
824 # Only add the archive if we have any compatibility info.
825 if compatibility_archive_zip.namelist():
826 common.ZipWrite(output_zip, compatibility_archive.name,
827 arcname="compatibility.zip",
828 compress_type=zipfile.ZIP_STORED)
829
Yifan Hong51d37562019-04-23 17:06:46 -0700830 def FingerprintChanged(source_fp, target_fp):
831 if source_fp is None or target_fp is None:
832 return True
833 return source_fp != target_fp
834
Tao Baobcd1d162017-08-26 13:10:26 -0700835 # Will only proceed if the target has enabled the Treble support (as well as
836 # having a /vendor partition).
Tao Bao481bab82017-12-21 11:23:09 -0800837 if not HasTrebleEnabled(target_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700838 return
839
xunchangabfa2652019-02-19 16:27:10 -0800840 # Skip adding the compatibility package as a workaround for b/114240221. The
841 # compatibility will always fail on devices without qualified kernels.
842 if OPTIONS.skip_compatibility_check:
843 return
844
Yifan Hong51d37562019-04-23 17:06:46 -0700845 # Full OTA carries the info for system/vendor/product/odm
Tao Bao481bab82017-12-21 11:23:09 -0800846 if source_info is None:
Tao Baobcd1d162017-08-26 13:10:26 -0700847 AddCompatibilityArchive(True, True)
848 return
849
Tao Bao481bab82017-12-21 11:23:09 -0800850 source_fp = source_info.fingerprint
851 target_fp = target_info.fingerprint
Tao Baobcd1d162017-08-26 13:10:26 -0700852 system_updated = source_fp != target_fp
853
Yifan Hong51d37562019-04-23 17:06:46 -0700854 # other build fingerprints could be possibly blacklisted at build time. For
855 # such a case, we consider those images being changed.
856 vendor_updated = FingerprintChanged(source_info.vendor_fingerprint,
857 target_info.vendor_fingerprint)
858 product_updated = HasProductPartition(target_zip) and \
859 FingerprintChanged(source_info.product_fingerprint,
860 target_info.product_fingerprint)
861 odm_updated = HasOdmPartition(target_zip) and \
862 FingerprintChanged(source_info.odm_fingerprint,
863 target_info.odm_fingerprint)
Tao Baobcd1d162017-08-26 13:10:26 -0700864
Yifan Hong51d37562019-04-23 17:06:46 -0700865 AddCompatibilityArchive(system_updated or product_updated,
866 vendor_updated or odm_updated)
Tao Bao21803d32017-04-19 10:16:09 -0700867
868
Tianjie Xuf67dd802019-05-20 17:50:36 -0700869def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
870 device_specific):
871 """Returns a ordered dict of block differences with partition name as key."""
872
873 def GetIncrementalBlockDifferenceForPartition(name):
874 if not HasPartition(source_zip, name):
875 raise RuntimeError("can't generate incremental that adds {}".format(name))
876
877 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
878 info_dict=source_info,
879 allow_shared_blocks=allow_shared_blocks)
880
881 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
882 name, 4096, target_info)
883 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
884 info_dict=target_info,
885 allow_shared_blocks=allow_shared_blocks,
886 hashtree_info_generator=
887 hashtree_info_generator)
888
889 # Check the first block of the source system partition for remount R/W only
890 # if the filesystem is ext4.
891 partition_source_info = source_info["fstab"]["/" + name]
892 check_first_block = partition_source_info.fs_type == "ext4"
893 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
894 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
895 # b) the blocks listed in block map may not contain all the bytes for a
896 # given file (because they're rounded to be 4K-aligned).
897 partition_target_info = target_info["fstab"]["/" + name]
898 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
899 partition_target_info.fs_type == "squashfs")
900 return common.BlockDifference(name, partition_src, partition_tgt,
901 check_first_block,
902 version=blockimgdiff_version,
903 disable_imgdiff=disable_imgdiff)
904
905 if source_zip:
906 # See notes in common.GetUserImage()
907 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
908 target_info.get('ext4_share_dup_blocks') == "true")
909 blockimgdiff_version = max(
910 int(i) for i in target_info.get(
911 "blockimgdiff_versions", "1").split(","))
912 assert blockimgdiff_version >= 3
913
914 block_diff_dict = collections.OrderedDict()
915 partition_names = ["system", "vendor", "product", "odm", "system_ext"]
916 for partition in partition_names:
917 if not HasPartition(target_zip, partition):
918 continue
919 # Full OTA update.
920 if not source_zip:
921 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
922 info_dict=target_info,
923 reset_file_map=True)
924 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
925 src=None)
926 # Incremental OTA update.
927 else:
928 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
929 partition)
930 assert "system" in block_diff_dict
931
932 # Get the block diffs from the device specific script. If there is a
933 # duplicate block diff for a partition, ignore the diff in the generic script
934 # and use the one in the device specific script instead.
935 if source_zip:
936 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
937 function_name = "IncrementalOTA_GetBlockDifferences"
938 else:
939 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
940 function_name = "FullOTA_GetBlockDifferences"
941
942 if device_specific_diffs:
943 assert all(isinstance(diff, common.BlockDifference)
944 for diff in device_specific_diffs), \
945 "{} is not returning a list of BlockDifference objects".format(
946 function_name)
947 for diff in device_specific_diffs:
948 if diff.partition in block_diff_dict:
949 logger.warning("Duplicate block difference found. Device specific block"
950 " diff for partition '%s' overrides the one in generic"
951 " script.", diff.partition)
952 block_diff_dict[diff.partition] = diff
953
954 return block_diff_dict
955
956
Tao Bao491d7e22018-02-21 13:17:22 -0800957def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800958 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700959
Tao Bao481bab82017-12-21 11:23:09 -0800960 # We don't know what version it will be installed on top of. We expect the API
961 # just won't change very often. Similarly for fstab, it might have changed in
962 # the target build.
963 target_api_version = target_info["recovery_api_version"]
964 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700965
Tao Bao481bab82017-12-21 11:23:09 -0800966 if target_info.oem_props and not OPTIONS.oem_no_mount:
967 target_info.WriteMountOemScript(script)
968
Tao Baodf3a48b2018-01-10 16:30:43 -0800969 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700970
Tao Bao491d7e22018-02-21 13:17:22 -0800971 if not OPTIONS.no_signing:
972 staging_file = common.MakeTempFile(suffix='.zip')
973 else:
974 staging_file = output_file
975
976 output_zip = zipfile.ZipFile(
977 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
978
Doug Zongker05d3dea2009-06-22 11:32:31 -0700979 device_specific = common.DeviceSpecificParams(
980 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800981 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700982 output_zip=output_zip,
983 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700984 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700985 metadata=metadata,
986 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700987
Tao Bao457cbf62017-03-06 09:56:01 -0800988 assert HasRecoveryPatch(input_zip)
Doug Zongkerc9253822014-02-04 12:17:58 -0800989
Tao Bao481bab82017-12-21 11:23:09 -0800990 # Assertions (e.g. downgrade check, device properties check).
991 ts = target_info.GetBuildProp("ro.build.date.utc")
992 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700993 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700994
Tao Bao481bab82017-12-21 11:23:09 -0800995 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700996 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800997
Tianjie Xuf67dd802019-05-20 17:50:36 -0700998 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
999 target_info=target_info,
1000 source_info=None,
1001 device_specific=device_specific)
1002
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001003 # Two-step package strategy (in chronological order, which is *not*
1004 # the order in which the generated script has things):
1005 #
1006 # if stage is not "2/3" or "3/3":
1007 # write recovery image to boot partition
1008 # set stage to "2/3"
1009 # reboot to boot partition and restart recovery
1010 # else if stage is "2/3":
1011 # write recovery image to recovery partition
1012 # set stage to "3/3"
1013 # reboot to recovery partition and restart recovery
1014 # else:
1015 # (stage must be "3/3")
1016 # set stage to ""
1017 # do normal full package installation:
1018 # wipe and install system, boot image, etc.
1019 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001020 # complete script normally
1021 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001022
1023 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
1024 OPTIONS.input_tmp, "RECOVERY")
1025 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001026 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001027 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -08001028 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001029 assert fs.fs_type.upper() == "EMMC", \
1030 "two-step packages only supported on devices with EMMC /misc partitions"
1031 bcb_dev = {"bcb_dev": fs.device}
1032 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
1033 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001034if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001035""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001036
1037 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1038 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001039 script.WriteRawImage("/recovery", "recovery.img")
1040 script.AppendExtra("""
1041set_stage("%(bcb_dev)s", "3/3");
1042reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001043else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001044""" % bcb_dev)
1045
Tao Baod42e97e2016-11-30 12:11:57 -08001046 # Stage 3/3: Make changes.
1047 script.Comment("Stage 3/3")
1048
Tao Bao6c55a8a2015-04-08 15:30:27 -07001049 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001050 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001051
Doug Zongkere5ff5902012-01-17 10:55:37 -08001052 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -07001053
Tianjie Xuf67dd802019-05-20 17:50:36 -07001054 # All other partitions as well as the data wipe use 10% of the progress, and
1055 # the update of the system partition takes the remaining progress.
1056 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -07001057 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -08001058 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -07001059 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1060 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001061
Yifan Hong10c530d2018-12-27 17:34:18 -08001062 if target_info.get('use_dynamic_partitions') == "true":
1063 # Use empty source_info_dict to indicate that all partitions / groups must
1064 # be re-added.
1065 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1066 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001067 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001068 progress_dict=progress_dict)
1069 dynamic_partitions_diff.WriteScript(script, output_zip,
1070 write_verify_script=OPTIONS.verify)
1071 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001072 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001073 block_diff.WriteScript(script, output_zip,
1074 progress=progress_dict.get(block_diff.partition),
1075 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001076
Tao Bao481bab82017-12-21 11:23:09 -08001077 AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001078
Yifan Hong10c530d2018-12-27 17:34:18 -08001079 boot_img = common.GetBootableImage(
1080 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001081 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001082 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001083
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001084 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001085
Tianjie Xuf67dd802019-05-20 17:50:36 -07001086 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001087 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001088
Doug Zongker1c390a22009-05-14 19:06:36 -07001089 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001090 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001091
Doug Zongker14833602010-02-02 13:12:04 -08001092 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001093
Doug Zongker922206e2014-03-04 13:16:24 -08001094 if OPTIONS.wipe_user_data:
1095 script.ShowProgress(0.1, 10)
1096 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001097
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001098 if OPTIONS.two_step:
1099 script.AppendExtra("""
1100set_stage("%(bcb_dev)s", "");
1101""" % bcb_dev)
1102 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001103
1104 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1105 script.Comment("Stage 1/3")
1106 _WriteRecoveryImageToBoot(script, output_zip)
1107
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001108 script.AppendExtra("""
1109set_stage("%(bcb_dev)s", "2/3");
1110reboot_now("%(bcb_dev)s", "");
1111endif;
1112endif;
1113""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001114
Tao Bao5d182562016-02-23 11:38:39 -08001115 script.SetProgress(1)
1116 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001117 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001118
1119 # We haven't written the metadata entry, which will be done in
1120 # FinalizeMetadata.
1121 common.ZipClose(output_zip)
1122
1123 needed_property_files = (
1124 NonAbOtaPropertyFiles(),
1125 )
1126 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001127
Doug Zongkerfc44a512014-08-26 13:10:25 -07001128
xunchang1cfe2512019-02-19 14:14:48 -08001129def WriteMetadata(metadata, output):
1130 """Writes the metadata to the zip archive or a file.
1131
1132 Args:
1133 metadata: The metadata dict for the package.
1134 output: A ZipFile object or a string of the output file path.
1135 """
1136
Tao Bao59cf0c52019-06-25 10:04:24 -07001137 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001138 if isinstance(output, zipfile.ZipFile):
1139 common.ZipWriteStr(output, METADATA_NAME, value,
1140 compress_type=zipfile.ZIP_STORED)
1141 return
1142
1143 with open(output, 'w') as f:
1144 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001145
Doug Zongkerfc44a512014-08-26 13:10:25 -07001146
Tao Bao481bab82017-12-21 11:23:09 -08001147def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001148 # Only incremental OTAs are allowed to reach here.
1149 assert OPTIONS.incremental_source is not None
1150
Tao Bao481bab82017-12-21 11:23:09 -08001151 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1152 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001153 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001154
1155 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001156 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001157 raise RuntimeError(
1158 "--downgrade or --override_timestamp specified but no downgrade "
1159 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001160 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001161 else:
1162 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001163 raise RuntimeError(
1164 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1165 "Need to specify --override_timestamp OR --downgrade to allow "
1166 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001167
1168
Tao Baodf3a48b2018-01-10 16:30:43 -08001169def GetPackageMetadata(target_info, source_info=None):
1170 """Generates and returns the metadata dict.
1171
1172 It generates a dict() that contains the info to be written into an OTA
1173 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001174 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001175
1176 Args:
1177 target_info: The BuildInfo instance that holds the target build info.
1178 source_info: The BuildInfo instance that holds the source build info, or
1179 None if generating full OTA.
1180
1181 Returns:
1182 A dict to be written into package metadata entry.
1183 """
1184 assert isinstance(target_info, BuildInfo)
1185 assert source_info is None or isinstance(source_info, BuildInfo)
1186
1187 metadata = {
1188 'post-build' : target_info.fingerprint,
1189 'post-build-incremental' : target_info.GetBuildProp(
1190 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001191 'post-sdk-level' : target_info.GetBuildProp(
1192 'ro.build.version.sdk'),
1193 'post-security-patch-level' : target_info.GetBuildProp(
1194 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001195 }
1196
1197 if target_info.is_ab:
1198 metadata['ota-type'] = 'AB'
1199 metadata['ota-required-cache'] = '0'
1200 else:
1201 metadata['ota-type'] = 'BLOCK'
1202
1203 if OPTIONS.wipe_user_data:
1204 metadata['ota-wipe'] = 'yes'
1205
Tao Bao393eeb42019-03-06 16:00:38 -08001206 if OPTIONS.retrofit_dynamic_partitions:
1207 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1208
Tao Baodf3a48b2018-01-10 16:30:43 -08001209 is_incremental = source_info is not None
1210 if is_incremental:
1211 metadata['pre-build'] = source_info.fingerprint
1212 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1213 'ro.build.version.incremental')
1214 metadata['pre-device'] = source_info.device
1215 else:
1216 metadata['pre-device'] = target_info.device
1217
Tao Baofaa8e0b2018-04-12 14:31:43 -07001218 # Use the actual post-timestamp, even for a downgrade case.
1219 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1220
1221 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001222 if is_incremental:
1223 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001224
1225 return metadata
1226
1227
Tao Baod3fc38a2018-03-08 16:09:01 -08001228class PropertyFiles(object):
1229 """A class that computes the property-files string for an OTA package.
1230
1231 A property-files string is a comma-separated string that contains the
1232 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1233 can be fetched directly with the package URL along with the offset/size info.
1234 These strings can be used for streaming A/B OTAs, or allowing an updater to
1235 download package metadata entry directly, without paying the cost of
1236 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001237
Tao Baocc8e2662018-03-01 19:30:00 -08001238 Computing the final property-files string requires two passes. Because doing
1239 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1240 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1241 values.
1242
1243 This class provides functions to be called for each pass. The general flow is
1244 as follows.
1245
Tao Baod3fc38a2018-03-08 16:09:01 -08001246 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001247 # The first pass, which writes placeholders before doing initial signing.
1248 property_files.Compute()
1249 SignOutput()
1250
1251 # The second pass, by replacing the placeholders with actual data.
1252 property_files.Finalize()
1253 SignOutput()
1254
1255 And the caller can additionally verify the final result.
1256
1257 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001258 """
1259
Tao Baocc8e2662018-03-01 19:30:00 -08001260 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001261 self.name = None
1262 self.required = ()
1263 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001264
Tao Baocc8e2662018-03-01 19:30:00 -08001265 def Compute(self, input_zip):
1266 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001267
Tao Baocc8e2662018-03-01 19:30:00 -08001268 We reserve extra space for the offset and size of the metadata entry itself,
1269 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001270
Tao Baocc8e2662018-03-01 19:30:00 -08001271 Args:
1272 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001273
Tao Baocc8e2662018-03-01 19:30:00 -08001274 Returns:
1275 A string with placeholders for the metadata offset/size info, e.g.
1276 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1277 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001278 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001279
Tao Baod2ce2ed2018-03-16 12:59:42 -07001280 class InsufficientSpaceException(Exception):
1281 pass
1282
Tao Baocc8e2662018-03-01 19:30:00 -08001283 def Finalize(self, input_zip, reserved_length):
1284 """Finalizes a property-files string with actual METADATA offset/size info.
1285
1286 The input ZIP file has been signed, with the ZIP entries in the desired
1287 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1288 the ZIP entry offsets and construct the property-files string with actual
1289 data. Note that during this process, we must pad the property-files string
1290 to the reserved length, so that the METADATA entry size remains the same.
1291 Otherwise the entries' offsets and sizes may change again.
1292
1293 Args:
1294 input_zip: The input ZIP file.
1295 reserved_length: The reserved length of the property-files string during
1296 the call to Compute(). The final string must be no more than this
1297 size.
1298
1299 Returns:
1300 A property-files string including the metadata offset/size info, e.g.
1301 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1302
1303 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001304 InsufficientSpaceException: If the reserved length is insufficient to hold
1305 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001306 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001307 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001308 if len(result) > reserved_length:
1309 raise self.InsufficientSpaceException(
1310 'Insufficient reserved space: reserved={}, actual={}'.format(
1311 reserved_length, len(result)))
1312
Tao Baocc8e2662018-03-01 19:30:00 -08001313 result += ' ' * (reserved_length - len(result))
1314 return result
1315
1316 def Verify(self, input_zip, expected):
1317 """Verifies the input ZIP file contains the expected property-files string.
1318
1319 Args:
1320 input_zip: The input ZIP file.
1321 expected: The property-files string that's computed from Finalize().
1322
1323 Raises:
1324 AssertionError: On finding a mismatch.
1325 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001326 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001327 assert actual == expected, \
1328 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1329
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001330 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1331 """
1332 Constructs the property-files string per request.
1333
1334 Args:
1335 zip_file: The input ZIP file.
1336 reserved_length: The reserved length of the property-files string.
1337
1338 Returns:
1339 A property-files string including the metadata offset/size info, e.g.
1340 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1341 """
Tao Baocc8e2662018-03-01 19:30:00 -08001342
1343 def ComputeEntryOffsetSize(name):
1344 """Computes the zip entry offset and size."""
1345 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001346 offset = info.header_offset
1347 offset += zipfile.sizeFileHeader
1348 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001349 size = info.file_size
1350 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1351
1352 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001353 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001354 for entry in self.required:
1355 tokens.append(ComputeEntryOffsetSize(entry))
1356 for entry in self.optional:
1357 if entry in zip_file.namelist():
1358 tokens.append(ComputeEntryOffsetSize(entry))
1359
1360 # 'META-INF/com/android/metadata' is required. We don't know its actual
1361 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001362 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1363 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1364 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1365 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001366 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001367 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001368 else:
1369 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1370
1371 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001372
Tao Bao85f16982018-03-08 16:28:33 -08001373 def _GetPrecomputed(self, input_zip):
1374 """Computes the additional tokens to be included into the property-files.
1375
1376 This applies to tokens without actual ZIP entries, such as
1377 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1378 that they can download the payload metadata directly with the info.
1379
1380 Args:
1381 input_zip: The input zip file.
1382
1383 Returns:
1384 A list of strings (tokens) to be added to the property-files string.
1385 """
1386 # pylint: disable=no-self-use
1387 # pylint: disable=unused-argument
1388 return []
1389
Tao Baofe5b69a2018-03-02 09:47:43 -08001390
Tao Baod3fc38a2018-03-08 16:09:01 -08001391class StreamingPropertyFiles(PropertyFiles):
1392 """A subclass for computing the property-files for streaming A/B OTAs."""
1393
1394 def __init__(self):
1395 super(StreamingPropertyFiles, self).__init__()
1396 self.name = 'ota-streaming-property-files'
1397 self.required = (
1398 # payload.bin and payload_properties.txt must exist.
1399 'payload.bin',
1400 'payload_properties.txt',
1401 )
1402 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001403 # care_map is available only if dm-verity is enabled.
1404 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001405 'care_map.txt',
1406 # compatibility.zip is available only if target supports Treble.
1407 'compatibility.zip',
1408 )
1409
1410
Tao Bao85f16982018-03-08 16:28:33 -08001411class AbOtaPropertyFiles(StreamingPropertyFiles):
1412 """The property-files for A/B OTA that includes payload_metadata.bin info.
1413
1414 Since P, we expose one more token (aka property-file), in addition to the ones
1415 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1416 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1417 doesn't exist as a separate ZIP entry, but can be used to verify if the
1418 payload can be applied on the given device.
1419
1420 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1421 and the newly added 'ota-property-files' in P. The new token will only be
1422 available in 'ota-property-files'.
1423 """
1424
1425 def __init__(self):
1426 super(AbOtaPropertyFiles, self).__init__()
1427 self.name = 'ota-property-files'
1428
1429 def _GetPrecomputed(self, input_zip):
1430 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1431 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1432
1433 @staticmethod
1434 def _GetPayloadMetadataOffsetAndSize(input_zip):
1435 """Computes the offset and size of the payload metadata for a given package.
1436
1437 (From system/update_engine/update_metadata.proto)
1438 A delta update file contains all the deltas needed to update a system from
1439 one specific version to another specific version. The update format is
1440 represented by this struct pseudocode:
1441
1442 struct delta_update_file {
1443 char magic[4] = "CrAU";
1444 uint64 file_format_version;
1445 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1446
1447 // Only present if format_version > 1:
1448 uint32 metadata_signature_size;
1449
1450 // The Bzip2 compressed DeltaArchiveManifest
1451 char manifest[metadata_signature_size];
1452
1453 // The signature of the metadata (from the beginning of the payload up to
1454 // this location, not including the signature itself). This is a
1455 // serialized Signatures message.
1456 char medatada_signature_message[metadata_signature_size];
1457
1458 // Data blobs for files, no specific format. The specific offset
1459 // and length of each data blob is recorded in the DeltaArchiveManifest.
1460 struct {
1461 char data[];
1462 } blobs[];
1463
1464 // These two are not signed:
1465 uint64 payload_signatures_message_size;
1466 char payload_signatures_message[];
1467 };
1468
1469 'payload-metadata.bin' contains all the bytes from the beginning of the
1470 payload, till the end of 'medatada_signature_message'.
1471 """
1472 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001473 payload_offset = payload_info.header_offset
1474 payload_offset += zipfile.sizeFileHeader
1475 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001476 payload_size = payload_info.file_size
1477
Tao Bao59cf0c52019-06-25 10:04:24 -07001478 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001479 header_bin = payload_fp.read(24)
1480
1481 # network byte order (big-endian)
1482 header = struct.unpack("!IQQL", header_bin)
1483
1484 # 'CrAU'
1485 magic = header[0]
1486 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1487
1488 manifest_size = header[2]
1489 metadata_signature_size = header[3]
1490 metadata_total = 24 + manifest_size + metadata_signature_size
1491 assert metadata_total < payload_size
1492
1493 return (payload_offset, metadata_total)
1494
1495
Tao Bao491d7e22018-02-21 13:17:22 -08001496class NonAbOtaPropertyFiles(PropertyFiles):
1497 """The property-files for non-A/B OTA.
1498
1499 For non-A/B OTA, the property-files string contains the info for METADATA
1500 entry, with which a system updater can be fetched the package metadata prior
1501 to downloading the entire package.
1502 """
1503
1504 def __init__(self):
1505 super(NonAbOtaPropertyFiles, self).__init__()
1506 self.name = 'ota-property-files'
1507
1508
Tao Baod3fc38a2018-03-08 16:09:01 -08001509def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001510 """Finalizes the metadata and signs an A/B OTA package.
1511
1512 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1513 that contains the offsets and sizes for the ZIP entries. An example
1514 property-files string is as follows.
1515
1516 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1517
1518 OTA server can pass down this string, in addition to the package URL, to the
1519 system update client. System update client can then fetch individual ZIP
1520 entries (ZIP_STORED) directly at the given offset of the URL.
1521
1522 Args:
1523 metadata: The metadata dict for the package.
1524 input_file: The input ZIP filename that doesn't contain the package METADATA
1525 entry yet.
1526 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001527 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001528 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001529
Tao Baod2ce2ed2018-03-16 12:59:42 -07001530 def ComputeAllPropertyFiles(input_file, needed_property_files):
1531 # Write the current metadata entry with placeholders.
1532 with zipfile.ZipFile(input_file) as input_zip:
1533 for property_files in needed_property_files:
1534 metadata[property_files.name] = property_files.Compute(input_zip)
1535 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001536
Tao Baod2ce2ed2018-03-16 12:59:42 -07001537 if METADATA_NAME in namelist:
1538 common.ZipDelete(input_file, METADATA_NAME)
1539 output_zip = zipfile.ZipFile(input_file, 'a')
1540 WriteMetadata(metadata, output_zip)
1541 common.ZipClose(output_zip)
1542
1543 if OPTIONS.no_signing:
1544 return input_file
1545
Tao Bao491d7e22018-02-21 13:17:22 -08001546 prelim_signing = common.MakeTempFile(suffix='.zip')
1547 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001548 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001549
Tao Baod2ce2ed2018-03-16 12:59:42 -07001550 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1551 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1552 for property_files in needed_property_files:
1553 metadata[property_files.name] = property_files.Finalize(
1554 prelim_signing_zip, len(metadata[property_files.name]))
1555
1556 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1557 # entries, as well as padding the entry headers. We do a preliminary signing
1558 # (with an incomplete metadata entry) to allow that to happen. Then compute
1559 # the ZIP entry offsets, write back the final metadata and do the final
1560 # signing.
1561 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1562 try:
1563 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1564 except PropertyFiles.InsufficientSpaceException:
1565 # Even with the preliminary signing, the entry orders may change
1566 # dramatically, which leads to insufficiently reserved space during the
1567 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1568 # preliminary signing works, based on the already ordered ZIP entries, to
1569 # address the issue.
1570 prelim_signing = ComputeAllPropertyFiles(
1571 prelim_signing, needed_property_files)
1572 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001573
1574 # Replace the METADATA entry.
1575 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001576 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001577 WriteMetadata(metadata, output_zip)
1578 common.ZipClose(output_zip)
1579
1580 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001581 if OPTIONS.no_signing:
1582 output_file = prelim_signing
1583 else:
1584 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001585
1586 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001587 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001588 for property_files in needed_property_files:
1589 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001590
xunchang1cfe2512019-02-19 14:14:48 -08001591 # If requested, dump the metadata to a separate file.
1592 output_metadata_path = OPTIONS.output_metadata_path
1593 if output_metadata_path:
1594 WriteMetadata(metadata, output_metadata_path)
1595
Tao Baofe5b69a2018-03-02 09:47:43 -08001596
Tao Bao491d7e22018-02-21 13:17:22 -08001597def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001598 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1599 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001600
Tao Bao481bab82017-12-21 11:23:09 -08001601 target_api_version = target_info["recovery_api_version"]
1602 source_api_version = source_info["recovery_api_version"]
1603 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001604 logger.warning(
1605 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001606
Tao Bao481bab82017-12-21 11:23:09 -08001607 script = edify_generator.EdifyGenerator(
1608 source_api_version, target_info, fstab=source_info["fstab"])
1609
1610 if target_info.oem_props or source_info.oem_props:
1611 if not OPTIONS.oem_no_mount:
1612 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001613
Tao Baodf3a48b2018-01-10 16:30:43 -08001614 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001615
Tao Bao491d7e22018-02-21 13:17:22 -08001616 if not OPTIONS.no_signing:
1617 staging_file = common.MakeTempFile(suffix='.zip')
1618 else:
1619 staging_file = output_file
1620
1621 output_zip = zipfile.ZipFile(
1622 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1623
Geremy Condra36bd3652014-02-06 19:45:10 -08001624 device_specific = common.DeviceSpecificParams(
1625 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001626 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001627 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001628 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001629 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001630 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001631 output_zip=output_zip,
1632 script=script,
1633 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001634 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001635
Geremy Condra36bd3652014-02-06 19:45:10 -08001636 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001637 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001638 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001639 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001640 updating_boot = (not OPTIONS.two_step and
1641 (source_boot.data != target_boot.data))
1642
Geremy Condra36bd3652014-02-06 19:45:10 -08001643 target_recovery = common.GetBootableImage(
1644 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001645
Tianjie Xuf67dd802019-05-20 17:50:36 -07001646 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1647 source_zip=source_zip,
1648 target_info=target_info,
1649 source_info=source_info,
1650 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001651
Tao Baobcd1d162017-08-26 13:10:26 -07001652 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08001653 target_zip, output_zip, target_info, source_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001654
Tao Bao481bab82017-12-21 11:23:09 -08001655 # Assertions (e.g. device properties check).
1656 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001657 device_specific.IncrementalOTA_Assertions()
1658
1659 # Two-step incremental package strategy (in chronological order,
1660 # which is *not* the order in which the generated script has
1661 # things):
1662 #
1663 # if stage is not "2/3" or "3/3":
1664 # do verification on current system
1665 # write recovery image to boot partition
1666 # set stage to "2/3"
1667 # reboot to boot partition and restart recovery
1668 # else if stage is "2/3":
1669 # write recovery image to recovery partition
1670 # set stage to "3/3"
1671 # reboot to recovery partition and restart recovery
1672 # else:
1673 # (stage must be "3/3")
1674 # perform update:
1675 # patch system files, etc.
1676 # force full install of new boot image
1677 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001678 # complete script normally
1679 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001680
1681 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001682 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001683 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001684 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001685 assert fs.fs_type.upper() == "EMMC", \
1686 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001687 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001688 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1689 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001690if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001691""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001692
1693 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1694 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001695 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001696 script.WriteRawImage("/recovery", "recovery.img")
1697 script.AppendExtra("""
1698set_stage("%(bcb_dev)s", "3/3");
1699reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001700else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001701""" % bcb_dev)
1702
Tao Baod42e97e2016-11-30 12:11:57 -08001703 # Stage 1/3: (a) Verify the current system.
1704 script.Comment("Stage 1/3")
1705
Tao Bao6c55a8a2015-04-08 15:30:27 -07001706 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001707 script.Print("Source: {}".format(source_info.fingerprint))
1708 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001709
Geremy Condra36bd3652014-02-06 19:45:10 -08001710 script.Print("Verifying current system...")
1711
1712 device_specific.IncrementalOTA_VerifyBegin()
1713
Tao Bao481bab82017-12-21 11:23:09 -08001714 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001715
Tao Baod8d14be2016-02-04 14:26:02 -08001716 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001717 required_cache_sizes = [diff.required_cache for diff in
1718 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001719 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001720 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001721 d = common.Difference(target_boot, source_boot)
1722 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001723 if d is None:
1724 include_full_boot = True
1725 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1726 else:
1727 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001728
Tao Bao32fcdab2018-10-12 10:30:39 -07001729 logger.info(
1730 "boot target: %d source: %d diff: %d", target_boot.size,
1731 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001732
Tao Bao51216552018-08-26 11:53:15 -07001733 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001734
Tao Bao51216552018-08-26 11:53:15 -07001735 script.PatchPartitionCheck(
1736 "{}:{}:{}:{}".format(
1737 boot_type, boot_device, target_boot.size, target_boot.sha1),
1738 "{}:{}:{}:{}".format(
1739 boot_type, boot_device, source_boot.size, source_boot.sha1))
1740
Tianjie Xuf67dd802019-05-20 17:50:36 -07001741 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001742
Tianjie Xuf67dd802019-05-20 17:50:36 -07001743 if required_cache_sizes:
1744 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1745
1746 # Verify the existing partitions.
1747 for diff in block_diff_dict.values():
1748 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001749
1750 device_specific.IncrementalOTA_VerifyEnd()
1751
1752 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001753 # Stage 1/3: (b) Write recovery image to /boot.
1754 _WriteRecoveryImageToBoot(script, output_zip)
1755
Geremy Condra36bd3652014-02-06 19:45:10 -08001756 script.AppendExtra("""
1757set_stage("%(bcb_dev)s", "2/3");
1758reboot_now("%(bcb_dev)s", "");
1759else
1760""" % bcb_dev)
1761
Tao Baod42e97e2016-11-30 12:11:57 -08001762 # Stage 3/3: Make changes.
1763 script.Comment("Stage 3/3")
1764
Geremy Condra36bd3652014-02-06 19:45:10 -08001765 script.Comment("---- start making changes here ----")
1766
1767 device_specific.IncrementalOTA_InstallBegin()
1768
Tianjie Xuf67dd802019-05-20 17:50:36 -07001769 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1770 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001771
1772 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1773 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1774 raise RuntimeError(
1775 "can't generate incremental that disables dynamic partitions")
1776 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1777 info_dict=OPTIONS.target_info_dict,
1778 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001779 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001780 progress_dict=progress_dict)
1781 dynamic_partitions_diff.WriteScript(
1782 script, output_zip, write_verify_script=OPTIONS.verify)
1783 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001784 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001785 block_diff.WriteScript(script, output_zip,
1786 progress=progress_dict.get(block_diff.partition),
1787 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001788
1789 if OPTIONS.two_step:
1790 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1791 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001792 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001793
1794 if not OPTIONS.two_step:
1795 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001796 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001797 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001798 script.Print("Installing boot image...")
1799 script.WriteRawImage("/boot", "boot.img")
1800 else:
1801 # Produce the boot image by applying a patch to the current
1802 # contents of the boot partition, and write it back to the
1803 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001804 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001805 script.Print("Patching boot image...")
1806 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001807 script.PatchPartition(
1808 '{}:{}:{}:{}'.format(
1809 boot_type, boot_device, target_boot.size, target_boot.sha1),
1810 '{}:{}:{}:{}'.format(
1811 boot_type, boot_device, source_boot.size, source_boot.sha1),
1812 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001813 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001814 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001815
1816 # Do device-specific installation (eg, write radio image).
1817 device_specific.IncrementalOTA_InstallEnd()
1818
1819 if OPTIONS.extra_script is not None:
1820 script.AppendExtra(OPTIONS.extra_script)
1821
Doug Zongker922206e2014-03-04 13:16:24 -08001822 if OPTIONS.wipe_user_data:
1823 script.Print("Erasing user data...")
1824 script.FormatPartition("/data")
1825
Geremy Condra36bd3652014-02-06 19:45:10 -08001826 if OPTIONS.two_step:
1827 script.AppendExtra("""
1828set_stage("%(bcb_dev)s", "");
1829endif;
1830endif;
1831""" % bcb_dev)
1832
1833 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001834 # For downgrade OTAs, we prefer to use the update-binary in the source
1835 # build that is actually newer than the one in the target build.
1836 if OPTIONS.downgrade:
1837 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1838 else:
1839 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001840 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001841
1842 # We haven't written the metadata entry yet, which will be handled in
1843 # FinalizeMetadata().
1844 common.ZipClose(output_zip)
1845
1846 # Sign the generated zip package unless no_signing is specified.
1847 needed_property_files = (
1848 NonAbOtaPropertyFiles(),
1849 )
1850 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001851
Doug Zongker32b527d2014-03-04 10:03:02 -08001852
Tao Bao15a146a2018-02-21 16:06:59 -08001853def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001854 """Returns a target-files.zip file for generating secondary payload.
1855
1856 Although the original target-files.zip already contains secondary slot
1857 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1858 ones without _other suffix. Note that we cannot instead modify the names in
1859 META/ab_partitions.txt, because there are no matching partitions on device.
1860
1861 For the partitions that don't have secondary images, the ones for primary
1862 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1863 bootloader images in the inactive slot.
1864
1865 Args:
1866 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001867 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001868
1869 Returns:
1870 The filename of the target-files.zip for generating secondary payload.
1871 """
1872 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1873 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1874
Tao Baodba59ee2018-01-09 13:21:02 -08001875 with zipfile.ZipFile(input_file, 'r') as input_zip:
1876 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001877
Tao Bao0ff15de2019-03-20 11:26:06 -07001878 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001879 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001880 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1881 if info.filename == 'IMAGES/system_other.img':
1882 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1883
1884 # Primary images and friends need to be skipped explicitly.
1885 elif info.filename in ('IMAGES/system.img',
1886 'IMAGES/system.map'):
1887 pass
1888
Tao Bao15a146a2018-02-21 16:06:59 -08001889 # Skip copying the postinstall config if requested.
1890 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1891 pass
1892
Tao Bao12489802018-07-12 14:47:38 -07001893 elif info.filename.startswith(('META/', 'IMAGES/', 'RADIO/')):
Tao Baof7140c02018-01-30 17:09:24 -08001894 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
1895
Tao Baof7140c02018-01-30 17:09:24 -08001896 common.ZipClose(target_zip)
1897
1898 return target_file
1899
1900
Tao Bao15a146a2018-02-21 16:06:59 -08001901def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1902 """Returns a target-files.zip that's not containing postinstall_config.txt.
1903
1904 This allows brillo_update_payload script to skip writing all the postinstall
1905 hooks in the generated payload. The input target-files.zip file will be
1906 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1907 contain the postinstall_config.txt entry, the input file will be returned.
1908
1909 Args:
1910 input_file: The input target-files.zip filename.
1911
1912 Returns:
1913 The filename of target-files.zip that doesn't contain postinstall config.
1914 """
1915 # We should only make a copy if postinstall_config entry exists.
1916 with zipfile.ZipFile(input_file, 'r') as input_zip:
1917 if POSTINSTALL_CONFIG not in input_zip.namelist():
1918 return input_file
1919
1920 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1921 shutil.copyfile(input_file, target_file)
1922 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1923 return target_file
1924
1925
Yifan Hong50e79542018-11-08 17:44:12 -08001926def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001927 super_block_devices,
1928 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001929 """Returns a target-files.zip for retrofitting dynamic partitions.
1930
1931 This allows brillo_update_payload to generate an OTA based on the exact
1932 bits on the block devices. Postinstall is disabled.
1933
1934 Args:
1935 input_file: The input target-files.zip filename.
1936 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001937 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001938
1939 Returns:
1940 The filename of target-files.zip with *.img replaced with super_*.img for
1941 each block device in super_block_devices.
1942 """
1943 assert super_block_devices, "No super_block_devices are specified."
1944
1945 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001946 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001947
1948 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1949 shutil.copyfile(input_file, target_file)
1950
Tao Baoa3705452019-06-24 15:33:41 -07001951 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001952 namelist = input_zip.namelist()
1953
Yifan Hongb433eba2019-03-06 12:42:53 -08001954 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1955
1956 # Remove partitions from META/ab_partitions.txt that is in
1957 # dynamic_partition_list but not in super_block_devices so that
1958 # brillo_update_payload won't generate update for those logical partitions.
1959 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1960 with open(ab_partitions_file) as f:
1961 ab_partitions_lines = f.readlines()
1962 ab_partitions = [line.strip() for line in ab_partitions_lines]
1963 # Assert that all super_block_devices are in ab_partitions
1964 super_device_not_updated = [partition for partition in super_block_devices
1965 if partition not in ab_partitions]
1966 assert not super_device_not_updated, \
1967 "{} is in super_block_devices but not in {}".format(
1968 super_device_not_updated, AB_PARTITIONS)
1969 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1970 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1971 with open(new_ab_partitions, 'w') as f:
1972 for partition in ab_partitions:
1973 if (partition in dynamic_partition_list and
1974 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001975 logger.info("Dropping %s from ab_partitions.txt", partition)
1976 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001977 f.write(partition + "\n")
1978 to_delete = [AB_PARTITIONS]
1979
Yifan Hong50e79542018-11-08 17:44:12 -08001980 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001981 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001982
1983 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1984 # is a regular update on devices without dynamic partitions support.
1985 to_delete += [DYNAMIC_PARTITION_INFO]
1986
Tao Bao03fecb62018-11-28 10:59:23 -08001987 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001988 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001989 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001990
1991 common.ZipDelete(target_file, to_delete)
1992
Yifan Hong50e79542018-11-08 17:44:12 -08001993 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1994
1995 # Write super_{foo}.img as {foo}.img.
1996 for src, dst in replace.items():
1997 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001998 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001999 unzipped_file = os.path.join(input_tmp, *src.split('/'))
2000 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
2001
Yifan Hongb433eba2019-03-06 12:42:53 -08002002 # Write new ab_partitions.txt file
2003 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
2004
Yifan Hong50e79542018-11-08 17:44:12 -08002005 common.ZipClose(target_zip)
2006
2007 return target_file
2008
2009
Tao Baof0c4aa22018-04-30 20:29:30 -07002010def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002011 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002012 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002013 if not OPTIONS.no_signing:
2014 staging_file = common.MakeTempFile(suffix='.zip')
2015 else:
2016 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002017 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002018 compression=zipfile.ZIP_DEFLATED)
2019
Tao Bao481bab82017-12-21 11:23:09 -08002020 if source_file is not None:
2021 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2022 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2023 else:
2024 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2025 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002026
Tao Bao481bab82017-12-21 11:23:09 -08002027 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002028 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002029
Yifan Hong50e79542018-11-08 17:44:12 -08002030 if OPTIONS.retrofit_dynamic_partitions:
2031 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002032 target_file, target_info.get("super_block_devices").strip().split(),
2033 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002034 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002035 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2036
Tao Bao40b18822018-01-30 18:19:04 -08002037 # Generate payload.
2038 payload = Payload()
2039
2040 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002041 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002042 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002043 else:
2044 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002045 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002046
Tao Bao40b18822018-01-30 18:19:04 -08002047 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002048
Tao Bao40b18822018-01-30 18:19:04 -08002049 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002050 payload_signer = PayloadSigner()
2051 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002052
Tao Bao40b18822018-01-30 18:19:04 -08002053 # Write the payload into output zip.
2054 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002055
Tao Baof7140c02018-01-30 17:09:24 -08002056 # Generate and include the secondary payload that installs secondary images
2057 # (e.g. system_other.img).
2058 if OPTIONS.include_secondary:
2059 # We always include a full payload for the secondary slot, even when
2060 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002061 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2062 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002063 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002064 secondary_payload.Generate(secondary_target_file,
2065 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002066 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002067 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002068
Tianjie Xucfa86222016-03-07 16:31:19 -08002069 # If dm-verity is supported for the device, copy contents of care_map
2070 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002071 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002072 if (target_info.get("verity") == "true" or
2073 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002074 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2075 "META/" + x in target_zip.namelist()]
2076
2077 # Adds care_map if either the protobuf format or the plain text one exists.
2078 if care_map_list:
2079 care_map_name = care_map_list[0]
2080 care_map_data = target_zip.read("META/" + care_map_name)
2081 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002082 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002083 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002084 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002085 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002086 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002087
Tao Baobcd1d162017-08-26 13:10:26 -07002088 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08002089 target_zip, output_zip, target_info, source_info)
Tao Bao21803d32017-04-19 10:16:09 -07002090
Tao Bao21803d32017-04-19 10:16:09 -07002091 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08002092
Tao Baofe5b69a2018-03-02 09:47:43 -08002093 # We haven't written the metadata entry yet, which will be handled in
2094 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002095 common.ZipClose(output_zip)
2096
Tao Bao85f16982018-03-08 16:28:33 -08002097 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2098 # all the info of the latter. However, system updaters and OTA servers need to
2099 # take time to switch to the new flag. We keep both of the flags for
2100 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002101 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002102 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002103 StreamingPropertyFiles(),
2104 )
2105 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002106
Tao Baoc098e9e2016-01-07 13:03:56 -08002107
Tao Baof0c4aa22018-04-30 20:29:30 -07002108def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
2109 """Generates a non-A/B OTA package."""
2110 # Sanity check the loaded info dicts first.
2111 if OPTIONS.info_dict.get("no_recovery") == "true":
2112 raise common.ExternalError(
2113 "--- target build has specified no recovery ---")
2114
2115 # Non-A/B OTAs rely on /cache partition to store temporary files.
2116 cache_size = OPTIONS.info_dict.get("cache_size")
2117 if cache_size is None:
2118 logger.warning("--- can't determine the cache partition size ---")
2119 OPTIONS.cache_size = cache_size
2120
2121 if OPTIONS.extra_script is not None:
2122 with open(OPTIONS.extra_script) as fp:
2123 OPTIONS.extra_script = fp.read()
2124
2125 if OPTIONS.extracted_input is not None:
2126 OPTIONS.input_tmp = OPTIONS.extracted_input
2127 else:
2128 logger.info("unzipping target target-files...")
2129 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
2130 OPTIONS.target_tmp = OPTIONS.input_tmp
2131
2132 # If the caller explicitly specified the device-specific extensions path via
2133 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2134 # is present in the target target_files. Otherwise, take the path of the file
2135 # from 'tool_extensions' in the info dict and look for that in the local
2136 # filesystem, relative to the current directory.
2137 if OPTIONS.device_specific is None:
2138 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2139 if os.path.exists(from_input):
2140 logger.info("(using device-specific extensions from target_files)")
2141 OPTIONS.device_specific = from_input
2142 else:
2143 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
2144
2145 if OPTIONS.device_specific is not None:
2146 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
2147
2148 # Generate a full OTA.
2149 if source_file is None:
2150 with zipfile.ZipFile(target_file) as input_zip:
2151 WriteFullOTAPackage(
2152 input_zip,
2153 output_file)
2154
2155 # Generate an incremental OTA.
2156 else:
2157 logger.info("unzipping source target-files...")
2158 OPTIONS.source_tmp = common.UnzipTemp(
2159 OPTIONS.incremental_source, UNZIP_PATTERN)
2160 with zipfile.ZipFile(target_file) as input_zip, \
2161 zipfile.ZipFile(source_file) as source_zip:
2162 WriteBlockIncrementalOTAPackage(
2163 input_zip,
2164 source_zip,
2165 output_file)
2166
2167
Doug Zongkereef39442009-04-02 12:14:19 -07002168def main(argv):
2169
2170 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002171 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002172 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002173 elif o in ("-i", "--incremental_from"):
2174 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002175 elif o == "--full_radio":
2176 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002177 elif o == "--full_bootloader":
2178 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002179 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002180 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002181 elif o == "--downgrade":
2182 OPTIONS.downgrade = True
2183 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002184 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002185 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002186 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002187 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002188 elif o == "--oem_no_mount":
2189 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002190 elif o in ("-e", "--extra_script"):
2191 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002192 elif o in ("-t", "--worker_threads"):
2193 if a.isdigit():
2194 OPTIONS.worker_threads = int(a)
2195 else:
2196 raise ValueError("Cannot parse value %r for option %r - only "
2197 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002198 elif o in ("-2", "--two_step"):
2199 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002200 elif o == "--include_secondary":
2201 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002202 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002203 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002204 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002205 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002206 elif o == "--block":
2207 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002208 elif o in ("-b", "--binary"):
2209 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002210 elif o == "--stash_threshold":
2211 try:
2212 OPTIONS.stash_threshold = float(a)
2213 except ValueError:
2214 raise ValueError("Cannot parse value %r for option %r - expecting "
2215 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002216 elif o == "--log_diff":
2217 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002218 elif o == "--payload_signer":
2219 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002220 elif o == "--payload_signer_args":
2221 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002222 elif o == "--payload_signer_key_size":
2223 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002224 elif o == "--extracted_input_target_files":
2225 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002226 elif o == "--skip_postinstall":
2227 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002228 elif o == "--retrofit_dynamic_partitions":
2229 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002230 elif o == "--skip_compatibility_check":
2231 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002232 elif o == "--output_metadata_path":
2233 OPTIONS.output_metadata_path = a
Tianjie Xu1b079832019-08-28 12:19:23 -07002234 elif o == "--disable_fec_computation":
2235 OPTIONS.disable_fec_computation = True
Doug Zongkereef39442009-04-02 12:14:19 -07002236 else:
2237 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002238 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002239
2240 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002241 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002242 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002243 "package_key=",
2244 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002245 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002246 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002247 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002248 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002249 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002250 "extra_script=",
2251 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002252 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002253 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002254 "no_signing",
2255 "block",
2256 "binary=",
2257 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002258 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002259 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002260 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002261 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002262 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002263 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002264 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002265 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002266 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002267 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002268 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002269 "output_metadata_path=",
Tianjie Xu1b079832019-08-28 12:19:23 -07002270 "disable_fec_computation",
Dan Albert8b72aef2015-03-23 19:13:21 -07002271 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002272
2273 if len(args) != 2:
2274 common.Usage(__doc__)
2275 sys.exit(1)
2276
Tao Bao32fcdab2018-10-12 10:30:39 -07002277 common.InitLogging()
2278
Tao Bao5d182562016-02-23 11:38:39 -08002279 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002280 # We should only allow downgrading incrementals (as opposed to full).
2281 # Otherwise the device may go back from arbitrary build with this full
2282 # OTA package.
2283 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002284 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002285
Tao Bao2db13852018-01-08 22:28:57 -08002286 # Load the build info dicts from the zip directly or the extracted input
2287 # directory. We don't need to unzip the entire target-files zips, because they
2288 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2289 # When loading the info dicts, we don't need to provide the second parameter
2290 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2291 # some properties with their actual paths, such as 'selinux_fc',
2292 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002293 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002294 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002295 else:
Tao Bao2db13852018-01-08 22:28:57 -08002296 with zipfile.ZipFile(args[0], 'r') as input_zip:
2297 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002298
Tao Bao32fcdab2018-10-12 10:30:39 -07002299 logger.info("--- target info ---")
2300 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002301
2302 # Load the source build dict if applicable.
2303 if OPTIONS.incremental_source is not None:
2304 OPTIONS.target_info_dict = OPTIONS.info_dict
2305 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2306 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2307
Tao Bao32fcdab2018-10-12 10:30:39 -07002308 logger.info("--- source info ---")
2309 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002310
2311 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002312 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2313
Yifan Hong50e79542018-11-08 17:44:12 -08002314 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002315 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002316 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002317 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2318 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002319 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2320 raise common.ExternalError(
2321 "Expect to generate incremental OTA for retrofitting dynamic "
2322 "partitions, but dynamic_partition_retrofit is not set in target "
2323 "build.")
2324 logger.info("Implicitly generating retrofit incremental OTA.")
2325 OPTIONS.retrofit_dynamic_partitions = True
2326
2327 # Skip postinstall for retrofitting dynamic partitions.
2328 if OPTIONS.retrofit_dynamic_partitions:
2329 OPTIONS.skip_postinstall = True
2330
Tao Baoc098e9e2016-01-07 13:03:56 -08002331 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2332
Christian Oderf63e2cd2017-05-01 22:30:15 +02002333 # Use the default key to sign the package if not specified with package_key.
2334 # package_keys are needed on ab_updates, so always define them if an
2335 # ab_update is getting created.
2336 if not OPTIONS.no_signing or ab_update:
2337 if OPTIONS.package_key is None:
2338 OPTIONS.package_key = OPTIONS.info_dict.get(
2339 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002340 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002341 # Get signing keys
2342 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2343
Tao Baoc098e9e2016-01-07 13:03:56 -08002344 if ab_update:
Tao Baof0c4aa22018-04-30 20:29:30 -07002345 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002346 target_file=args[0],
2347 output_file=args[1],
2348 source_file=OPTIONS.incremental_source)
2349
Dan Willemsencea5cd22017-03-21 14:44:27 -07002350 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002351 GenerateNonAbOtaPackage(
2352 target_file=args[0],
2353 output_file=args[1],
2354 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002355
Tao Baof0c4aa22018-04-30 20:29:30 -07002356 # Post OTA generation works.
2357 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2358 logger.info("Generating diff logs...")
2359 logger.info("Unzipping target-files for diffing...")
2360 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2361 source_dir = common.UnzipTemp(
2362 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002363
Tao Baof0c4aa22018-04-30 20:29:30 -07002364 with open(OPTIONS.log_diff, 'w') as out_file:
2365 import target_files_diff
2366 target_files_diff.recursiveDiff(
2367 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002368
Tao Bao32fcdab2018-10-12 10:30:39 -07002369 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002370
2371
2372if __name__ == '__main__':
2373 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002374 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002375 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002376 except common.ExternalError:
2377 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002378 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002379 finally:
2380 common.Cleanup()