blob: 3fc3c224914c485c509c08206c08d94a5a8bc538 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
Tao Bao30df8b42018-04-23 15:32:53 -070018Given a target-files zipfile, produces an OTA package that installs that build.
19An incremental OTA is produced if -i is given, otherwise a full OTA is produced.
Doug Zongkereef39442009-04-02 12:14:19 -070020
Tao Bao30df8b42018-04-23 15:32:53 -070021Usage: ota_from_target_files [options] input_target_files output_ota_package
Doug Zongkereef39442009-04-02 12:14:19 -070022
Tao Bao30df8b42018-04-23 15:32:53 -070023Common options that apply to both of non-A/B and A/B OTAs
24
25 --downgrade
26 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070027 to an older one (e.g. downgrading from P preview back to O MR1).
28 "ota-downgrade=yes" will be set in the package metadata file. A data wipe
29 will always be enforced when using this flag, so "ota-wipe=yes" will also
30 be included in the metadata file. The update-binary in the source build
31 will be used in the OTA package, unless --binary flag is specified. Please
32 also check the comment for --override_timestamp below.
Tao Bao30df8b42018-04-23 15:32:53 -070033
34 -i (--incremental_from) <file>
35 Generate an incremental OTA using the given target-files zip as the
36 starting build.
37
38 -k (--package_key) <key>
39 Key to use to sign the package (default is the value of
40 default_system_dev_certificate from the input target-files's
Tao Bao59cf0c52019-06-25 10:04:24 -070041 META/misc_info.txt, or "build/make/target/product/security/testkey" if
42 that value is not specified).
Doug Zongkerafb32ea2011-09-22 10:28:04 -070043
44 For incremental OTAs, the default value is based on the source
45 target-file, not the target build.
Doug Zongkereef39442009-04-02 12:14:19 -070046
Tao Bao30df8b42018-04-23 15:32:53 -070047 --override_timestamp
48 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070049 to an older one (based on timestamp comparison), by setting the downgrade
50 flag in the package metadata. This differs from --downgrade flag, as we
51 don't enforce a data wipe with this flag. Because we know for sure this is
52 NOT an actual downgrade case, but two builds happen to be cut in a reverse
53 order (e.g. from two branches). A legit use case is that we cut a new
54 build C (after having A and B), but want to enfore an update path of A ->
55 C -> B. Specifying --downgrade may not help since that would enforce a
56 data wipe for C -> B update.
57
58 We used to set a fake timestamp in the package metadata for this flow. But
59 now we consolidate the two cases (i.e. an actual downgrade, or a downgrade
60 based on timestamp) with the same "ota-downgrade=yes" flag, with the
61 difference being whether "ota-wipe=yes" is set.
Doug Zongkereef39442009-04-02 12:14:19 -070062
Tao Bao30df8b42018-04-23 15:32:53 -070063 --wipe_user_data
64 Generate an OTA package that will wipe the user data partition when
65 installed.
66
Yifan Hong50e79542018-11-08 17:44:12 -080067 --retrofit_dynamic_partitions
68 Generates an OTA package that updates a device to support dynamic
69 partitions (default False). This flag is implied when generating
70 an incremental OTA where the base build does not support dynamic
71 partitions but the target build does. For A/B, when this flag is set,
72 --skip_postinstall is implied.
73
xunchangabfa2652019-02-19 16:27:10 -080074 --skip_compatibility_check
Yifan Hong9276cf02019-08-21 16:37:04 -070075 Skip checking compatibility of the input target files package.
xunchangabfa2652019-02-19 16:27:10 -080076
xunchang1cfe2512019-02-19 14:14:48 -080077 --output_metadata_path
78 Write a copy of the metadata to a separate file. Therefore, users can
79 read the post build fingerprint without extracting the OTA package.
80
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 zipfile
196
Yifan Hong9276cf02019-08-21 16:37:04 -0700197import check_target_files_vintf
Doug Zongkereef39442009-04-02 12:14:19 -0700198import 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 Bao3e759462019-09-17 22:43:11 -0700253
254# Images to be excluded from secondary payload. We essentially only keep
255# 'system_other' and bootloader partitions.
256SECONDARY_PAYLOAD_SKIPPED_IMAGES = [
257 'boot', 'dtbo', 'modem', 'odm', 'product', 'radio', 'recovery',
258 'system_ext', 'vbmeta', 'vbmeta_system', 'vbmeta_vendor', 'vendor']
Tao Bao6b0b2f92017-03-05 11:38:11 -0800259
Tao Bao2dd1c482017-02-03 16:49:39 -0800260
Tao Bao481bab82017-12-21 11:23:09 -0800261class BuildInfo(object):
262 """A class that holds the information for a given build.
263
264 This class wraps up the property querying for a given source or target build.
265 It abstracts away the logic of handling OEM-specific properties, and caches
266 the commonly used properties such as fingerprint.
267
268 There are two types of info dicts: a) build-time info dict, which is generated
269 at build time (i.e. included in a target_files zip); b) OEM info dict that is
270 specified at package generation time (via command line argument
271 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
272 having "oem_fingerprint_properties" in build-time info dict), all the queries
273 would be answered based on build-time info dict only. Otherwise if using
274 OEM-specific properties, some of them will be calculated from two info dicts.
275
276 Users can query properties similarly as using a dict() (e.g. info['fstab']),
277 or to query build properties via GetBuildProp() or GetVendorBuildProp().
278
279 Attributes:
280 info_dict: The build-time info dict.
281 is_ab: Whether it's a build that uses A/B OTA.
282 oem_dicts: A list of OEM dicts.
283 oem_props: A list of OEM properties that should be read from OEM dicts; None
284 if the build doesn't use any OEM-specific property.
285 fingerprint: The fingerprint of the build, which would be calculated based
286 on OEM properties if applicable.
287 device: The device name, which could come from OEM dicts if applicable.
288 """
289
Steven Laver9e73e822019-01-29 20:20:08 -0800290 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
291 "ro.product.manufacturer", "ro.product.model",
292 "ro.product.name"]
Justin Yun6151e3f2019-06-25 15:58:13 +0900293 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
294 "system_ext", "system"]
Steven Laver9e73e822019-01-29 20:20:08 -0800295
Tao Bao481bab82017-12-21 11:23:09 -0800296 def __init__(self, info_dict, oem_dicts):
297 """Initializes a BuildInfo instance with the given dicts.
298
Tao Bao667c7532018-07-06 10:13:59 -0700299 Note that it only wraps up the given dicts, without making copies.
300
Tao Bao481bab82017-12-21 11:23:09 -0800301 Arguments:
302 info_dict: The build-time info dict.
303 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
304 that it always uses the first dict to calculate the fingerprint or the
305 device name. The rest would be used for asserting OEM properties only
Tao Bao667c7532018-07-06 10:13:59 -0700306 (e.g. one package can be installed on one of these devices).
Tao Baoc4011cd72019-09-17 00:14:44 -0700307
308 Raises:
309 ValueError: On invalid inputs.
Tao Bao481bab82017-12-21 11:23:09 -0800310 """
311 self.info_dict = info_dict
312 self.oem_dicts = oem_dicts
313
314 self._is_ab = info_dict.get("ab_update") == "true"
315 self._oem_props = info_dict.get("oem_fingerprint_properties")
316
317 if self._oem_props:
318 assert oem_dicts, "OEM source required for this build"
319
320 # These two should be computed only after setting self._oem_props.
321 self._device = self.GetOemProperty("ro.product.device")
322 self._fingerprint = self.CalculateFingerprint()
323
Tao Baoc4011cd72019-09-17 00:14:44 -0700324 # Sanity check the build fingerprint.
325 if (' ' in self._fingerprint or
326 any(ord(ch) > 127 for ch in self._fingerprint)):
327 raise ValueError(
328 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
329 '3.2.2. Build Parameters.'.format(self._fingerprint))
330
Tao Bao481bab82017-12-21 11:23:09 -0800331 @property
332 def is_ab(self):
333 return self._is_ab
334
335 @property
336 def device(self):
337 return self._device
338
339 @property
340 def fingerprint(self):
341 return self._fingerprint
342
343 @property
Tao Baoea6cbd02018-09-05 13:06:37 -0700344 def vendor_fingerprint(self):
Yifan Hong51d37562019-04-23 17:06:46 -0700345 return self._fingerprint_of("vendor")
346
347 @property
348 def product_fingerprint(self):
349 return self._fingerprint_of("product")
350
351 @property
352 def odm_fingerprint(self):
353 return self._fingerprint_of("odm")
354
355 def _fingerprint_of(self, partition):
356 if partition + ".build.prop" not in self.info_dict:
Tao Baoea6cbd02018-09-05 13:06:37 -0700357 return None
Yifan Hong51d37562019-04-23 17:06:46 -0700358 build_prop = self.info_dict[partition + ".build.prop"]
359 if "ro." + partition + ".build.fingerprint" in build_prop:
360 return build_prop["ro." + partition + ".build.fingerprint"]
361 if "ro." + partition + ".build.thumbprint" in build_prop:
362 return build_prop["ro." + partition + ".build.thumbprint"]
Tao Baoea6cbd02018-09-05 13:06:37 -0700363 return None
364
365 @property
Tao Bao481bab82017-12-21 11:23:09 -0800366 def oem_props(self):
367 return self._oem_props
368
369 def __getitem__(self, key):
370 return self.info_dict[key]
371
Tao Bao667c7532018-07-06 10:13:59 -0700372 def __setitem__(self, key, value):
373 self.info_dict[key] = value
374
Tao Bao481bab82017-12-21 11:23:09 -0800375 def get(self, key, default=None):
376 return self.info_dict.get(key, default)
377
Tao Bao667c7532018-07-06 10:13:59 -0700378 def items(self):
379 return self.info_dict.items()
380
Tao Bao481bab82017-12-21 11:23:09 -0800381 def GetBuildProp(self, prop):
382 """Returns the inquired build property."""
Steven Laver9e73e822019-01-29 20:20:08 -0800383 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
384 return self._ResolveRoProductBuildProp(prop)
385
Tao Bao481bab82017-12-21 11:23:09 -0800386 try:
387 return self.info_dict.get("build.prop", {})[prop]
388 except KeyError:
389 raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
390
Steven Laver9e73e822019-01-29 20:20:08 -0800391 def _ResolveRoProductBuildProp(self, prop):
392 """Resolves the inquired ro.product.* build property"""
393 prop_val = self.info_dict.get("build.prop", {}).get(prop)
394 if prop_val:
395 return prop_val
396
397 source_order_val = self.info_dict.get("build.prop", {}).get(
Tao Bao59cf0c52019-06-25 10:04:24 -0700398 "ro.product.property_source_order")
Steven Laver9e73e822019-01-29 20:20:08 -0800399 if source_order_val:
400 source_order = source_order_val.split(",")
401 else:
402 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
403
404 # Check that all sources in ro.product.property_source_order are valid
405 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
406 for x in source_order]):
407 raise common.ExternalError(
Tao Bao59cf0c52019-06-25 10:04:24 -0700408 "Invalid ro.product.property_source_order '{}'".format(source_order))
Steven Laver9e73e822019-01-29 20:20:08 -0800409
410 for source in source_order:
Tao Bao59cf0c52019-06-25 10:04:24 -0700411 source_prop = prop.replace(
412 "ro.product", "ro.product.{}".format(source), 1)
413 prop_val = self.info_dict.get(
414 "{}.build.prop".format(source), {}).get(source_prop)
Steven Laver9e73e822019-01-29 20:20:08 -0800415 if prop_val:
416 return prop_val
417
418 raise common.ExternalError("couldn't resolve {}".format(prop))
419
Tao Bao481bab82017-12-21 11:23:09 -0800420 def GetVendorBuildProp(self, prop):
421 """Returns the inquired vendor build property."""
422 try:
423 return self.info_dict.get("vendor.build.prop", {})[prop]
424 except KeyError:
425 raise common.ExternalError(
426 "couldn't find %s in vendor.build.prop" % (prop,))
427
428 def GetOemProperty(self, key):
429 if self.oem_props is not None and key in self.oem_props:
430 return self.oem_dicts[0][key]
431 return self.GetBuildProp(key)
432
433 def CalculateFingerprint(self):
434 if self.oem_props is None:
Steven Laver9e73e822019-01-29 20:20:08 -0800435 try:
436 return self.GetBuildProp("ro.build.fingerprint")
437 except common.ExternalError:
438 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
Tao Bao59cf0c52019-06-25 10:04:24 -0700439 self.GetBuildProp("ro.product.brand"),
440 self.GetBuildProp("ro.product.name"),
441 self.GetBuildProp("ro.product.device"),
442 self.GetBuildProp("ro.build.version.release"),
443 self.GetBuildProp("ro.build.id"),
444 self.GetBuildProp("ro.build.version.incremental"),
445 self.GetBuildProp("ro.build.type"),
446 self.GetBuildProp("ro.build.tags"))
Tao Bao481bab82017-12-21 11:23:09 -0800447 return "%s/%s/%s:%s" % (
448 self.GetOemProperty("ro.product.brand"),
449 self.GetOemProperty("ro.product.name"),
450 self.GetOemProperty("ro.product.device"),
451 self.GetBuildProp("ro.build.thumbprint"))
452
453 def WriteMountOemScript(self, script):
454 assert self.oem_props is not None
455 recovery_mount_options = self.info_dict.get("recovery_mount_options")
456 script.Mount("/oem", recovery_mount_options)
457
458 def WriteDeviceAssertions(self, script, oem_no_mount):
459 # Read the property directly if not using OEM properties.
460 if not self.oem_props:
461 script.AssertDevice(self.device)
462 return
463
464 # Otherwise assert OEM properties.
465 if not self.oem_dicts:
466 raise common.ExternalError(
467 "No OEM file provided to answer expected assertions")
468
469 for prop in self.oem_props.split():
470 values = []
471 for oem_dict in self.oem_dicts:
472 if prop in oem_dict:
473 values.append(oem_dict[prop])
474 if not values:
475 raise common.ExternalError(
476 "The OEM file is missing the property %s" % (prop,))
477 script.AssertOemProperty(prop, values, oem_no_mount)
478
479
Tao Baofabe0832018-01-17 15:52:28 -0800480class PayloadSigner(object):
481 """A class that wraps the payload signing works.
482
483 When generating a Payload, hashes of the payload and metadata files will be
484 signed with the device key, either by calling an external payload signer or
485 by calling openssl with the package key. This class provides a unified
486 interface, so that callers can just call PayloadSigner.Sign().
487
488 If an external payload signer has been specified (OPTIONS.payload_signer), it
489 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
490 that the signing key should be provided as part of the payload_signer_args.
491 Otherwise without an external signer, it uses the package key
492 (OPTIONS.package_key) and calls openssl for the signing works.
493 """
494
495 def __init__(self):
496 if OPTIONS.payload_signer is None:
497 # Prepare the payload signing key.
498 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
499 pw = OPTIONS.key_passwords[OPTIONS.package_key]
500
501 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
502 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
503 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
504 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700505 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800506
507 self.signer = "openssl"
508 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
509 "-pkeyopt", "digest:sha256"]
xunchang376cc7c2019-04-08 23:04:58 -0700510 self.key_size = self._GetKeySizeInBytes(signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800511 else:
512 self.signer = OPTIONS.payload_signer
513 self.signer_args = OPTIONS.payload_signer_args
xunchang376cc7c2019-04-08 23:04:58 -0700514 if OPTIONS.payload_signer_key_size:
515 self.key_size = int(OPTIONS.payload_signer_key_size)
516 assert self.key_size == 256 or self.key_size == 512, \
517 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size)
518 else:
519 self.key_size = 256
520
521 @staticmethod
522 def _GetKeySizeInBytes(signing_key):
523 modulus_file = common.MakeTempFile(prefix="modulus-")
524 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus",
525 "-noout", "-out", modulus_file]
526 common.RunAndCheckOutput(cmd, verbose=False)
527
528 with open(modulus_file) as f:
529 modulus_string = f.read()
530 # The modulus string has the format "Modulus=$data", where $data is the
531 # concatenation of hex dump of the modulus.
532 MODULUS_PREFIX = "Modulus="
533 assert modulus_string.startswith(MODULUS_PREFIX)
534 modulus_string = modulus_string[len(MODULUS_PREFIX):]
Tao Bao59cf0c52019-06-25 10:04:24 -0700535 key_size = len(modulus_string) // 2
xunchang376cc7c2019-04-08 23:04:58 -0700536 assert key_size == 256 or key_size == 512, \
537 "Unsupported key size {}".format(key_size)
538 return key_size
Tao Baofabe0832018-01-17 15:52:28 -0800539
540 def Sign(self, in_file):
541 """Signs the given input file. Returns the output filename."""
542 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
543 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700544 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800545 return out_file
546
547
Tao Bao40b18822018-01-30 18:19:04 -0800548class Payload(object):
549 """Manages the creation and the signing of an A/B OTA Payload."""
550
551 PAYLOAD_BIN = 'payload.bin'
552 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800553 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
554 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800555
Tao Bao667ff572018-02-10 00:02:40 -0800556 def __init__(self, secondary=False):
557 """Initializes a Payload instance.
558
559 Args:
560 secondary: Whether it's generating a secondary payload (default: False).
561 """
Tao Bao40b18822018-01-30 18:19:04 -0800562 self.payload_file = None
563 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800564 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800565
Tao Baof0c4aa22018-04-30 20:29:30 -0700566 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700567 # Don't pipe (buffer) the output if verbose is set. Let
568 # brillo_update_payload write to stdout/stderr directly, so its progress can
569 # be monitored.
570 if OPTIONS.verbose:
571 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
572 else:
573 common.RunAndCheckOutput(cmd)
574
Tao Bao40b18822018-01-30 18:19:04 -0800575 def Generate(self, target_file, source_file=None, additional_args=None):
576 """Generates a payload from the given target-files zip(s).
577
578 Args:
579 target_file: The filename of the target build target-files zip.
580 source_file: The filename of the source build target-files zip; or None if
581 generating a full OTA.
582 additional_args: A list of additional args that should be passed to
583 brillo_update_payload script; or None.
584 """
585 if additional_args is None:
586 additional_args = []
587
588 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
589 cmd = ["brillo_update_payload", "generate",
590 "--payload", payload_file,
591 "--target_image", target_file]
592 if source_file is not None:
593 cmd.extend(["--source_image", source_file])
Tianjie Xu1b079832019-08-28 12:19:23 -0700594 if OPTIONS.disable_fec_computation:
595 cmd.extend(["--disable_fec_computation", "true"])
Tao Bao40b18822018-01-30 18:19:04 -0800596 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700597 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800598
599 self.payload_file = payload_file
600 self.payload_properties = None
601
602 def Sign(self, payload_signer):
603 """Generates and signs the hashes of the payload and metadata.
604
605 Args:
606 payload_signer: A PayloadSigner() instance that serves the signing work.
607
608 Raises:
609 AssertionError: On any failure when calling brillo_update_payload script.
610 """
611 assert isinstance(payload_signer, PayloadSigner)
612
613 # 1. Generate hashes of the payload and metadata files.
614 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
615 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
616 cmd = ["brillo_update_payload", "hash",
617 "--unsigned_payload", self.payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700618 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800619 "--metadata_hash_file", metadata_sig_file,
620 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700621 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800622
623 # 2. Sign the hashes.
624 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
625 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
626
627 # 3. Insert the signatures back into the payload file.
628 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
629 suffix=".bin")
630 cmd = ["brillo_update_payload", "sign",
631 "--unsigned_payload", self.payload_file,
632 "--payload", signed_payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700633 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800634 "--metadata_signature_file", signed_metadata_sig_file,
635 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700636 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800637
638 # 4. Dump the signed payload properties.
639 properties_file = common.MakeTempFile(prefix="payload-properties-",
640 suffix=".txt")
641 cmd = ["brillo_update_payload", "properties",
642 "--payload", signed_payload_file,
643 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700644 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800645
Tao Bao667ff572018-02-10 00:02:40 -0800646 if self.secondary:
647 with open(properties_file, "a") as f:
648 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
649
Tao Bao40b18822018-01-30 18:19:04 -0800650 if OPTIONS.wipe_user_data:
651 with open(properties_file, "a") as f:
652 f.write("POWERWASH=1\n")
653
654 self.payload_file = signed_payload_file
655 self.payload_properties = properties_file
656
Tao Bao667ff572018-02-10 00:02:40 -0800657 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800658 """Writes the payload to the given zip.
659
660 Args:
661 output_zip: The output ZipFile instance.
662 """
663 assert self.payload_file is not None
664 assert self.payload_properties is not None
665
Tao Bao667ff572018-02-10 00:02:40 -0800666 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800667 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
668 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
669 else:
670 payload_arcname = Payload.PAYLOAD_BIN
671 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
672
Tao Bao40b18822018-01-30 18:19:04 -0800673 # Add the signed payload file and properties into the zip. In order to
674 # support streaming, we pack them as ZIP_STORED. So these entries can be
675 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800676 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800677 compress_type=zipfile.ZIP_STORED)
678 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800679 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800680 compress_type=zipfile.ZIP_STORED)
681
682
Doug Zongkereef39442009-04-02 12:14:19 -0700683def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200684 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700685
Doug Zongker951495f2009-08-14 12:44:19 -0700686 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
687 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700688
689
Tao Bao481bab82017-12-21 11:23:09 -0800690def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800691 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800692 if not oem_source:
693 return None
694
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800695 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800696 for oem_file in oem_source:
697 with open(oem_file) as fp:
698 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800699 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700700
Doug Zongkereef39442009-04-02 12:14:19 -0700701
Tao Baod42e97e2016-11-30 12:11:57 -0800702def _WriteRecoveryImageToBoot(script, output_zip):
703 """Find and write recovery image to /boot in two-step OTA.
704
705 In two-step OTAs, we write recovery image to /boot as the first step so that
706 we can reboot to there and install a new recovery image to /recovery.
707 A special "recovery-two-step.img" will be preferred, which encodes the correct
708 path of "/boot". Otherwise the device may show "device is corrupt" message
709 when booting into /boot.
710
711 Fall back to using the regular recovery.img if the two-step recovery image
712 doesn't exist. Note that rebuilding the special image at this point may be
713 infeasible, because we don't have the desired boot signer and keys when
714 calling ota_from_target_files.py.
715 """
716
717 recovery_two_step_img_name = "recovery-two-step.img"
718 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700719 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800720 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700721 common.ZipWrite(
722 output_zip,
723 recovery_two_step_img_path,
724 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700725 logger.info(
726 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800727 script.WriteRawImage("/boot", recovery_two_step_img_name)
728 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700729 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800730 # The "recovery.img" entry has been written into package earlier.
731 script.WriteRawImage("/boot", "recovery.img")
732
733
Bill Peckhame868aec2019-09-17 17:06:47 -0700734def HasRecoveryPatch(target_files_zip, info_dict):
735 board_uses_vendorimage = info_dict.get("board_uses_vendorimage") == "true"
736
737 if board_uses_vendorimage:
738 target_files_dir = "VENDOR"
739 else:
740 target_files_dir = "SYSTEM/vendor"
741
742 patch = "%s/recovery-from-boot.p" % target_files_dir
743 img = "%s/etc/recovery.img" %target_files_dir
744
Tao Baof2cffbd2015-07-22 12:33:18 -0700745 namelist = [name for name in target_files_zip.namelist()]
Bill Peckhame868aec2019-09-17 17:06:47 -0700746 return (patch in namelist or img in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700747
Tao Bao457cbf62017-03-06 09:56:01 -0800748
Yifan Hong51d37562019-04-23 17:06:46 -0700749def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700750 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700751 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700752 return True
753 except KeyError:
754 return False
755
Tao Bao457cbf62017-03-06 09:56:01 -0800756
Yifan Hong9276cf02019-08-21 16:37:04 -0700757def HasTrebleEnabled(target_files, target_info):
758 def HasVendorPartition(target_files):
759 if os.path.isdir(target_files):
760 return os.path.isdir(os.path.join(target_files, "VENDOR"))
761 if zipfile.is_zipfile(target_files):
762 return HasPartition(zipfile.ZipFile(target_files), "vendor")
763 raise ValueError("Unknown target_files argument")
Yifan Hong51d37562019-04-23 17:06:46 -0700764
Yifan Hong9276cf02019-08-21 16:37:04 -0700765 return (HasVendorPartition(target_files) and
Tao Bao481bab82017-12-21 11:23:09 -0800766 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700767
768
Tao Bao481bab82017-12-21 11:23:09 -0800769def WriteFingerprintAssertion(script, target_info, source_info):
770 source_oem_props = source_info.oem_props
771 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700772
Tao Bao481bab82017-12-21 11:23:09 -0800773 if source_oem_props is None and target_oem_props is None:
774 script.AssertSomeFingerprint(
775 source_info.fingerprint, target_info.fingerprint)
776 elif source_oem_props is not None and target_oem_props is not None:
777 script.AssertSomeThumbprint(
778 target_info.GetBuildProp("ro.build.thumbprint"),
779 source_info.GetBuildProp("ro.build.thumbprint"))
780 elif source_oem_props is None and target_oem_props is not None:
781 script.AssertFingerprintOrThumbprint(
782 source_info.fingerprint,
783 target_info.GetBuildProp("ro.build.thumbprint"))
784 else:
785 script.AssertFingerprintOrThumbprint(
786 target_info.fingerprint,
787 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700788
Doug Zongkerfc44a512014-08-26 13:10:25 -0700789
Yifan Hong9276cf02019-08-21 16:37:04 -0700790def CheckVintfIfTrebleEnabled(target_files, target_info):
791 """Checks compatibility info of the input target files.
Tao Bao21803d32017-04-19 10:16:09 -0700792
Yifan Hong9276cf02019-08-21 16:37:04 -0700793 Metadata used for compatibility verification is retrieved from target_zip.
Tao Bao21803d32017-04-19 10:16:09 -0700794
Yifan Hong9276cf02019-08-21 16:37:04 -0700795 Compatibility should only be checked for devices that have enabled
Tao Baobcd1d162017-08-26 13:10:26 -0700796 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700797
798 Args:
Yifan Hong9276cf02019-08-21 16:37:04 -0700799 target_files: Path to zip file containing the source files to be included
800 for OTA. Can also be the path to extracted directory.
Tao Bao481bab82017-12-21 11:23:09 -0800801 target_info: The BuildInfo instance that holds the target build info.
Tao Bao21803d32017-04-19 10:16:09 -0700802 """
803
Tao Baobcd1d162017-08-26 13:10:26 -0700804 # Will only proceed if the target has enabled the Treble support (as well as
805 # having a /vendor partition).
Yifan Hong9276cf02019-08-21 16:37:04 -0700806 if not HasTrebleEnabled(target_files, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700807 return
808
xunchangabfa2652019-02-19 16:27:10 -0800809 # Skip adding the compatibility package as a workaround for b/114240221. The
810 # compatibility will always fail on devices without qualified kernels.
811 if OPTIONS.skip_compatibility_check:
812 return
813
Yifan Hong9276cf02019-08-21 16:37:04 -0700814 if not check_target_files_vintf.CheckVintf(target_files, target_info):
815 raise RuntimeError("VINTF compatibility check failed")
Tao Bao21803d32017-04-19 10:16:09 -0700816
817
Tianjie Xuf67dd802019-05-20 17:50:36 -0700818def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
819 device_specific):
820 """Returns a ordered dict of block differences with partition name as key."""
821
822 def GetIncrementalBlockDifferenceForPartition(name):
823 if not HasPartition(source_zip, name):
824 raise RuntimeError("can't generate incremental that adds {}".format(name))
825
826 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
827 info_dict=source_info,
828 allow_shared_blocks=allow_shared_blocks)
829
830 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
831 name, 4096, target_info)
832 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
833 info_dict=target_info,
834 allow_shared_blocks=allow_shared_blocks,
835 hashtree_info_generator=
836 hashtree_info_generator)
837
838 # Check the first block of the source system partition for remount R/W only
839 # if the filesystem is ext4.
840 partition_source_info = source_info["fstab"]["/" + name]
841 check_first_block = partition_source_info.fs_type == "ext4"
842 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
843 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
844 # b) the blocks listed in block map may not contain all the bytes for a
845 # given file (because they're rounded to be 4K-aligned).
846 partition_target_info = target_info["fstab"]["/" + name]
847 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
848 partition_target_info.fs_type == "squashfs")
849 return common.BlockDifference(name, partition_src, partition_tgt,
850 check_first_block,
851 version=blockimgdiff_version,
852 disable_imgdiff=disable_imgdiff)
853
854 if source_zip:
855 # See notes in common.GetUserImage()
856 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
857 target_info.get('ext4_share_dup_blocks') == "true")
858 blockimgdiff_version = max(
859 int(i) for i in target_info.get(
860 "blockimgdiff_versions", "1").split(","))
861 assert blockimgdiff_version >= 3
862
863 block_diff_dict = collections.OrderedDict()
864 partition_names = ["system", "vendor", "product", "odm", "system_ext"]
865 for partition in partition_names:
866 if not HasPartition(target_zip, partition):
867 continue
868 # Full OTA update.
869 if not source_zip:
870 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
871 info_dict=target_info,
872 reset_file_map=True)
873 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
874 src=None)
875 # Incremental OTA update.
876 else:
877 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
878 partition)
879 assert "system" in block_diff_dict
880
881 # Get the block diffs from the device specific script. If there is a
882 # duplicate block diff for a partition, ignore the diff in the generic script
883 # and use the one in the device specific script instead.
884 if source_zip:
885 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
886 function_name = "IncrementalOTA_GetBlockDifferences"
887 else:
888 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
889 function_name = "FullOTA_GetBlockDifferences"
890
891 if device_specific_diffs:
892 assert all(isinstance(diff, common.BlockDifference)
893 for diff in device_specific_diffs), \
894 "{} is not returning a list of BlockDifference objects".format(
895 function_name)
896 for diff in device_specific_diffs:
897 if diff.partition in block_diff_dict:
898 logger.warning("Duplicate block difference found. Device specific block"
899 " diff for partition '%s' overrides the one in generic"
900 " script.", diff.partition)
901 block_diff_dict[diff.partition] = diff
902
903 return block_diff_dict
904
905
Tao Bao491d7e22018-02-21 13:17:22 -0800906def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800907 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700908
Tao Bao481bab82017-12-21 11:23:09 -0800909 # We don't know what version it will be installed on top of. We expect the API
910 # just won't change very often. Similarly for fstab, it might have changed in
911 # the target build.
912 target_api_version = target_info["recovery_api_version"]
913 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700914
Tao Bao481bab82017-12-21 11:23:09 -0800915 if target_info.oem_props and not OPTIONS.oem_no_mount:
916 target_info.WriteMountOemScript(script)
917
Tao Baodf3a48b2018-01-10 16:30:43 -0800918 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700919
Tao Bao491d7e22018-02-21 13:17:22 -0800920 if not OPTIONS.no_signing:
921 staging_file = common.MakeTempFile(suffix='.zip')
922 else:
923 staging_file = output_file
924
925 output_zip = zipfile.ZipFile(
926 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
927
Doug Zongker05d3dea2009-06-22 11:32:31 -0700928 device_specific = common.DeviceSpecificParams(
929 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800930 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700931 output_zip=output_zip,
932 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700933 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700934 metadata=metadata,
935 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700936
Bill Peckhame868aec2019-09-17 17:06:47 -0700937 assert HasRecoveryPatch(input_zip, info_dict=OPTIONS.info_dict)
Doug Zongkerc9253822014-02-04 12:17:58 -0800938
Tao Bao481bab82017-12-21 11:23:09 -0800939 # Assertions (e.g. downgrade check, device properties check).
940 ts = target_info.GetBuildProp("ro.build.date.utc")
941 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700942 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700943
Tao Bao481bab82017-12-21 11:23:09 -0800944 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700945 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800946
Tianjie Xuf67dd802019-05-20 17:50:36 -0700947 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
948 target_info=target_info,
949 source_info=None,
950 device_specific=device_specific)
951
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800952 # Two-step package strategy (in chronological order, which is *not*
953 # the order in which the generated script has things):
954 #
955 # if stage is not "2/3" or "3/3":
956 # write recovery image to boot partition
957 # set stage to "2/3"
958 # reboot to boot partition and restart recovery
959 # else if stage is "2/3":
960 # write recovery image to recovery partition
961 # set stage to "3/3"
962 # reboot to recovery partition and restart recovery
963 # else:
964 # (stage must be "3/3")
965 # set stage to ""
966 # do normal full package installation:
967 # wipe and install system, boot image, etc.
968 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700969 # complete script normally
970 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800971
972 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
973 OPTIONS.input_tmp, "RECOVERY")
974 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800975 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800976 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800977 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800978 assert fs.fs_type.upper() == "EMMC", \
979 "two-step packages only supported on devices with EMMC /misc partitions"
980 bcb_dev = {"bcb_dev": fs.device}
981 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
982 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700983if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800984""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800985
986 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
987 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800988 script.WriteRawImage("/recovery", "recovery.img")
989 script.AppendExtra("""
990set_stage("%(bcb_dev)s", "3/3");
991reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700992else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800993""" % bcb_dev)
994
Tao Baod42e97e2016-11-30 12:11:57 -0800995 # Stage 3/3: Make changes.
996 script.Comment("Stage 3/3")
997
Tao Bao6c55a8a2015-04-08 15:30:27 -0700998 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800999 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001000
Doug Zongkere5ff5902012-01-17 10:55:37 -08001001 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -07001002
Tianjie Xuf67dd802019-05-20 17:50:36 -07001003 # All other partitions as well as the data wipe use 10% of the progress, and
1004 # the update of the system partition takes the remaining progress.
1005 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -07001006 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -08001007 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -07001008 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1009 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001010
Yifan Hong10c530d2018-12-27 17:34:18 -08001011 if target_info.get('use_dynamic_partitions') == "true":
1012 # Use empty source_info_dict to indicate that all partitions / groups must
1013 # be re-added.
1014 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1015 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001016 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001017 progress_dict=progress_dict)
1018 dynamic_partitions_diff.WriteScript(script, output_zip,
1019 write_verify_script=OPTIONS.verify)
1020 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001021 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001022 block_diff.WriteScript(script, output_zip,
1023 progress=progress_dict.get(block_diff.partition),
1024 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001025
Yifan Hong9276cf02019-08-21 16:37:04 -07001026 CheckVintfIfTrebleEnabled(OPTIONS.input_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001027
Yifan Hong10c530d2018-12-27 17:34:18 -08001028 boot_img = common.GetBootableImage(
1029 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001030 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001031 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001032
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001033 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001034
Tianjie Xuf67dd802019-05-20 17:50:36 -07001035 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001036 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001037
Doug Zongker1c390a22009-05-14 19:06:36 -07001038 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001039 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001040
Doug Zongker14833602010-02-02 13:12:04 -08001041 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001042
Doug Zongker922206e2014-03-04 13:16:24 -08001043 if OPTIONS.wipe_user_data:
1044 script.ShowProgress(0.1, 10)
1045 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001046
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001047 if OPTIONS.two_step:
1048 script.AppendExtra("""
1049set_stage("%(bcb_dev)s", "");
1050""" % bcb_dev)
1051 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001052
1053 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1054 script.Comment("Stage 1/3")
1055 _WriteRecoveryImageToBoot(script, output_zip)
1056
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001057 script.AppendExtra("""
1058set_stage("%(bcb_dev)s", "2/3");
1059reboot_now("%(bcb_dev)s", "");
1060endif;
1061endif;
1062""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001063
Tao Bao5d182562016-02-23 11:38:39 -08001064 script.SetProgress(1)
1065 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001066 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001067
1068 # We haven't written the metadata entry, which will be done in
1069 # FinalizeMetadata.
1070 common.ZipClose(output_zip)
1071
1072 needed_property_files = (
1073 NonAbOtaPropertyFiles(),
1074 )
1075 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001076
Doug Zongkerfc44a512014-08-26 13:10:25 -07001077
xunchang1cfe2512019-02-19 14:14:48 -08001078def WriteMetadata(metadata, output):
1079 """Writes the metadata to the zip archive or a file.
1080
1081 Args:
1082 metadata: The metadata dict for the package.
1083 output: A ZipFile object or a string of the output file path.
1084 """
1085
Tao Bao59cf0c52019-06-25 10:04:24 -07001086 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001087 if isinstance(output, zipfile.ZipFile):
1088 common.ZipWriteStr(output, METADATA_NAME, value,
1089 compress_type=zipfile.ZIP_STORED)
1090 return
1091
1092 with open(output, 'w') as f:
1093 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001094
Doug Zongkerfc44a512014-08-26 13:10:25 -07001095
Tao Bao481bab82017-12-21 11:23:09 -08001096def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001097 # Only incremental OTAs are allowed to reach here.
1098 assert OPTIONS.incremental_source is not None
1099
Tao Bao481bab82017-12-21 11:23:09 -08001100 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1101 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001102 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001103
1104 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001105 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001106 raise RuntimeError(
1107 "--downgrade or --override_timestamp specified but no downgrade "
1108 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001109 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001110 else:
1111 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001112 raise RuntimeError(
1113 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1114 "Need to specify --override_timestamp OR --downgrade to allow "
1115 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001116
1117
Tao Baodf3a48b2018-01-10 16:30:43 -08001118def GetPackageMetadata(target_info, source_info=None):
1119 """Generates and returns the metadata dict.
1120
1121 It generates a dict() that contains the info to be written into an OTA
1122 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001123 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001124
1125 Args:
1126 target_info: The BuildInfo instance that holds the target build info.
1127 source_info: The BuildInfo instance that holds the source build info, or
1128 None if generating full OTA.
1129
1130 Returns:
1131 A dict to be written into package metadata entry.
1132 """
1133 assert isinstance(target_info, BuildInfo)
1134 assert source_info is None or isinstance(source_info, BuildInfo)
1135
1136 metadata = {
1137 'post-build' : target_info.fingerprint,
1138 'post-build-incremental' : target_info.GetBuildProp(
1139 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001140 'post-sdk-level' : target_info.GetBuildProp(
1141 'ro.build.version.sdk'),
1142 'post-security-patch-level' : target_info.GetBuildProp(
1143 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001144 }
1145
1146 if target_info.is_ab:
1147 metadata['ota-type'] = 'AB'
1148 metadata['ota-required-cache'] = '0'
1149 else:
1150 metadata['ota-type'] = 'BLOCK'
1151
1152 if OPTIONS.wipe_user_data:
1153 metadata['ota-wipe'] = 'yes'
1154
Tao Bao393eeb42019-03-06 16:00:38 -08001155 if OPTIONS.retrofit_dynamic_partitions:
1156 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1157
Tao Baodf3a48b2018-01-10 16:30:43 -08001158 is_incremental = source_info is not None
1159 if is_incremental:
1160 metadata['pre-build'] = source_info.fingerprint
1161 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1162 'ro.build.version.incremental')
1163 metadata['pre-device'] = source_info.device
1164 else:
1165 metadata['pre-device'] = target_info.device
1166
Tao Baofaa8e0b2018-04-12 14:31:43 -07001167 # Use the actual post-timestamp, even for a downgrade case.
1168 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1169
1170 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001171 if is_incremental:
1172 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001173
1174 return metadata
1175
1176
Tao Baod3fc38a2018-03-08 16:09:01 -08001177class PropertyFiles(object):
1178 """A class that computes the property-files string for an OTA package.
1179
1180 A property-files string is a comma-separated string that contains the
1181 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1182 can be fetched directly with the package URL along with the offset/size info.
1183 These strings can be used for streaming A/B OTAs, or allowing an updater to
1184 download package metadata entry directly, without paying the cost of
1185 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001186
Tao Baocc8e2662018-03-01 19:30:00 -08001187 Computing the final property-files string requires two passes. Because doing
1188 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1189 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1190 values.
1191
1192 This class provides functions to be called for each pass. The general flow is
1193 as follows.
1194
Tao Baod3fc38a2018-03-08 16:09:01 -08001195 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001196 # The first pass, which writes placeholders before doing initial signing.
1197 property_files.Compute()
1198 SignOutput()
1199
1200 # The second pass, by replacing the placeholders with actual data.
1201 property_files.Finalize()
1202 SignOutput()
1203
1204 And the caller can additionally verify the final result.
1205
1206 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001207 """
1208
Tao Baocc8e2662018-03-01 19:30:00 -08001209 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001210 self.name = None
1211 self.required = ()
1212 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001213
Tao Baocc8e2662018-03-01 19:30:00 -08001214 def Compute(self, input_zip):
1215 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001216
Tao Baocc8e2662018-03-01 19:30:00 -08001217 We reserve extra space for the offset and size of the metadata entry itself,
1218 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001219
Tao Baocc8e2662018-03-01 19:30:00 -08001220 Args:
1221 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001222
Tao Baocc8e2662018-03-01 19:30:00 -08001223 Returns:
1224 A string with placeholders for the metadata offset/size info, e.g.
1225 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1226 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001227 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001228
Tao Baod2ce2ed2018-03-16 12:59:42 -07001229 class InsufficientSpaceException(Exception):
1230 pass
1231
Tao Baocc8e2662018-03-01 19:30:00 -08001232 def Finalize(self, input_zip, reserved_length):
1233 """Finalizes a property-files string with actual METADATA offset/size info.
1234
1235 The input ZIP file has been signed, with the ZIP entries in the desired
1236 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1237 the ZIP entry offsets and construct the property-files string with actual
1238 data. Note that during this process, we must pad the property-files string
1239 to the reserved length, so that the METADATA entry size remains the same.
1240 Otherwise the entries' offsets and sizes may change again.
1241
1242 Args:
1243 input_zip: The input ZIP file.
1244 reserved_length: The reserved length of the property-files string during
1245 the call to Compute(). The final string must be no more than this
1246 size.
1247
1248 Returns:
1249 A property-files string including the metadata offset/size info, e.g.
1250 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1251
1252 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001253 InsufficientSpaceException: If the reserved length is insufficient to hold
1254 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001255 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001256 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001257 if len(result) > reserved_length:
1258 raise self.InsufficientSpaceException(
1259 'Insufficient reserved space: reserved={}, actual={}'.format(
1260 reserved_length, len(result)))
1261
Tao Baocc8e2662018-03-01 19:30:00 -08001262 result += ' ' * (reserved_length - len(result))
1263 return result
1264
1265 def Verify(self, input_zip, expected):
1266 """Verifies the input ZIP file contains the expected property-files string.
1267
1268 Args:
1269 input_zip: The input ZIP file.
1270 expected: The property-files string that's computed from Finalize().
1271
1272 Raises:
1273 AssertionError: On finding a mismatch.
1274 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001275 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001276 assert actual == expected, \
1277 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1278
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001279 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1280 """
1281 Constructs the property-files string per request.
1282
1283 Args:
1284 zip_file: The input ZIP file.
1285 reserved_length: The reserved length of the property-files string.
1286
1287 Returns:
1288 A property-files string including the metadata offset/size info, e.g.
1289 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1290 """
Tao Baocc8e2662018-03-01 19:30:00 -08001291
1292 def ComputeEntryOffsetSize(name):
1293 """Computes the zip entry offset and size."""
1294 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001295 offset = info.header_offset
1296 offset += zipfile.sizeFileHeader
1297 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001298 size = info.file_size
1299 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1300
1301 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001302 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001303 for entry in self.required:
1304 tokens.append(ComputeEntryOffsetSize(entry))
1305 for entry in self.optional:
1306 if entry in zip_file.namelist():
1307 tokens.append(ComputeEntryOffsetSize(entry))
1308
1309 # 'META-INF/com/android/metadata' is required. We don't know its actual
1310 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001311 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1312 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1313 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1314 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001315 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001316 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001317 else:
1318 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1319
1320 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001321
Tao Bao85f16982018-03-08 16:28:33 -08001322 def _GetPrecomputed(self, input_zip):
1323 """Computes the additional tokens to be included into the property-files.
1324
1325 This applies to tokens without actual ZIP entries, such as
1326 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1327 that they can download the payload metadata directly with the info.
1328
1329 Args:
1330 input_zip: The input zip file.
1331
1332 Returns:
1333 A list of strings (tokens) to be added to the property-files string.
1334 """
1335 # pylint: disable=no-self-use
1336 # pylint: disable=unused-argument
1337 return []
1338
Tao Baofe5b69a2018-03-02 09:47:43 -08001339
Tao Baod3fc38a2018-03-08 16:09:01 -08001340class StreamingPropertyFiles(PropertyFiles):
1341 """A subclass for computing the property-files for streaming A/B OTAs."""
1342
1343 def __init__(self):
1344 super(StreamingPropertyFiles, self).__init__()
1345 self.name = 'ota-streaming-property-files'
1346 self.required = (
1347 # payload.bin and payload_properties.txt must exist.
1348 'payload.bin',
1349 'payload_properties.txt',
1350 )
1351 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001352 # care_map is available only if dm-verity is enabled.
1353 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001354 'care_map.txt',
1355 # compatibility.zip is available only if target supports Treble.
1356 'compatibility.zip',
1357 )
1358
1359
Tao Bao85f16982018-03-08 16:28:33 -08001360class AbOtaPropertyFiles(StreamingPropertyFiles):
1361 """The property-files for A/B OTA that includes payload_metadata.bin info.
1362
1363 Since P, we expose one more token (aka property-file), in addition to the ones
1364 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1365 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1366 doesn't exist as a separate ZIP entry, but can be used to verify if the
1367 payload can be applied on the given device.
1368
1369 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1370 and the newly added 'ota-property-files' in P. The new token will only be
1371 available in 'ota-property-files'.
1372 """
1373
1374 def __init__(self):
1375 super(AbOtaPropertyFiles, self).__init__()
1376 self.name = 'ota-property-files'
1377
1378 def _GetPrecomputed(self, input_zip):
1379 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1380 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1381
1382 @staticmethod
1383 def _GetPayloadMetadataOffsetAndSize(input_zip):
1384 """Computes the offset and size of the payload metadata for a given package.
1385
1386 (From system/update_engine/update_metadata.proto)
1387 A delta update file contains all the deltas needed to update a system from
1388 one specific version to another specific version. The update format is
1389 represented by this struct pseudocode:
1390
1391 struct delta_update_file {
1392 char magic[4] = "CrAU";
1393 uint64 file_format_version;
1394 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1395
1396 // Only present if format_version > 1:
1397 uint32 metadata_signature_size;
1398
1399 // The Bzip2 compressed DeltaArchiveManifest
1400 char manifest[metadata_signature_size];
1401
1402 // The signature of the metadata (from the beginning of the payload up to
1403 // this location, not including the signature itself). This is a
1404 // serialized Signatures message.
1405 char medatada_signature_message[metadata_signature_size];
1406
1407 // Data blobs for files, no specific format. The specific offset
1408 // and length of each data blob is recorded in the DeltaArchiveManifest.
1409 struct {
1410 char data[];
1411 } blobs[];
1412
1413 // These two are not signed:
1414 uint64 payload_signatures_message_size;
1415 char payload_signatures_message[];
1416 };
1417
1418 'payload-metadata.bin' contains all the bytes from the beginning of the
1419 payload, till the end of 'medatada_signature_message'.
1420 """
1421 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001422 payload_offset = payload_info.header_offset
1423 payload_offset += zipfile.sizeFileHeader
1424 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001425 payload_size = payload_info.file_size
1426
Tao Bao59cf0c52019-06-25 10:04:24 -07001427 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001428 header_bin = payload_fp.read(24)
1429
1430 # network byte order (big-endian)
1431 header = struct.unpack("!IQQL", header_bin)
1432
1433 # 'CrAU'
1434 magic = header[0]
1435 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1436
1437 manifest_size = header[2]
1438 metadata_signature_size = header[3]
1439 metadata_total = 24 + manifest_size + metadata_signature_size
1440 assert metadata_total < payload_size
1441
1442 return (payload_offset, metadata_total)
1443
1444
Tao Bao491d7e22018-02-21 13:17:22 -08001445class NonAbOtaPropertyFiles(PropertyFiles):
1446 """The property-files for non-A/B OTA.
1447
1448 For non-A/B OTA, the property-files string contains the info for METADATA
1449 entry, with which a system updater can be fetched the package metadata prior
1450 to downloading the entire package.
1451 """
1452
1453 def __init__(self):
1454 super(NonAbOtaPropertyFiles, self).__init__()
1455 self.name = 'ota-property-files'
1456
1457
Tao Baod3fc38a2018-03-08 16:09:01 -08001458def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001459 """Finalizes the metadata and signs an A/B OTA package.
1460
1461 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1462 that contains the offsets and sizes for the ZIP entries. An example
1463 property-files string is as follows.
1464
1465 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1466
1467 OTA server can pass down this string, in addition to the package URL, to the
1468 system update client. System update client can then fetch individual ZIP
1469 entries (ZIP_STORED) directly at the given offset of the URL.
1470
1471 Args:
1472 metadata: The metadata dict for the package.
1473 input_file: The input ZIP filename that doesn't contain the package METADATA
1474 entry yet.
1475 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001476 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001477 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001478
Tao Baod2ce2ed2018-03-16 12:59:42 -07001479 def ComputeAllPropertyFiles(input_file, needed_property_files):
1480 # Write the current metadata entry with placeholders.
1481 with zipfile.ZipFile(input_file) as input_zip:
1482 for property_files in needed_property_files:
1483 metadata[property_files.name] = property_files.Compute(input_zip)
1484 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001485
Tao Baod2ce2ed2018-03-16 12:59:42 -07001486 if METADATA_NAME in namelist:
1487 common.ZipDelete(input_file, METADATA_NAME)
1488 output_zip = zipfile.ZipFile(input_file, 'a')
1489 WriteMetadata(metadata, output_zip)
1490 common.ZipClose(output_zip)
1491
1492 if OPTIONS.no_signing:
1493 return input_file
1494
Tao Bao491d7e22018-02-21 13:17:22 -08001495 prelim_signing = common.MakeTempFile(suffix='.zip')
1496 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001497 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001498
Tao Baod2ce2ed2018-03-16 12:59:42 -07001499 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1500 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1501 for property_files in needed_property_files:
1502 metadata[property_files.name] = property_files.Finalize(
1503 prelim_signing_zip, len(metadata[property_files.name]))
1504
1505 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1506 # entries, as well as padding the entry headers. We do a preliminary signing
1507 # (with an incomplete metadata entry) to allow that to happen. Then compute
1508 # the ZIP entry offsets, write back the final metadata and do the final
1509 # signing.
1510 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1511 try:
1512 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1513 except PropertyFiles.InsufficientSpaceException:
1514 # Even with the preliminary signing, the entry orders may change
1515 # dramatically, which leads to insufficiently reserved space during the
1516 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1517 # preliminary signing works, based on the already ordered ZIP entries, to
1518 # address the issue.
1519 prelim_signing = ComputeAllPropertyFiles(
1520 prelim_signing, needed_property_files)
1521 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001522
1523 # Replace the METADATA entry.
1524 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001525 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001526 WriteMetadata(metadata, output_zip)
1527 common.ZipClose(output_zip)
1528
1529 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001530 if OPTIONS.no_signing:
1531 output_file = prelim_signing
1532 else:
1533 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001534
1535 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001536 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001537 for property_files in needed_property_files:
1538 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001539
xunchang1cfe2512019-02-19 14:14:48 -08001540 # If requested, dump the metadata to a separate file.
1541 output_metadata_path = OPTIONS.output_metadata_path
1542 if output_metadata_path:
1543 WriteMetadata(metadata, output_metadata_path)
1544
Tao Baofe5b69a2018-03-02 09:47:43 -08001545
Tao Bao491d7e22018-02-21 13:17:22 -08001546def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001547 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1548 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001549
Tao Bao481bab82017-12-21 11:23:09 -08001550 target_api_version = target_info["recovery_api_version"]
1551 source_api_version = source_info["recovery_api_version"]
1552 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001553 logger.warning(
1554 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001555
Tao Bao481bab82017-12-21 11:23:09 -08001556 script = edify_generator.EdifyGenerator(
1557 source_api_version, target_info, fstab=source_info["fstab"])
1558
1559 if target_info.oem_props or source_info.oem_props:
1560 if not OPTIONS.oem_no_mount:
1561 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001562
Tao Baodf3a48b2018-01-10 16:30:43 -08001563 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001564
Tao Bao491d7e22018-02-21 13:17:22 -08001565 if not OPTIONS.no_signing:
1566 staging_file = common.MakeTempFile(suffix='.zip')
1567 else:
1568 staging_file = output_file
1569
1570 output_zip = zipfile.ZipFile(
1571 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1572
Geremy Condra36bd3652014-02-06 19:45:10 -08001573 device_specific = common.DeviceSpecificParams(
1574 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001575 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001576 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001577 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001578 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001579 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001580 output_zip=output_zip,
1581 script=script,
1582 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001583 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001584
Geremy Condra36bd3652014-02-06 19:45:10 -08001585 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001586 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001587 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001588 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001589 updating_boot = (not OPTIONS.two_step and
1590 (source_boot.data != target_boot.data))
1591
Geremy Condra36bd3652014-02-06 19:45:10 -08001592 target_recovery = common.GetBootableImage(
1593 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001594
Tianjie Xuf67dd802019-05-20 17:50:36 -07001595 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1596 source_zip=source_zip,
1597 target_info=target_info,
1598 source_info=source_info,
1599 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001600
Yifan Hong9276cf02019-08-21 16:37:04 -07001601 CheckVintfIfTrebleEnabled(OPTIONS.target_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001602
Tao Bao481bab82017-12-21 11:23:09 -08001603 # Assertions (e.g. device properties check).
1604 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001605 device_specific.IncrementalOTA_Assertions()
1606
1607 # Two-step incremental package strategy (in chronological order,
1608 # which is *not* the order in which the generated script has
1609 # things):
1610 #
1611 # if stage is not "2/3" or "3/3":
1612 # do verification on current system
1613 # write recovery image to boot partition
1614 # set stage to "2/3"
1615 # reboot to boot partition and restart recovery
1616 # else if stage is "2/3":
1617 # write recovery image to recovery partition
1618 # set stage to "3/3"
1619 # reboot to recovery partition and restart recovery
1620 # else:
1621 # (stage must be "3/3")
1622 # perform update:
1623 # patch system files, etc.
1624 # force full install of new boot image
1625 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001626 # complete script normally
1627 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001628
1629 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001630 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001631 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001632 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001633 assert fs.fs_type.upper() == "EMMC", \
1634 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001635 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001636 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1637 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001638if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001639""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001640
1641 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1642 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001643 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001644 script.WriteRawImage("/recovery", "recovery.img")
1645 script.AppendExtra("""
1646set_stage("%(bcb_dev)s", "3/3");
1647reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001648else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001649""" % bcb_dev)
1650
Tao Baod42e97e2016-11-30 12:11:57 -08001651 # Stage 1/3: (a) Verify the current system.
1652 script.Comment("Stage 1/3")
1653
Tao Bao6c55a8a2015-04-08 15:30:27 -07001654 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001655 script.Print("Source: {}".format(source_info.fingerprint))
1656 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001657
Geremy Condra36bd3652014-02-06 19:45:10 -08001658 script.Print("Verifying current system...")
1659
1660 device_specific.IncrementalOTA_VerifyBegin()
1661
Tao Bao481bab82017-12-21 11:23:09 -08001662 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001663
Tao Baod8d14be2016-02-04 14:26:02 -08001664 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001665 required_cache_sizes = [diff.required_cache for diff in
1666 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001667 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001668 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001669 d = common.Difference(target_boot, source_boot)
1670 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001671 if d is None:
1672 include_full_boot = True
1673 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1674 else:
1675 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001676
Tao Bao32fcdab2018-10-12 10:30:39 -07001677 logger.info(
1678 "boot target: %d source: %d diff: %d", target_boot.size,
1679 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001680
Tao Bao51216552018-08-26 11:53:15 -07001681 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001682
Tao Bao51216552018-08-26 11:53:15 -07001683 script.PatchPartitionCheck(
1684 "{}:{}:{}:{}".format(
1685 boot_type, boot_device, target_boot.size, target_boot.sha1),
1686 "{}:{}:{}:{}".format(
1687 boot_type, boot_device, source_boot.size, source_boot.sha1))
1688
Tianjie Xuf67dd802019-05-20 17:50:36 -07001689 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001690
Tianjie Xuf67dd802019-05-20 17:50:36 -07001691 if required_cache_sizes:
1692 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1693
1694 # Verify the existing partitions.
1695 for diff in block_diff_dict.values():
1696 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001697
1698 device_specific.IncrementalOTA_VerifyEnd()
1699
1700 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001701 # Stage 1/3: (b) Write recovery image to /boot.
1702 _WriteRecoveryImageToBoot(script, output_zip)
1703
Geremy Condra36bd3652014-02-06 19:45:10 -08001704 script.AppendExtra("""
1705set_stage("%(bcb_dev)s", "2/3");
1706reboot_now("%(bcb_dev)s", "");
1707else
1708""" % bcb_dev)
1709
Tao Baod42e97e2016-11-30 12:11:57 -08001710 # Stage 3/3: Make changes.
1711 script.Comment("Stage 3/3")
1712
Geremy Condra36bd3652014-02-06 19:45:10 -08001713 script.Comment("---- start making changes here ----")
1714
1715 device_specific.IncrementalOTA_InstallBegin()
1716
Tianjie Xuf67dd802019-05-20 17:50:36 -07001717 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1718 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001719
1720 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1721 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1722 raise RuntimeError(
1723 "can't generate incremental that disables dynamic partitions")
1724 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1725 info_dict=OPTIONS.target_info_dict,
1726 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001727 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001728 progress_dict=progress_dict)
1729 dynamic_partitions_diff.WriteScript(
1730 script, output_zip, write_verify_script=OPTIONS.verify)
1731 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001732 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001733 block_diff.WriteScript(script, output_zip,
1734 progress=progress_dict.get(block_diff.partition),
1735 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001736
1737 if OPTIONS.two_step:
1738 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1739 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001740 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001741
1742 if not OPTIONS.two_step:
1743 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001744 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001745 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001746 script.Print("Installing boot image...")
1747 script.WriteRawImage("/boot", "boot.img")
1748 else:
1749 # Produce the boot image by applying a patch to the current
1750 # contents of the boot partition, and write it back to the
1751 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001752 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001753 script.Print("Patching boot image...")
1754 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001755 script.PatchPartition(
1756 '{}:{}:{}:{}'.format(
1757 boot_type, boot_device, target_boot.size, target_boot.sha1),
1758 '{}:{}:{}:{}'.format(
1759 boot_type, boot_device, source_boot.size, source_boot.sha1),
1760 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001761 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001762 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001763
1764 # Do device-specific installation (eg, write radio image).
1765 device_specific.IncrementalOTA_InstallEnd()
1766
1767 if OPTIONS.extra_script is not None:
1768 script.AppendExtra(OPTIONS.extra_script)
1769
Doug Zongker922206e2014-03-04 13:16:24 -08001770 if OPTIONS.wipe_user_data:
1771 script.Print("Erasing user data...")
1772 script.FormatPartition("/data")
1773
Geremy Condra36bd3652014-02-06 19:45:10 -08001774 if OPTIONS.two_step:
1775 script.AppendExtra("""
1776set_stage("%(bcb_dev)s", "");
1777endif;
1778endif;
1779""" % bcb_dev)
1780
1781 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001782 # For downgrade OTAs, we prefer to use the update-binary in the source
1783 # build that is actually newer than the one in the target build.
1784 if OPTIONS.downgrade:
1785 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1786 else:
1787 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001788 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001789
1790 # We haven't written the metadata entry yet, which will be handled in
1791 # FinalizeMetadata().
1792 common.ZipClose(output_zip)
1793
1794 # Sign the generated zip package unless no_signing is specified.
1795 needed_property_files = (
1796 NonAbOtaPropertyFiles(),
1797 )
1798 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001799
Doug Zongker32b527d2014-03-04 10:03:02 -08001800
Tao Bao15a146a2018-02-21 16:06:59 -08001801def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001802 """Returns a target-files.zip file for generating secondary payload.
1803
1804 Although the original target-files.zip already contains secondary slot
1805 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1806 ones without _other suffix. Note that we cannot instead modify the names in
1807 META/ab_partitions.txt, because there are no matching partitions on device.
1808
1809 For the partitions that don't have secondary images, the ones for primary
1810 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1811 bootloader images in the inactive slot.
1812
1813 Args:
1814 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001815 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001816
1817 Returns:
1818 The filename of the target-files.zip for generating secondary payload.
1819 """
Tianjie Xu1c808002019-09-11 00:29:26 -07001820
1821 def GetInfoForSecondaryImages(info_file):
1822 """Updates info file for secondary payload generation.
1823
1824 Scan each line in the info file, and remove the unwanted partitions from
1825 the dynamic partition list in the related properties. e.g.
1826 "super_google_dynamic_partitions_partition_list=system vendor product"
1827 will become "super_google_dynamic_partitions_partition_list=system".
1828
1829 Args:
1830 info_file: The input info file. e.g. misc_info.txt.
1831
1832 Returns:
1833 A string of the updated info content.
1834 """
1835
1836 output_list = []
1837 with open(info_file) as f:
1838 lines = f.read().splitlines()
1839
1840 # The suffix in partition_list variables that follows the name of the
1841 # partition group.
1842 LIST_SUFFIX = 'partition_list'
1843 for line in lines:
1844 if line.startswith('#') or '=' not in line:
1845 output_list.append(line)
1846 continue
1847 key, value = line.strip().split('=', 1)
1848 if key == 'dynamic_partition_list' or key.endswith(LIST_SUFFIX):
1849 partitions = value.split()
1850 partitions = [partition for partition in partitions if partition
Tao Bao3e759462019-09-17 22:43:11 -07001851 not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Tianjie Xu1c808002019-09-11 00:29:26 -07001852 output_list.append('{}={}'.format(key, ' '.join(partitions)))
1853 else:
1854 output_list.append(line)
1855 return '\n'.join(output_list)
1856
Tao Baof7140c02018-01-30 17:09:24 -08001857 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1858 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1859
Tao Baodba59ee2018-01-09 13:21:02 -08001860 with zipfile.ZipFile(input_file, 'r') as input_zip:
1861 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001862
Tao Bao0ff15de2019-03-20 11:26:06 -07001863 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001864 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001865 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1866 if info.filename == 'IMAGES/system_other.img':
1867 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1868
1869 # Primary images and friends need to be skipped explicitly.
1870 elif info.filename in ('IMAGES/system.img',
1871 'IMAGES/system.map'):
1872 pass
Tao Bao3e759462019-09-17 22:43:11 -07001873
1874 # Copy images that are not in SECONDARY_PAYLOAD_SKIPPED_IMAGES.
1875 elif info.filename.startswith(('IMAGES/', 'RADIO/')):
1876 image_name = os.path.basename(info.filename)
1877 if image_name not in ['{}.img'.format(partition) for partition in
1878 SECONDARY_PAYLOAD_SKIPPED_IMAGES]:
1879 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001880
Tao Bao15a146a2018-02-21 16:06:59 -08001881 # Skip copying the postinstall config if requested.
1882 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1883 pass
1884
Tianjie Xu1c808002019-09-11 00:29:26 -07001885 elif info.filename.startswith('META/'):
1886 # Remove the unnecessary partitions for secondary images from the
1887 # ab_partitions file.
1888 if info.filename == AB_PARTITIONS:
1889 with open(unzipped_file) as f:
1890 partition_list = f.read().splitlines()
1891 partition_list = [partition for partition in partition_list if partition
Tao Bao3e759462019-09-17 22:43:11 -07001892 and partition not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Tianjie Xu1c808002019-09-11 00:29:26 -07001893 common.ZipWriteStr(target_zip, info.filename, '\n'.join(partition_list))
1894 # Remove the unnecessary partitions from the dynamic partitions list.
1895 elif (info.filename == 'META/misc_info.txt' or
1896 info.filename == DYNAMIC_PARTITION_INFO):
1897 modified_info = GetInfoForSecondaryImages(unzipped_file)
1898 common.ZipWriteStr(target_zip, info.filename, modified_info)
1899 else:
1900 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001901
Tao Baof7140c02018-01-30 17:09:24 -08001902 common.ZipClose(target_zip)
1903
1904 return target_file
1905
1906
Tao Bao15a146a2018-02-21 16:06:59 -08001907def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1908 """Returns a target-files.zip that's not containing postinstall_config.txt.
1909
1910 This allows brillo_update_payload script to skip writing all the postinstall
1911 hooks in the generated payload. The input target-files.zip file will be
1912 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1913 contain the postinstall_config.txt entry, the input file will be returned.
1914
1915 Args:
1916 input_file: The input target-files.zip filename.
1917
1918 Returns:
1919 The filename of target-files.zip that doesn't contain postinstall config.
1920 """
1921 # We should only make a copy if postinstall_config entry exists.
1922 with zipfile.ZipFile(input_file, 'r') as input_zip:
1923 if POSTINSTALL_CONFIG not in input_zip.namelist():
1924 return input_file
1925
1926 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1927 shutil.copyfile(input_file, target_file)
1928 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1929 return target_file
1930
1931
Yifan Hong50e79542018-11-08 17:44:12 -08001932def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001933 super_block_devices,
1934 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001935 """Returns a target-files.zip for retrofitting dynamic partitions.
1936
1937 This allows brillo_update_payload to generate an OTA based on the exact
1938 bits on the block devices. Postinstall is disabled.
1939
1940 Args:
1941 input_file: The input target-files.zip filename.
1942 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001943 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001944
1945 Returns:
1946 The filename of target-files.zip with *.img replaced with super_*.img for
1947 each block device in super_block_devices.
1948 """
1949 assert super_block_devices, "No super_block_devices are specified."
1950
1951 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001952 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001953
1954 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1955 shutil.copyfile(input_file, target_file)
1956
Tao Baoa3705452019-06-24 15:33:41 -07001957 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001958 namelist = input_zip.namelist()
1959
Yifan Hongb433eba2019-03-06 12:42:53 -08001960 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1961
1962 # Remove partitions from META/ab_partitions.txt that is in
1963 # dynamic_partition_list but not in super_block_devices so that
1964 # brillo_update_payload won't generate update for those logical partitions.
1965 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1966 with open(ab_partitions_file) as f:
1967 ab_partitions_lines = f.readlines()
1968 ab_partitions = [line.strip() for line in ab_partitions_lines]
1969 # Assert that all super_block_devices are in ab_partitions
1970 super_device_not_updated = [partition for partition in super_block_devices
1971 if partition not in ab_partitions]
1972 assert not super_device_not_updated, \
1973 "{} is in super_block_devices but not in {}".format(
1974 super_device_not_updated, AB_PARTITIONS)
1975 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1976 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1977 with open(new_ab_partitions, 'w') as f:
1978 for partition in ab_partitions:
1979 if (partition in dynamic_partition_list and
1980 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001981 logger.info("Dropping %s from ab_partitions.txt", partition)
1982 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001983 f.write(partition + "\n")
1984 to_delete = [AB_PARTITIONS]
1985
Yifan Hong50e79542018-11-08 17:44:12 -08001986 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001987 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001988
1989 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1990 # is a regular update on devices without dynamic partitions support.
1991 to_delete += [DYNAMIC_PARTITION_INFO]
1992
Tao Bao03fecb62018-11-28 10:59:23 -08001993 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001994 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001995 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001996
1997 common.ZipDelete(target_file, to_delete)
1998
Yifan Hong50e79542018-11-08 17:44:12 -08001999 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
2000
2001 # Write super_{foo}.img as {foo}.img.
2002 for src, dst in replace.items():
2003 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07002004 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08002005 unzipped_file = os.path.join(input_tmp, *src.split('/'))
2006 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
2007
Yifan Hongb433eba2019-03-06 12:42:53 -08002008 # Write new ab_partitions.txt file
2009 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
2010
Yifan Hong50e79542018-11-08 17:44:12 -08002011 common.ZipClose(target_zip)
2012
2013 return target_file
2014
2015
Tao Baof0c4aa22018-04-30 20:29:30 -07002016def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002017 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002018 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002019 if not OPTIONS.no_signing:
2020 staging_file = common.MakeTempFile(suffix='.zip')
2021 else:
2022 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002023 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002024 compression=zipfile.ZIP_DEFLATED)
2025
Tao Bao481bab82017-12-21 11:23:09 -08002026 if source_file is not None:
2027 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2028 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2029 else:
2030 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2031 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002032
Tao Bao481bab82017-12-21 11:23:09 -08002033 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002034 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002035
Yifan Hong50e79542018-11-08 17:44:12 -08002036 if OPTIONS.retrofit_dynamic_partitions:
2037 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002038 target_file, target_info.get("super_block_devices").strip().split(),
2039 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002040 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002041 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2042
Tao Bao40b18822018-01-30 18:19:04 -08002043 # Generate payload.
2044 payload = Payload()
2045
2046 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002047 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002048 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002049 else:
2050 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002051 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002052
Tao Bao40b18822018-01-30 18:19:04 -08002053 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002054
Tao Bao40b18822018-01-30 18:19:04 -08002055 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002056 payload_signer = PayloadSigner()
2057 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002058
Tao Bao40b18822018-01-30 18:19:04 -08002059 # Write the payload into output zip.
2060 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002061
Tao Baof7140c02018-01-30 17:09:24 -08002062 # Generate and include the secondary payload that installs secondary images
2063 # (e.g. system_other.img).
2064 if OPTIONS.include_secondary:
2065 # We always include a full payload for the secondary slot, even when
2066 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002067 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2068 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002069 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002070 secondary_payload.Generate(secondary_target_file,
2071 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002072 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002073 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002074
Tianjie Xucfa86222016-03-07 16:31:19 -08002075 # If dm-verity is supported for the device, copy contents of care_map
2076 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002077 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002078 if (target_info.get("verity") == "true" or
2079 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002080 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2081 "META/" + x in target_zip.namelist()]
2082
2083 # Adds care_map if either the protobuf format or the plain text one exists.
2084 if care_map_list:
2085 care_map_name = care_map_list[0]
2086 care_map_data = target_zip.read("META/" + care_map_name)
2087 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002088 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002089 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002090 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002091 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002092 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002093
Tao Bao21803d32017-04-19 10:16:09 -07002094 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08002095
Yifan Hong9276cf02019-08-21 16:37:04 -07002096 CheckVintfIfTrebleEnabled(target_file, target_info)
2097
Tao Baofe5b69a2018-03-02 09:47:43 -08002098 # We haven't written the metadata entry yet, which will be handled in
2099 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002100 common.ZipClose(output_zip)
2101
Tao Bao85f16982018-03-08 16:28:33 -08002102 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2103 # all the info of the latter. However, system updaters and OTA servers need to
2104 # take time to switch to the new flag. We keep both of the flags for
2105 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002106 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002107 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002108 StreamingPropertyFiles(),
2109 )
2110 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002111
Tao Baoc098e9e2016-01-07 13:03:56 -08002112
Tao Baof0c4aa22018-04-30 20:29:30 -07002113def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
2114 """Generates a non-A/B OTA package."""
2115 # Sanity check the loaded info dicts first.
2116 if OPTIONS.info_dict.get("no_recovery") == "true":
2117 raise common.ExternalError(
2118 "--- target build has specified no recovery ---")
2119
2120 # Non-A/B OTAs rely on /cache partition to store temporary files.
2121 cache_size = OPTIONS.info_dict.get("cache_size")
2122 if cache_size is None:
2123 logger.warning("--- can't determine the cache partition size ---")
2124 OPTIONS.cache_size = cache_size
2125
2126 if OPTIONS.extra_script is not None:
2127 with open(OPTIONS.extra_script) as fp:
2128 OPTIONS.extra_script = fp.read()
2129
2130 if OPTIONS.extracted_input is not None:
2131 OPTIONS.input_tmp = OPTIONS.extracted_input
2132 else:
2133 logger.info("unzipping target target-files...")
2134 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
2135 OPTIONS.target_tmp = OPTIONS.input_tmp
2136
2137 # If the caller explicitly specified the device-specific extensions path via
2138 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2139 # is present in the target target_files. Otherwise, take the path of the file
2140 # from 'tool_extensions' in the info dict and look for that in the local
2141 # filesystem, relative to the current directory.
2142 if OPTIONS.device_specific is None:
2143 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2144 if os.path.exists(from_input):
2145 logger.info("(using device-specific extensions from target_files)")
2146 OPTIONS.device_specific = from_input
2147 else:
2148 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
2149
2150 if OPTIONS.device_specific is not None:
2151 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
2152
2153 # Generate a full OTA.
2154 if source_file is None:
2155 with zipfile.ZipFile(target_file) as input_zip:
2156 WriteFullOTAPackage(
2157 input_zip,
2158 output_file)
2159
2160 # Generate an incremental OTA.
2161 else:
2162 logger.info("unzipping source target-files...")
2163 OPTIONS.source_tmp = common.UnzipTemp(
2164 OPTIONS.incremental_source, UNZIP_PATTERN)
2165 with zipfile.ZipFile(target_file) as input_zip, \
2166 zipfile.ZipFile(source_file) as source_zip:
2167 WriteBlockIncrementalOTAPackage(
2168 input_zip,
2169 source_zip,
2170 output_file)
2171
2172
Doug Zongkereef39442009-04-02 12:14:19 -07002173def main(argv):
2174
2175 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002176 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002177 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002178 elif o in ("-i", "--incremental_from"):
2179 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002180 elif o == "--full_radio":
2181 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002182 elif o == "--full_bootloader":
2183 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002184 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002185 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002186 elif o == "--downgrade":
2187 OPTIONS.downgrade = True
2188 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002189 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002190 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002191 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002192 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002193 elif o == "--oem_no_mount":
2194 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002195 elif o in ("-e", "--extra_script"):
2196 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002197 elif o in ("-t", "--worker_threads"):
2198 if a.isdigit():
2199 OPTIONS.worker_threads = int(a)
2200 else:
2201 raise ValueError("Cannot parse value %r for option %r - only "
2202 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002203 elif o in ("-2", "--two_step"):
2204 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002205 elif o == "--include_secondary":
2206 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002207 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002208 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002209 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002210 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002211 elif o == "--block":
2212 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002213 elif o in ("-b", "--binary"):
2214 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002215 elif o == "--stash_threshold":
2216 try:
2217 OPTIONS.stash_threshold = float(a)
2218 except ValueError:
2219 raise ValueError("Cannot parse value %r for option %r - expecting "
2220 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002221 elif o == "--log_diff":
2222 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002223 elif o == "--payload_signer":
2224 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002225 elif o == "--payload_signer_args":
2226 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002227 elif o == "--payload_signer_key_size":
2228 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002229 elif o == "--extracted_input_target_files":
2230 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002231 elif o == "--skip_postinstall":
2232 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002233 elif o == "--retrofit_dynamic_partitions":
2234 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002235 elif o == "--skip_compatibility_check":
2236 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002237 elif o == "--output_metadata_path":
2238 OPTIONS.output_metadata_path = a
Tianjie Xu1b079832019-08-28 12:19:23 -07002239 elif o == "--disable_fec_computation":
2240 OPTIONS.disable_fec_computation = True
Doug Zongkereef39442009-04-02 12:14:19 -07002241 else:
2242 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002243 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002244
2245 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002246 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002247 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002248 "package_key=",
2249 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002250 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002251 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002252 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002253 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002254 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002255 "extra_script=",
2256 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002257 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002258 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002259 "no_signing",
2260 "block",
2261 "binary=",
2262 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002263 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002264 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002265 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002266 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002267 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002268 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002269 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002270 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002271 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002272 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002273 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002274 "output_metadata_path=",
Tianjie Xu1b079832019-08-28 12:19:23 -07002275 "disable_fec_computation",
Dan Albert8b72aef2015-03-23 19:13:21 -07002276 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002277
2278 if len(args) != 2:
2279 common.Usage(__doc__)
2280 sys.exit(1)
2281
Tao Bao32fcdab2018-10-12 10:30:39 -07002282 common.InitLogging()
2283
Tao Bao5d182562016-02-23 11:38:39 -08002284 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002285 # We should only allow downgrading incrementals (as opposed to full).
2286 # Otherwise the device may go back from arbitrary build with this full
2287 # OTA package.
2288 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002289 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002290
Tao Bao2db13852018-01-08 22:28:57 -08002291 # Load the build info dicts from the zip directly or the extracted input
2292 # directory. We don't need to unzip the entire target-files zips, because they
2293 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2294 # When loading the info dicts, we don't need to provide the second parameter
2295 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2296 # some properties with their actual paths, such as 'selinux_fc',
2297 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002298 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002299 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002300 else:
Tao Bao2db13852018-01-08 22:28:57 -08002301 with zipfile.ZipFile(args[0], 'r') as input_zip:
2302 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002303
Tao Bao32fcdab2018-10-12 10:30:39 -07002304 logger.info("--- target info ---")
2305 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002306
2307 # Load the source build dict if applicable.
2308 if OPTIONS.incremental_source is not None:
2309 OPTIONS.target_info_dict = OPTIONS.info_dict
2310 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2311 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2312
Tao Bao32fcdab2018-10-12 10:30:39 -07002313 logger.info("--- source info ---")
2314 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002315
2316 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002317 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2318
Yifan Hong50e79542018-11-08 17:44:12 -08002319 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002320 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002321 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002322 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2323 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002324 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2325 raise common.ExternalError(
2326 "Expect to generate incremental OTA for retrofitting dynamic "
2327 "partitions, but dynamic_partition_retrofit is not set in target "
2328 "build.")
2329 logger.info("Implicitly generating retrofit incremental OTA.")
2330 OPTIONS.retrofit_dynamic_partitions = True
2331
2332 # Skip postinstall for retrofitting dynamic partitions.
2333 if OPTIONS.retrofit_dynamic_partitions:
2334 OPTIONS.skip_postinstall = True
2335
Tao Baoc098e9e2016-01-07 13:03:56 -08002336 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2337
Christian Oderf63e2cd2017-05-01 22:30:15 +02002338 # Use the default key to sign the package if not specified with package_key.
2339 # package_keys are needed on ab_updates, so always define them if an
2340 # ab_update is getting created.
2341 if not OPTIONS.no_signing or ab_update:
2342 if OPTIONS.package_key is None:
2343 OPTIONS.package_key = OPTIONS.info_dict.get(
2344 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002345 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002346 # Get signing keys
2347 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2348
Tao Baoc098e9e2016-01-07 13:03:56 -08002349 if ab_update:
Tao Baof0c4aa22018-04-30 20:29:30 -07002350 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002351 target_file=args[0],
2352 output_file=args[1],
2353 source_file=OPTIONS.incremental_source)
2354
Dan Willemsencea5cd22017-03-21 14:44:27 -07002355 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002356 GenerateNonAbOtaPackage(
2357 target_file=args[0],
2358 output_file=args[1],
2359 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002360
Tao Baof0c4aa22018-04-30 20:29:30 -07002361 # Post OTA generation works.
2362 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2363 logger.info("Generating diff logs...")
2364 logger.info("Unzipping target-files for diffing...")
2365 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2366 source_dir = common.UnzipTemp(
2367 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002368
Tao Baof0c4aa22018-04-30 20:29:30 -07002369 with open(OPTIONS.log_diff, 'w') as out_file:
2370 import target_files_diff
2371 target_files_diff.recursiveDiff(
2372 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002373
Tao Bao32fcdab2018-10-12 10:30:39 -07002374 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002375
2376
2377if __name__ == '__main__':
2378 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002379 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002380 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002381 except common.ExternalError:
2382 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002383 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002384 finally:
2385 common.Cleanup()