blob: de947f329a7392e4b0c72da987b72b1afbcd465c [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
Doug Zongkerc9253822014-02-04 12:17:58 -0800734def HasRecoveryPatch(target_files_zip):
Tao Baof2cffbd2015-07-22 12:33:18 -0700735 namelist = [name for name in target_files_zip.namelist()]
736 return ("SYSTEM/recovery-from-boot.p" in namelist or
737 "SYSTEM/etc/recovery.img" in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700738
Tao Bao457cbf62017-03-06 09:56:01 -0800739
Yifan Hong51d37562019-04-23 17:06:46 -0700740def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700741 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700742 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700743 return True
744 except KeyError:
745 return False
746
Tao Bao457cbf62017-03-06 09:56:01 -0800747
Yifan Hong9276cf02019-08-21 16:37:04 -0700748def HasTrebleEnabled(target_files, target_info):
749 def HasVendorPartition(target_files):
750 if os.path.isdir(target_files):
751 return os.path.isdir(os.path.join(target_files, "VENDOR"))
752 if zipfile.is_zipfile(target_files):
753 return HasPartition(zipfile.ZipFile(target_files), "vendor")
754 raise ValueError("Unknown target_files argument")
Yifan Hong51d37562019-04-23 17:06:46 -0700755
Yifan Hong9276cf02019-08-21 16:37:04 -0700756 return (HasVendorPartition(target_files) and
Tao Bao481bab82017-12-21 11:23:09 -0800757 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700758
759
Tao Bao481bab82017-12-21 11:23:09 -0800760def WriteFingerprintAssertion(script, target_info, source_info):
761 source_oem_props = source_info.oem_props
762 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700763
Tao Bao481bab82017-12-21 11:23:09 -0800764 if source_oem_props is None and target_oem_props is None:
765 script.AssertSomeFingerprint(
766 source_info.fingerprint, target_info.fingerprint)
767 elif source_oem_props is not None and target_oem_props is not None:
768 script.AssertSomeThumbprint(
769 target_info.GetBuildProp("ro.build.thumbprint"),
770 source_info.GetBuildProp("ro.build.thumbprint"))
771 elif source_oem_props is None and target_oem_props is not None:
772 script.AssertFingerprintOrThumbprint(
773 source_info.fingerprint,
774 target_info.GetBuildProp("ro.build.thumbprint"))
775 else:
776 script.AssertFingerprintOrThumbprint(
777 target_info.fingerprint,
778 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700779
Doug Zongkerfc44a512014-08-26 13:10:25 -0700780
Yifan Hong9276cf02019-08-21 16:37:04 -0700781def CheckVintfIfTrebleEnabled(target_files, target_info):
782 """Checks compatibility info of the input target files.
Tao Bao21803d32017-04-19 10:16:09 -0700783
Yifan Hong9276cf02019-08-21 16:37:04 -0700784 Metadata used for compatibility verification is retrieved from target_zip.
Tao Bao21803d32017-04-19 10:16:09 -0700785
Yifan Hong9276cf02019-08-21 16:37:04 -0700786 Compatibility should only be checked for devices that have enabled
Tao Baobcd1d162017-08-26 13:10:26 -0700787 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700788
789 Args:
Yifan Hong9276cf02019-08-21 16:37:04 -0700790 target_files: Path to zip file containing the source files to be included
791 for OTA. Can also be the path to extracted directory.
Tao Bao481bab82017-12-21 11:23:09 -0800792 target_info: The BuildInfo instance that holds the target build info.
Tao Bao21803d32017-04-19 10:16:09 -0700793 """
794
Tao Baobcd1d162017-08-26 13:10:26 -0700795 # Will only proceed if the target has enabled the Treble support (as well as
796 # having a /vendor partition).
Yifan Hong9276cf02019-08-21 16:37:04 -0700797 if not HasTrebleEnabled(target_files, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700798 return
799
xunchangabfa2652019-02-19 16:27:10 -0800800 # Skip adding the compatibility package as a workaround for b/114240221. The
801 # compatibility will always fail on devices without qualified kernels.
802 if OPTIONS.skip_compatibility_check:
803 return
804
Yifan Hong9276cf02019-08-21 16:37:04 -0700805 if not check_target_files_vintf.CheckVintf(target_files, target_info):
806 raise RuntimeError("VINTF compatibility check failed")
Tao Bao21803d32017-04-19 10:16:09 -0700807
808
Tianjie Xuf67dd802019-05-20 17:50:36 -0700809def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
810 device_specific):
811 """Returns a ordered dict of block differences with partition name as key."""
812
813 def GetIncrementalBlockDifferenceForPartition(name):
814 if not HasPartition(source_zip, name):
815 raise RuntimeError("can't generate incremental that adds {}".format(name))
816
817 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
818 info_dict=source_info,
819 allow_shared_blocks=allow_shared_blocks)
820
821 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
822 name, 4096, target_info)
823 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
824 info_dict=target_info,
825 allow_shared_blocks=allow_shared_blocks,
826 hashtree_info_generator=
827 hashtree_info_generator)
828
829 # Check the first block of the source system partition for remount R/W only
830 # if the filesystem is ext4.
831 partition_source_info = source_info["fstab"]["/" + name]
832 check_first_block = partition_source_info.fs_type == "ext4"
833 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
834 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
835 # b) the blocks listed in block map may not contain all the bytes for a
836 # given file (because they're rounded to be 4K-aligned).
837 partition_target_info = target_info["fstab"]["/" + name]
838 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
839 partition_target_info.fs_type == "squashfs")
840 return common.BlockDifference(name, partition_src, partition_tgt,
841 check_first_block,
842 version=blockimgdiff_version,
843 disable_imgdiff=disable_imgdiff)
844
845 if source_zip:
846 # See notes in common.GetUserImage()
847 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
848 target_info.get('ext4_share_dup_blocks') == "true")
849 blockimgdiff_version = max(
850 int(i) for i in target_info.get(
851 "blockimgdiff_versions", "1").split(","))
852 assert blockimgdiff_version >= 3
853
854 block_diff_dict = collections.OrderedDict()
855 partition_names = ["system", "vendor", "product", "odm", "system_ext"]
856 for partition in partition_names:
857 if not HasPartition(target_zip, partition):
858 continue
859 # Full OTA update.
860 if not source_zip:
861 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
862 info_dict=target_info,
863 reset_file_map=True)
864 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
865 src=None)
866 # Incremental OTA update.
867 else:
868 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
869 partition)
870 assert "system" in block_diff_dict
871
872 # Get the block diffs from the device specific script. If there is a
873 # duplicate block diff for a partition, ignore the diff in the generic script
874 # and use the one in the device specific script instead.
875 if source_zip:
876 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
877 function_name = "IncrementalOTA_GetBlockDifferences"
878 else:
879 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
880 function_name = "FullOTA_GetBlockDifferences"
881
882 if device_specific_diffs:
883 assert all(isinstance(diff, common.BlockDifference)
884 for diff in device_specific_diffs), \
885 "{} is not returning a list of BlockDifference objects".format(
886 function_name)
887 for diff in device_specific_diffs:
888 if diff.partition in block_diff_dict:
889 logger.warning("Duplicate block difference found. Device specific block"
890 " diff for partition '%s' overrides the one in generic"
891 " script.", diff.partition)
892 block_diff_dict[diff.partition] = diff
893
894 return block_diff_dict
895
896
Tao Bao491d7e22018-02-21 13:17:22 -0800897def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800898 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700899
Tao Bao481bab82017-12-21 11:23:09 -0800900 # We don't know what version it will be installed on top of. We expect the API
901 # just won't change very often. Similarly for fstab, it might have changed in
902 # the target build.
903 target_api_version = target_info["recovery_api_version"]
904 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700905
Tao Bao481bab82017-12-21 11:23:09 -0800906 if target_info.oem_props and not OPTIONS.oem_no_mount:
907 target_info.WriteMountOemScript(script)
908
Tao Baodf3a48b2018-01-10 16:30:43 -0800909 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700910
Tao Bao491d7e22018-02-21 13:17:22 -0800911 if not OPTIONS.no_signing:
912 staging_file = common.MakeTempFile(suffix='.zip')
913 else:
914 staging_file = output_file
915
916 output_zip = zipfile.ZipFile(
917 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
918
Doug Zongker05d3dea2009-06-22 11:32:31 -0700919 device_specific = common.DeviceSpecificParams(
920 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800921 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700922 output_zip=output_zip,
923 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700924 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700925 metadata=metadata,
926 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700927
Tao Bao457cbf62017-03-06 09:56:01 -0800928 assert HasRecoveryPatch(input_zip)
Doug Zongkerc9253822014-02-04 12:17:58 -0800929
Tao Bao481bab82017-12-21 11:23:09 -0800930 # Assertions (e.g. downgrade check, device properties check).
931 ts = target_info.GetBuildProp("ro.build.date.utc")
932 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700933 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700934
Tao Bao481bab82017-12-21 11:23:09 -0800935 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700936 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800937
Tianjie Xuf67dd802019-05-20 17:50:36 -0700938 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
939 target_info=target_info,
940 source_info=None,
941 device_specific=device_specific)
942
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800943 # Two-step package strategy (in chronological order, which is *not*
944 # the order in which the generated script has things):
945 #
946 # if stage is not "2/3" or "3/3":
947 # write recovery image to boot partition
948 # set stage to "2/3"
949 # reboot to boot partition and restart recovery
950 # else if stage is "2/3":
951 # write recovery image to recovery partition
952 # set stage to "3/3"
953 # reboot to recovery partition and restart recovery
954 # else:
955 # (stage must be "3/3")
956 # set stage to ""
957 # do normal full package installation:
958 # wipe and install system, boot image, etc.
959 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700960 # complete script normally
961 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800962
963 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
964 OPTIONS.input_tmp, "RECOVERY")
965 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800966 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800967 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800968 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800969 assert fs.fs_type.upper() == "EMMC", \
970 "two-step packages only supported on devices with EMMC /misc partitions"
971 bcb_dev = {"bcb_dev": fs.device}
972 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
973 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700974if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800975""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800976
977 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
978 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800979 script.WriteRawImage("/recovery", "recovery.img")
980 script.AppendExtra("""
981set_stage("%(bcb_dev)s", "3/3");
982reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700983else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800984""" % bcb_dev)
985
Tao Baod42e97e2016-11-30 12:11:57 -0800986 # Stage 3/3: Make changes.
987 script.Comment("Stage 3/3")
988
Tao Bao6c55a8a2015-04-08 15:30:27 -0700989 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800990 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -0700991
Doug Zongkere5ff5902012-01-17 10:55:37 -0800992 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700993
Tianjie Xuf67dd802019-05-20 17:50:36 -0700994 # All other partitions as well as the data wipe use 10% of the progress, and
995 # the update of the system partition takes the remaining progress.
996 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700997 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -0800998 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -0700999 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1000 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001001
Yifan Hong10c530d2018-12-27 17:34:18 -08001002 if target_info.get('use_dynamic_partitions') == "true":
1003 # Use empty source_info_dict to indicate that all partitions / groups must
1004 # be re-added.
1005 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1006 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001007 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001008 progress_dict=progress_dict)
1009 dynamic_partitions_diff.WriteScript(script, output_zip,
1010 write_verify_script=OPTIONS.verify)
1011 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001012 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001013 block_diff.WriteScript(script, output_zip,
1014 progress=progress_dict.get(block_diff.partition),
1015 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001016
Yifan Hong9276cf02019-08-21 16:37:04 -07001017 CheckVintfIfTrebleEnabled(OPTIONS.input_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001018
Yifan Hong10c530d2018-12-27 17:34:18 -08001019 boot_img = common.GetBootableImage(
1020 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001021 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001022 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001023
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001024 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001025
Tianjie Xuf67dd802019-05-20 17:50:36 -07001026 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001027 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001028
Doug Zongker1c390a22009-05-14 19:06:36 -07001029 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001030 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001031
Doug Zongker14833602010-02-02 13:12:04 -08001032 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001033
Doug Zongker922206e2014-03-04 13:16:24 -08001034 if OPTIONS.wipe_user_data:
1035 script.ShowProgress(0.1, 10)
1036 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001037
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001038 if OPTIONS.two_step:
1039 script.AppendExtra("""
1040set_stage("%(bcb_dev)s", "");
1041""" % bcb_dev)
1042 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001043
1044 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1045 script.Comment("Stage 1/3")
1046 _WriteRecoveryImageToBoot(script, output_zip)
1047
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001048 script.AppendExtra("""
1049set_stage("%(bcb_dev)s", "2/3");
1050reboot_now("%(bcb_dev)s", "");
1051endif;
1052endif;
1053""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001054
Tao Bao5d182562016-02-23 11:38:39 -08001055 script.SetProgress(1)
1056 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001057 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001058
1059 # We haven't written the metadata entry, which will be done in
1060 # FinalizeMetadata.
1061 common.ZipClose(output_zip)
1062
1063 needed_property_files = (
1064 NonAbOtaPropertyFiles(),
1065 )
1066 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001067
Doug Zongkerfc44a512014-08-26 13:10:25 -07001068
xunchang1cfe2512019-02-19 14:14:48 -08001069def WriteMetadata(metadata, output):
1070 """Writes the metadata to the zip archive or a file.
1071
1072 Args:
1073 metadata: The metadata dict for the package.
1074 output: A ZipFile object or a string of the output file path.
1075 """
1076
Tao Bao59cf0c52019-06-25 10:04:24 -07001077 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001078 if isinstance(output, zipfile.ZipFile):
1079 common.ZipWriteStr(output, METADATA_NAME, value,
1080 compress_type=zipfile.ZIP_STORED)
1081 return
1082
1083 with open(output, 'w') as f:
1084 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001085
Doug Zongkerfc44a512014-08-26 13:10:25 -07001086
Tao Bao481bab82017-12-21 11:23:09 -08001087def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001088 # Only incremental OTAs are allowed to reach here.
1089 assert OPTIONS.incremental_source is not None
1090
Tao Bao481bab82017-12-21 11:23:09 -08001091 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1092 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001093 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001094
1095 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001096 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001097 raise RuntimeError(
1098 "--downgrade or --override_timestamp specified but no downgrade "
1099 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001100 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001101 else:
1102 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001103 raise RuntimeError(
1104 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1105 "Need to specify --override_timestamp OR --downgrade to allow "
1106 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001107
1108
Tao Baodf3a48b2018-01-10 16:30:43 -08001109def GetPackageMetadata(target_info, source_info=None):
1110 """Generates and returns the metadata dict.
1111
1112 It generates a dict() that contains the info to be written into an OTA
1113 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001114 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001115
1116 Args:
1117 target_info: The BuildInfo instance that holds the target build info.
1118 source_info: The BuildInfo instance that holds the source build info, or
1119 None if generating full OTA.
1120
1121 Returns:
1122 A dict to be written into package metadata entry.
1123 """
1124 assert isinstance(target_info, BuildInfo)
1125 assert source_info is None or isinstance(source_info, BuildInfo)
1126
1127 metadata = {
1128 'post-build' : target_info.fingerprint,
1129 'post-build-incremental' : target_info.GetBuildProp(
1130 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001131 'post-sdk-level' : target_info.GetBuildProp(
1132 'ro.build.version.sdk'),
1133 'post-security-patch-level' : target_info.GetBuildProp(
1134 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001135 }
1136
1137 if target_info.is_ab:
1138 metadata['ota-type'] = 'AB'
1139 metadata['ota-required-cache'] = '0'
1140 else:
1141 metadata['ota-type'] = 'BLOCK'
1142
1143 if OPTIONS.wipe_user_data:
1144 metadata['ota-wipe'] = 'yes'
1145
Tao Bao393eeb42019-03-06 16:00:38 -08001146 if OPTIONS.retrofit_dynamic_partitions:
1147 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1148
Tao Baodf3a48b2018-01-10 16:30:43 -08001149 is_incremental = source_info is not None
1150 if is_incremental:
1151 metadata['pre-build'] = source_info.fingerprint
1152 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1153 'ro.build.version.incremental')
1154 metadata['pre-device'] = source_info.device
1155 else:
1156 metadata['pre-device'] = target_info.device
1157
Tao Baofaa8e0b2018-04-12 14:31:43 -07001158 # Use the actual post-timestamp, even for a downgrade case.
1159 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1160
1161 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001162 if is_incremental:
1163 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001164
1165 return metadata
1166
1167
Tao Baod3fc38a2018-03-08 16:09:01 -08001168class PropertyFiles(object):
1169 """A class that computes the property-files string for an OTA package.
1170
1171 A property-files string is a comma-separated string that contains the
1172 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1173 can be fetched directly with the package URL along with the offset/size info.
1174 These strings can be used for streaming A/B OTAs, or allowing an updater to
1175 download package metadata entry directly, without paying the cost of
1176 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001177
Tao Baocc8e2662018-03-01 19:30:00 -08001178 Computing the final property-files string requires two passes. Because doing
1179 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1180 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1181 values.
1182
1183 This class provides functions to be called for each pass. The general flow is
1184 as follows.
1185
Tao Baod3fc38a2018-03-08 16:09:01 -08001186 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001187 # The first pass, which writes placeholders before doing initial signing.
1188 property_files.Compute()
1189 SignOutput()
1190
1191 # The second pass, by replacing the placeholders with actual data.
1192 property_files.Finalize()
1193 SignOutput()
1194
1195 And the caller can additionally verify the final result.
1196
1197 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001198 """
1199
Tao Baocc8e2662018-03-01 19:30:00 -08001200 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001201 self.name = None
1202 self.required = ()
1203 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001204
Tao Baocc8e2662018-03-01 19:30:00 -08001205 def Compute(self, input_zip):
1206 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001207
Tao Baocc8e2662018-03-01 19:30:00 -08001208 We reserve extra space for the offset and size of the metadata entry itself,
1209 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001210
Tao Baocc8e2662018-03-01 19:30:00 -08001211 Args:
1212 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001213
Tao Baocc8e2662018-03-01 19:30:00 -08001214 Returns:
1215 A string with placeholders for the metadata offset/size info, e.g.
1216 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1217 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001218 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001219
Tao Baod2ce2ed2018-03-16 12:59:42 -07001220 class InsufficientSpaceException(Exception):
1221 pass
1222
Tao Baocc8e2662018-03-01 19:30:00 -08001223 def Finalize(self, input_zip, reserved_length):
1224 """Finalizes a property-files string with actual METADATA offset/size info.
1225
1226 The input ZIP file has been signed, with the ZIP entries in the desired
1227 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1228 the ZIP entry offsets and construct the property-files string with actual
1229 data. Note that during this process, we must pad the property-files string
1230 to the reserved length, so that the METADATA entry size remains the same.
1231 Otherwise the entries' offsets and sizes may change again.
1232
1233 Args:
1234 input_zip: The input ZIP file.
1235 reserved_length: The reserved length of the property-files string during
1236 the call to Compute(). The final string must be no more than this
1237 size.
1238
1239 Returns:
1240 A property-files string including the metadata offset/size info, e.g.
1241 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1242
1243 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001244 InsufficientSpaceException: If the reserved length is insufficient to hold
1245 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001246 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001247 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001248 if len(result) > reserved_length:
1249 raise self.InsufficientSpaceException(
1250 'Insufficient reserved space: reserved={}, actual={}'.format(
1251 reserved_length, len(result)))
1252
Tao Baocc8e2662018-03-01 19:30:00 -08001253 result += ' ' * (reserved_length - len(result))
1254 return result
1255
1256 def Verify(self, input_zip, expected):
1257 """Verifies the input ZIP file contains the expected property-files string.
1258
1259 Args:
1260 input_zip: The input ZIP file.
1261 expected: The property-files string that's computed from Finalize().
1262
1263 Raises:
1264 AssertionError: On finding a mismatch.
1265 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001266 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001267 assert actual == expected, \
1268 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1269
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001270 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1271 """
1272 Constructs the property-files string per request.
1273
1274 Args:
1275 zip_file: The input ZIP file.
1276 reserved_length: The reserved length of the property-files string.
1277
1278 Returns:
1279 A property-files string including the metadata offset/size info, e.g.
1280 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1281 """
Tao Baocc8e2662018-03-01 19:30:00 -08001282
1283 def ComputeEntryOffsetSize(name):
1284 """Computes the zip entry offset and size."""
1285 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001286 offset = info.header_offset
1287 offset += zipfile.sizeFileHeader
1288 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001289 size = info.file_size
1290 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1291
1292 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001293 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001294 for entry in self.required:
1295 tokens.append(ComputeEntryOffsetSize(entry))
1296 for entry in self.optional:
1297 if entry in zip_file.namelist():
1298 tokens.append(ComputeEntryOffsetSize(entry))
1299
1300 # 'META-INF/com/android/metadata' is required. We don't know its actual
1301 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001302 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1303 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1304 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1305 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001306 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001307 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001308 else:
1309 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1310
1311 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001312
Tao Bao85f16982018-03-08 16:28:33 -08001313 def _GetPrecomputed(self, input_zip):
1314 """Computes the additional tokens to be included into the property-files.
1315
1316 This applies to tokens without actual ZIP entries, such as
1317 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1318 that they can download the payload metadata directly with the info.
1319
1320 Args:
1321 input_zip: The input zip file.
1322
1323 Returns:
1324 A list of strings (tokens) to be added to the property-files string.
1325 """
1326 # pylint: disable=no-self-use
1327 # pylint: disable=unused-argument
1328 return []
1329
Tao Baofe5b69a2018-03-02 09:47:43 -08001330
Tao Baod3fc38a2018-03-08 16:09:01 -08001331class StreamingPropertyFiles(PropertyFiles):
1332 """A subclass for computing the property-files for streaming A/B OTAs."""
1333
1334 def __init__(self):
1335 super(StreamingPropertyFiles, self).__init__()
1336 self.name = 'ota-streaming-property-files'
1337 self.required = (
1338 # payload.bin and payload_properties.txt must exist.
1339 'payload.bin',
1340 'payload_properties.txt',
1341 )
1342 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001343 # care_map is available only if dm-verity is enabled.
1344 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001345 'care_map.txt',
1346 # compatibility.zip is available only if target supports Treble.
1347 'compatibility.zip',
1348 )
1349
1350
Tao Bao85f16982018-03-08 16:28:33 -08001351class AbOtaPropertyFiles(StreamingPropertyFiles):
1352 """The property-files for A/B OTA that includes payload_metadata.bin info.
1353
1354 Since P, we expose one more token (aka property-file), in addition to the ones
1355 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1356 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1357 doesn't exist as a separate ZIP entry, but can be used to verify if the
1358 payload can be applied on the given device.
1359
1360 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1361 and the newly added 'ota-property-files' in P. The new token will only be
1362 available in 'ota-property-files'.
1363 """
1364
1365 def __init__(self):
1366 super(AbOtaPropertyFiles, self).__init__()
1367 self.name = 'ota-property-files'
1368
1369 def _GetPrecomputed(self, input_zip):
1370 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1371 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1372
1373 @staticmethod
1374 def _GetPayloadMetadataOffsetAndSize(input_zip):
1375 """Computes the offset and size of the payload metadata for a given package.
1376
1377 (From system/update_engine/update_metadata.proto)
1378 A delta update file contains all the deltas needed to update a system from
1379 one specific version to another specific version. The update format is
1380 represented by this struct pseudocode:
1381
1382 struct delta_update_file {
1383 char magic[4] = "CrAU";
1384 uint64 file_format_version;
1385 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1386
1387 // Only present if format_version > 1:
1388 uint32 metadata_signature_size;
1389
1390 // The Bzip2 compressed DeltaArchiveManifest
1391 char manifest[metadata_signature_size];
1392
1393 // The signature of the metadata (from the beginning of the payload up to
1394 // this location, not including the signature itself). This is a
1395 // serialized Signatures message.
1396 char medatada_signature_message[metadata_signature_size];
1397
1398 // Data blobs for files, no specific format. The specific offset
1399 // and length of each data blob is recorded in the DeltaArchiveManifest.
1400 struct {
1401 char data[];
1402 } blobs[];
1403
1404 // These two are not signed:
1405 uint64 payload_signatures_message_size;
1406 char payload_signatures_message[];
1407 };
1408
1409 'payload-metadata.bin' contains all the bytes from the beginning of the
1410 payload, till the end of 'medatada_signature_message'.
1411 """
1412 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001413 payload_offset = payload_info.header_offset
1414 payload_offset += zipfile.sizeFileHeader
1415 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001416 payload_size = payload_info.file_size
1417
Tao Bao59cf0c52019-06-25 10:04:24 -07001418 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001419 header_bin = payload_fp.read(24)
1420
1421 # network byte order (big-endian)
1422 header = struct.unpack("!IQQL", header_bin)
1423
1424 # 'CrAU'
1425 magic = header[0]
1426 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1427
1428 manifest_size = header[2]
1429 metadata_signature_size = header[3]
1430 metadata_total = 24 + manifest_size + metadata_signature_size
1431 assert metadata_total < payload_size
1432
1433 return (payload_offset, metadata_total)
1434
1435
Tao Bao491d7e22018-02-21 13:17:22 -08001436class NonAbOtaPropertyFiles(PropertyFiles):
1437 """The property-files for non-A/B OTA.
1438
1439 For non-A/B OTA, the property-files string contains the info for METADATA
1440 entry, with which a system updater can be fetched the package metadata prior
1441 to downloading the entire package.
1442 """
1443
1444 def __init__(self):
1445 super(NonAbOtaPropertyFiles, self).__init__()
1446 self.name = 'ota-property-files'
1447
1448
Tao Baod3fc38a2018-03-08 16:09:01 -08001449def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001450 """Finalizes the metadata and signs an A/B OTA package.
1451
1452 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1453 that contains the offsets and sizes for the ZIP entries. An example
1454 property-files string is as follows.
1455
1456 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1457
1458 OTA server can pass down this string, in addition to the package URL, to the
1459 system update client. System update client can then fetch individual ZIP
1460 entries (ZIP_STORED) directly at the given offset of the URL.
1461
1462 Args:
1463 metadata: The metadata dict for the package.
1464 input_file: The input ZIP filename that doesn't contain the package METADATA
1465 entry yet.
1466 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001467 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001468 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001469
Tao Baod2ce2ed2018-03-16 12:59:42 -07001470 def ComputeAllPropertyFiles(input_file, needed_property_files):
1471 # Write the current metadata entry with placeholders.
1472 with zipfile.ZipFile(input_file) as input_zip:
1473 for property_files in needed_property_files:
1474 metadata[property_files.name] = property_files.Compute(input_zip)
1475 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001476
Tao Baod2ce2ed2018-03-16 12:59:42 -07001477 if METADATA_NAME in namelist:
1478 common.ZipDelete(input_file, METADATA_NAME)
1479 output_zip = zipfile.ZipFile(input_file, 'a')
1480 WriteMetadata(metadata, output_zip)
1481 common.ZipClose(output_zip)
1482
1483 if OPTIONS.no_signing:
1484 return input_file
1485
Tao Bao491d7e22018-02-21 13:17:22 -08001486 prelim_signing = common.MakeTempFile(suffix='.zip')
1487 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001488 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001489
Tao Baod2ce2ed2018-03-16 12:59:42 -07001490 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1491 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1492 for property_files in needed_property_files:
1493 metadata[property_files.name] = property_files.Finalize(
1494 prelim_signing_zip, len(metadata[property_files.name]))
1495
1496 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1497 # entries, as well as padding the entry headers. We do a preliminary signing
1498 # (with an incomplete metadata entry) to allow that to happen. Then compute
1499 # the ZIP entry offsets, write back the final metadata and do the final
1500 # signing.
1501 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1502 try:
1503 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1504 except PropertyFiles.InsufficientSpaceException:
1505 # Even with the preliminary signing, the entry orders may change
1506 # dramatically, which leads to insufficiently reserved space during the
1507 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1508 # preliminary signing works, based on the already ordered ZIP entries, to
1509 # address the issue.
1510 prelim_signing = ComputeAllPropertyFiles(
1511 prelim_signing, needed_property_files)
1512 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001513
1514 # Replace the METADATA entry.
1515 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001516 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001517 WriteMetadata(metadata, output_zip)
1518 common.ZipClose(output_zip)
1519
1520 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001521 if OPTIONS.no_signing:
1522 output_file = prelim_signing
1523 else:
1524 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001525
1526 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001527 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001528 for property_files in needed_property_files:
1529 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001530
xunchang1cfe2512019-02-19 14:14:48 -08001531 # If requested, dump the metadata to a separate file.
1532 output_metadata_path = OPTIONS.output_metadata_path
1533 if output_metadata_path:
1534 WriteMetadata(metadata, output_metadata_path)
1535
Tao Baofe5b69a2018-03-02 09:47:43 -08001536
Tao Bao491d7e22018-02-21 13:17:22 -08001537def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001538 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1539 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001540
Tao Bao481bab82017-12-21 11:23:09 -08001541 target_api_version = target_info["recovery_api_version"]
1542 source_api_version = source_info["recovery_api_version"]
1543 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001544 logger.warning(
1545 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001546
Tao Bao481bab82017-12-21 11:23:09 -08001547 script = edify_generator.EdifyGenerator(
1548 source_api_version, target_info, fstab=source_info["fstab"])
1549
1550 if target_info.oem_props or source_info.oem_props:
1551 if not OPTIONS.oem_no_mount:
1552 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001553
Tao Baodf3a48b2018-01-10 16:30:43 -08001554 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001555
Tao Bao491d7e22018-02-21 13:17:22 -08001556 if not OPTIONS.no_signing:
1557 staging_file = common.MakeTempFile(suffix='.zip')
1558 else:
1559 staging_file = output_file
1560
1561 output_zip = zipfile.ZipFile(
1562 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1563
Geremy Condra36bd3652014-02-06 19:45:10 -08001564 device_specific = common.DeviceSpecificParams(
1565 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001566 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001567 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001568 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001569 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001570 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001571 output_zip=output_zip,
1572 script=script,
1573 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001574 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001575
Geremy Condra36bd3652014-02-06 19:45:10 -08001576 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001577 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001578 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001579 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001580 updating_boot = (not OPTIONS.two_step and
1581 (source_boot.data != target_boot.data))
1582
Geremy Condra36bd3652014-02-06 19:45:10 -08001583 target_recovery = common.GetBootableImage(
1584 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001585
Tianjie Xuf67dd802019-05-20 17:50:36 -07001586 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1587 source_zip=source_zip,
1588 target_info=target_info,
1589 source_info=source_info,
1590 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001591
Yifan Hong9276cf02019-08-21 16:37:04 -07001592 CheckVintfIfTrebleEnabled(OPTIONS.target_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001593
Tao Bao481bab82017-12-21 11:23:09 -08001594 # Assertions (e.g. device properties check).
1595 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001596 device_specific.IncrementalOTA_Assertions()
1597
1598 # Two-step incremental package strategy (in chronological order,
1599 # which is *not* the order in which the generated script has
1600 # things):
1601 #
1602 # if stage is not "2/3" or "3/3":
1603 # do verification on current system
1604 # write recovery image to boot partition
1605 # set stage to "2/3"
1606 # reboot to boot partition and restart recovery
1607 # else if stage is "2/3":
1608 # write recovery image to recovery partition
1609 # set stage to "3/3"
1610 # reboot to recovery partition and restart recovery
1611 # else:
1612 # (stage must be "3/3")
1613 # perform update:
1614 # patch system files, etc.
1615 # force full install of new boot image
1616 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001617 # complete script normally
1618 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001619
1620 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001621 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001622 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001623 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001624 assert fs.fs_type.upper() == "EMMC", \
1625 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001626 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001627 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1628 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001629if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001630""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001631
1632 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1633 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001634 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001635 script.WriteRawImage("/recovery", "recovery.img")
1636 script.AppendExtra("""
1637set_stage("%(bcb_dev)s", "3/3");
1638reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001639else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001640""" % bcb_dev)
1641
Tao Baod42e97e2016-11-30 12:11:57 -08001642 # Stage 1/3: (a) Verify the current system.
1643 script.Comment("Stage 1/3")
1644
Tao Bao6c55a8a2015-04-08 15:30:27 -07001645 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001646 script.Print("Source: {}".format(source_info.fingerprint))
1647 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001648
Geremy Condra36bd3652014-02-06 19:45:10 -08001649 script.Print("Verifying current system...")
1650
1651 device_specific.IncrementalOTA_VerifyBegin()
1652
Tao Bao481bab82017-12-21 11:23:09 -08001653 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001654
Tao Baod8d14be2016-02-04 14:26:02 -08001655 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001656 required_cache_sizes = [diff.required_cache for diff in
1657 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001658 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001659 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001660 d = common.Difference(target_boot, source_boot)
1661 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001662 if d is None:
1663 include_full_boot = True
1664 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1665 else:
1666 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001667
Tao Bao32fcdab2018-10-12 10:30:39 -07001668 logger.info(
1669 "boot target: %d source: %d diff: %d", target_boot.size,
1670 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001671
Tao Bao51216552018-08-26 11:53:15 -07001672 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001673
Tao Bao51216552018-08-26 11:53:15 -07001674 script.PatchPartitionCheck(
1675 "{}:{}:{}:{}".format(
1676 boot_type, boot_device, target_boot.size, target_boot.sha1),
1677 "{}:{}:{}:{}".format(
1678 boot_type, boot_device, source_boot.size, source_boot.sha1))
1679
Tianjie Xuf67dd802019-05-20 17:50:36 -07001680 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001681
Tianjie Xuf67dd802019-05-20 17:50:36 -07001682 if required_cache_sizes:
1683 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1684
1685 # Verify the existing partitions.
1686 for diff in block_diff_dict.values():
1687 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001688
1689 device_specific.IncrementalOTA_VerifyEnd()
1690
1691 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001692 # Stage 1/3: (b) Write recovery image to /boot.
1693 _WriteRecoveryImageToBoot(script, output_zip)
1694
Geremy Condra36bd3652014-02-06 19:45:10 -08001695 script.AppendExtra("""
1696set_stage("%(bcb_dev)s", "2/3");
1697reboot_now("%(bcb_dev)s", "");
1698else
1699""" % bcb_dev)
1700
Tao Baod42e97e2016-11-30 12:11:57 -08001701 # Stage 3/3: Make changes.
1702 script.Comment("Stage 3/3")
1703
Geremy Condra36bd3652014-02-06 19:45:10 -08001704 script.Comment("---- start making changes here ----")
1705
1706 device_specific.IncrementalOTA_InstallBegin()
1707
Tianjie Xuf67dd802019-05-20 17:50:36 -07001708 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1709 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001710
1711 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1712 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1713 raise RuntimeError(
1714 "can't generate incremental that disables dynamic partitions")
1715 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1716 info_dict=OPTIONS.target_info_dict,
1717 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001718 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001719 progress_dict=progress_dict)
1720 dynamic_partitions_diff.WriteScript(
1721 script, output_zip, write_verify_script=OPTIONS.verify)
1722 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001723 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001724 block_diff.WriteScript(script, output_zip,
1725 progress=progress_dict.get(block_diff.partition),
1726 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001727
1728 if OPTIONS.two_step:
1729 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1730 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001731 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001732
1733 if not OPTIONS.two_step:
1734 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001735 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001736 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001737 script.Print("Installing boot image...")
1738 script.WriteRawImage("/boot", "boot.img")
1739 else:
1740 # Produce the boot image by applying a patch to the current
1741 # contents of the boot partition, and write it back to the
1742 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001743 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001744 script.Print("Patching boot image...")
1745 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001746 script.PatchPartition(
1747 '{}:{}:{}:{}'.format(
1748 boot_type, boot_device, target_boot.size, target_boot.sha1),
1749 '{}:{}:{}:{}'.format(
1750 boot_type, boot_device, source_boot.size, source_boot.sha1),
1751 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001752 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001753 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001754
1755 # Do device-specific installation (eg, write radio image).
1756 device_specific.IncrementalOTA_InstallEnd()
1757
1758 if OPTIONS.extra_script is not None:
1759 script.AppendExtra(OPTIONS.extra_script)
1760
Doug Zongker922206e2014-03-04 13:16:24 -08001761 if OPTIONS.wipe_user_data:
1762 script.Print("Erasing user data...")
1763 script.FormatPartition("/data")
1764
Geremy Condra36bd3652014-02-06 19:45:10 -08001765 if OPTIONS.two_step:
1766 script.AppendExtra("""
1767set_stage("%(bcb_dev)s", "");
1768endif;
1769endif;
1770""" % bcb_dev)
1771
1772 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001773 # For downgrade OTAs, we prefer to use the update-binary in the source
1774 # build that is actually newer than the one in the target build.
1775 if OPTIONS.downgrade:
1776 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1777 else:
1778 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001779 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001780
1781 # We haven't written the metadata entry yet, which will be handled in
1782 # FinalizeMetadata().
1783 common.ZipClose(output_zip)
1784
1785 # Sign the generated zip package unless no_signing is specified.
1786 needed_property_files = (
1787 NonAbOtaPropertyFiles(),
1788 )
1789 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001790
Doug Zongker32b527d2014-03-04 10:03:02 -08001791
Tao Bao15a146a2018-02-21 16:06:59 -08001792def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001793 """Returns a target-files.zip file for generating secondary payload.
1794
1795 Although the original target-files.zip already contains secondary slot
1796 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1797 ones without _other suffix. Note that we cannot instead modify the names in
1798 META/ab_partitions.txt, because there are no matching partitions on device.
1799
1800 For the partitions that don't have secondary images, the ones for primary
1801 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1802 bootloader images in the inactive slot.
1803
1804 Args:
1805 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001806 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001807
1808 Returns:
1809 The filename of the target-files.zip for generating secondary payload.
1810 """
Tianjie Xu1c808002019-09-11 00:29:26 -07001811
1812 def GetInfoForSecondaryImages(info_file):
1813 """Updates info file for secondary payload generation.
1814
1815 Scan each line in the info file, and remove the unwanted partitions from
1816 the dynamic partition list in the related properties. e.g.
1817 "super_google_dynamic_partitions_partition_list=system vendor product"
1818 will become "super_google_dynamic_partitions_partition_list=system".
1819
1820 Args:
1821 info_file: The input info file. e.g. misc_info.txt.
1822
1823 Returns:
1824 A string of the updated info content.
1825 """
1826
1827 output_list = []
1828 with open(info_file) as f:
1829 lines = f.read().splitlines()
1830
1831 # The suffix in partition_list variables that follows the name of the
1832 # partition group.
1833 LIST_SUFFIX = 'partition_list'
1834 for line in lines:
1835 if line.startswith('#') or '=' not in line:
1836 output_list.append(line)
1837 continue
1838 key, value = line.strip().split('=', 1)
1839 if key == 'dynamic_partition_list' or key.endswith(LIST_SUFFIX):
1840 partitions = value.split()
1841 partitions = [partition for partition in partitions if partition
Tao Bao3e759462019-09-17 22:43:11 -07001842 not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Tianjie Xu1c808002019-09-11 00:29:26 -07001843 output_list.append('{}={}'.format(key, ' '.join(partitions)))
1844 else:
1845 output_list.append(line)
1846 return '\n'.join(output_list)
1847
Tao Baof7140c02018-01-30 17:09:24 -08001848 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1849 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1850
Tao Baodba59ee2018-01-09 13:21:02 -08001851 with zipfile.ZipFile(input_file, 'r') as input_zip:
1852 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001853
Tao Bao0ff15de2019-03-20 11:26:06 -07001854 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001855 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001856 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1857 if info.filename == 'IMAGES/system_other.img':
1858 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1859
1860 # Primary images and friends need to be skipped explicitly.
1861 elif info.filename in ('IMAGES/system.img',
1862 'IMAGES/system.map'):
1863 pass
Tao Bao3e759462019-09-17 22:43:11 -07001864
1865 # Copy images that are not in SECONDARY_PAYLOAD_SKIPPED_IMAGES.
1866 elif info.filename.startswith(('IMAGES/', 'RADIO/')):
1867 image_name = os.path.basename(info.filename)
1868 if image_name not in ['{}.img'.format(partition) for partition in
1869 SECONDARY_PAYLOAD_SKIPPED_IMAGES]:
1870 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001871
Tao Bao15a146a2018-02-21 16:06:59 -08001872 # Skip copying the postinstall config if requested.
1873 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1874 pass
1875
Tianjie Xu1c808002019-09-11 00:29:26 -07001876 elif info.filename.startswith('META/'):
1877 # Remove the unnecessary partitions for secondary images from the
1878 # ab_partitions file.
1879 if info.filename == AB_PARTITIONS:
1880 with open(unzipped_file) as f:
1881 partition_list = f.read().splitlines()
1882 partition_list = [partition for partition in partition_list if partition
Tao Bao3e759462019-09-17 22:43:11 -07001883 and partition not in SECONDARY_PAYLOAD_SKIPPED_IMAGES]
Tianjie Xu1c808002019-09-11 00:29:26 -07001884 common.ZipWriteStr(target_zip, info.filename, '\n'.join(partition_list))
1885 # Remove the unnecessary partitions from the dynamic partitions list.
1886 elif (info.filename == 'META/misc_info.txt' or
1887 info.filename == DYNAMIC_PARTITION_INFO):
1888 modified_info = GetInfoForSecondaryImages(unzipped_file)
1889 common.ZipWriteStr(target_zip, info.filename, modified_info)
1890 else:
1891 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
Tao Baof7140c02018-01-30 17:09:24 -08001892
Tao Baof7140c02018-01-30 17:09:24 -08001893 common.ZipClose(target_zip)
1894
1895 return target_file
1896
1897
Tao Bao15a146a2018-02-21 16:06:59 -08001898def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1899 """Returns a target-files.zip that's not containing postinstall_config.txt.
1900
1901 This allows brillo_update_payload script to skip writing all the postinstall
1902 hooks in the generated payload. The input target-files.zip file will be
1903 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1904 contain the postinstall_config.txt entry, the input file will be returned.
1905
1906 Args:
1907 input_file: The input target-files.zip filename.
1908
1909 Returns:
1910 The filename of target-files.zip that doesn't contain postinstall config.
1911 """
1912 # We should only make a copy if postinstall_config entry exists.
1913 with zipfile.ZipFile(input_file, 'r') as input_zip:
1914 if POSTINSTALL_CONFIG not in input_zip.namelist():
1915 return input_file
1916
1917 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1918 shutil.copyfile(input_file, target_file)
1919 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1920 return target_file
1921
1922
Yifan Hong50e79542018-11-08 17:44:12 -08001923def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001924 super_block_devices,
1925 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001926 """Returns a target-files.zip for retrofitting dynamic partitions.
1927
1928 This allows brillo_update_payload to generate an OTA based on the exact
1929 bits on the block devices. Postinstall is disabled.
1930
1931 Args:
1932 input_file: The input target-files.zip filename.
1933 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001934 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001935
1936 Returns:
1937 The filename of target-files.zip with *.img replaced with super_*.img for
1938 each block device in super_block_devices.
1939 """
1940 assert super_block_devices, "No super_block_devices are specified."
1941
1942 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001943 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001944
1945 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1946 shutil.copyfile(input_file, target_file)
1947
Tao Baoa3705452019-06-24 15:33:41 -07001948 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001949 namelist = input_zip.namelist()
1950
Yifan Hongb433eba2019-03-06 12:42:53 -08001951 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1952
1953 # Remove partitions from META/ab_partitions.txt that is in
1954 # dynamic_partition_list but not in super_block_devices so that
1955 # brillo_update_payload won't generate update for those logical partitions.
1956 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1957 with open(ab_partitions_file) as f:
1958 ab_partitions_lines = f.readlines()
1959 ab_partitions = [line.strip() for line in ab_partitions_lines]
1960 # Assert that all super_block_devices are in ab_partitions
1961 super_device_not_updated = [partition for partition in super_block_devices
1962 if partition not in ab_partitions]
1963 assert not super_device_not_updated, \
1964 "{} is in super_block_devices but not in {}".format(
1965 super_device_not_updated, AB_PARTITIONS)
1966 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1967 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1968 with open(new_ab_partitions, 'w') as f:
1969 for partition in ab_partitions:
1970 if (partition in dynamic_partition_list and
1971 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001972 logger.info("Dropping %s from ab_partitions.txt", partition)
1973 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001974 f.write(partition + "\n")
1975 to_delete = [AB_PARTITIONS]
1976
Yifan Hong50e79542018-11-08 17:44:12 -08001977 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001978 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001979
1980 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1981 # is a regular update on devices without dynamic partitions support.
1982 to_delete += [DYNAMIC_PARTITION_INFO]
1983
Tao Bao03fecb62018-11-28 10:59:23 -08001984 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001985 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001986 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001987
1988 common.ZipDelete(target_file, to_delete)
1989
Yifan Hong50e79542018-11-08 17:44:12 -08001990 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1991
1992 # Write super_{foo}.img as {foo}.img.
1993 for src, dst in replace.items():
1994 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001995 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001996 unzipped_file = os.path.join(input_tmp, *src.split('/'))
1997 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
1998
Yifan Hongb433eba2019-03-06 12:42:53 -08001999 # Write new ab_partitions.txt file
2000 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
2001
Yifan Hong50e79542018-11-08 17:44:12 -08002002 common.ZipClose(target_zip)
2003
2004 return target_file
2005
2006
Tao Baof0c4aa22018-04-30 20:29:30 -07002007def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002008 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002009 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002010 if not OPTIONS.no_signing:
2011 staging_file = common.MakeTempFile(suffix='.zip')
2012 else:
2013 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002014 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002015 compression=zipfile.ZIP_DEFLATED)
2016
Tao Bao481bab82017-12-21 11:23:09 -08002017 if source_file is not None:
2018 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2019 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2020 else:
2021 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2022 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002023
Tao Bao481bab82017-12-21 11:23:09 -08002024 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002025 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002026
Yifan Hong50e79542018-11-08 17:44:12 -08002027 if OPTIONS.retrofit_dynamic_partitions:
2028 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002029 target_file, target_info.get("super_block_devices").strip().split(),
2030 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002031 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002032 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2033
Tao Bao40b18822018-01-30 18:19:04 -08002034 # Generate payload.
2035 payload = Payload()
2036
2037 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002038 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002039 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002040 else:
2041 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002042 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002043
Tao Bao40b18822018-01-30 18:19:04 -08002044 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002045
Tao Bao40b18822018-01-30 18:19:04 -08002046 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002047 payload_signer = PayloadSigner()
2048 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002049
Tao Bao40b18822018-01-30 18:19:04 -08002050 # Write the payload into output zip.
2051 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002052
Tao Baof7140c02018-01-30 17:09:24 -08002053 # Generate and include the secondary payload that installs secondary images
2054 # (e.g. system_other.img).
2055 if OPTIONS.include_secondary:
2056 # We always include a full payload for the secondary slot, even when
2057 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002058 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2059 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002060 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002061 secondary_payload.Generate(secondary_target_file,
2062 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002063 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002064 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002065
Tianjie Xucfa86222016-03-07 16:31:19 -08002066 # If dm-verity is supported for the device, copy contents of care_map
2067 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002068 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002069 if (target_info.get("verity") == "true" or
2070 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002071 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2072 "META/" + x in target_zip.namelist()]
2073
2074 # Adds care_map if either the protobuf format or the plain text one exists.
2075 if care_map_list:
2076 care_map_name = care_map_list[0]
2077 care_map_data = target_zip.read("META/" + care_map_name)
2078 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002079 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002080 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002081 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002082 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002083 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002084
Tao Bao21803d32017-04-19 10:16:09 -07002085 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08002086
Yifan Hong9276cf02019-08-21 16:37:04 -07002087 CheckVintfIfTrebleEnabled(target_file, target_info)
2088
Tao Baofe5b69a2018-03-02 09:47:43 -08002089 # We haven't written the metadata entry yet, which will be handled in
2090 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002091 common.ZipClose(output_zip)
2092
Tao Bao85f16982018-03-08 16:28:33 -08002093 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2094 # all the info of the latter. However, system updaters and OTA servers need to
2095 # take time to switch to the new flag. We keep both of the flags for
2096 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002097 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002098 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002099 StreamingPropertyFiles(),
2100 )
2101 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002102
Tao Baoc098e9e2016-01-07 13:03:56 -08002103
Tao Baof0c4aa22018-04-30 20:29:30 -07002104def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
2105 """Generates a non-A/B OTA package."""
2106 # Sanity check the loaded info dicts first.
2107 if OPTIONS.info_dict.get("no_recovery") == "true":
2108 raise common.ExternalError(
2109 "--- target build has specified no recovery ---")
2110
2111 # Non-A/B OTAs rely on /cache partition to store temporary files.
2112 cache_size = OPTIONS.info_dict.get("cache_size")
2113 if cache_size is None:
2114 logger.warning("--- can't determine the cache partition size ---")
2115 OPTIONS.cache_size = cache_size
2116
2117 if OPTIONS.extra_script is not None:
2118 with open(OPTIONS.extra_script) as fp:
2119 OPTIONS.extra_script = fp.read()
2120
2121 if OPTIONS.extracted_input is not None:
2122 OPTIONS.input_tmp = OPTIONS.extracted_input
2123 else:
2124 logger.info("unzipping target target-files...")
2125 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
2126 OPTIONS.target_tmp = OPTIONS.input_tmp
2127
2128 # If the caller explicitly specified the device-specific extensions path via
2129 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2130 # is present in the target target_files. Otherwise, take the path of the file
2131 # from 'tool_extensions' in the info dict and look for that in the local
2132 # filesystem, relative to the current directory.
2133 if OPTIONS.device_specific is None:
2134 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2135 if os.path.exists(from_input):
2136 logger.info("(using device-specific extensions from target_files)")
2137 OPTIONS.device_specific = from_input
2138 else:
2139 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
2140
2141 if OPTIONS.device_specific is not None:
2142 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
2143
2144 # Generate a full OTA.
2145 if source_file is None:
2146 with zipfile.ZipFile(target_file) as input_zip:
2147 WriteFullOTAPackage(
2148 input_zip,
2149 output_file)
2150
2151 # Generate an incremental OTA.
2152 else:
2153 logger.info("unzipping source target-files...")
2154 OPTIONS.source_tmp = common.UnzipTemp(
2155 OPTIONS.incremental_source, UNZIP_PATTERN)
2156 with zipfile.ZipFile(target_file) as input_zip, \
2157 zipfile.ZipFile(source_file) as source_zip:
2158 WriteBlockIncrementalOTAPackage(
2159 input_zip,
2160 source_zip,
2161 output_file)
2162
2163
Doug Zongkereef39442009-04-02 12:14:19 -07002164def main(argv):
2165
2166 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002167 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002168 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002169 elif o in ("-i", "--incremental_from"):
2170 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002171 elif o == "--full_radio":
2172 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002173 elif o == "--full_bootloader":
2174 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002175 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002176 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002177 elif o == "--downgrade":
2178 OPTIONS.downgrade = True
2179 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002180 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002181 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002182 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002183 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002184 elif o == "--oem_no_mount":
2185 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002186 elif o in ("-e", "--extra_script"):
2187 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002188 elif o in ("-t", "--worker_threads"):
2189 if a.isdigit():
2190 OPTIONS.worker_threads = int(a)
2191 else:
2192 raise ValueError("Cannot parse value %r for option %r - only "
2193 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002194 elif o in ("-2", "--two_step"):
2195 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002196 elif o == "--include_secondary":
2197 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002198 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002199 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002200 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002201 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002202 elif o == "--block":
2203 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002204 elif o in ("-b", "--binary"):
2205 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002206 elif o == "--stash_threshold":
2207 try:
2208 OPTIONS.stash_threshold = float(a)
2209 except ValueError:
2210 raise ValueError("Cannot parse value %r for option %r - expecting "
2211 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002212 elif o == "--log_diff":
2213 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002214 elif o == "--payload_signer":
2215 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002216 elif o == "--payload_signer_args":
2217 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002218 elif o == "--payload_signer_key_size":
2219 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002220 elif o == "--extracted_input_target_files":
2221 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002222 elif o == "--skip_postinstall":
2223 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002224 elif o == "--retrofit_dynamic_partitions":
2225 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002226 elif o == "--skip_compatibility_check":
2227 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002228 elif o == "--output_metadata_path":
2229 OPTIONS.output_metadata_path = a
Tianjie Xu1b079832019-08-28 12:19:23 -07002230 elif o == "--disable_fec_computation":
2231 OPTIONS.disable_fec_computation = True
Doug Zongkereef39442009-04-02 12:14:19 -07002232 else:
2233 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002234 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002235
2236 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002237 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002238 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002239 "package_key=",
2240 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002241 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002242 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002243 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002244 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002245 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002246 "extra_script=",
2247 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002248 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002249 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002250 "no_signing",
2251 "block",
2252 "binary=",
2253 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002254 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002255 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002256 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002257 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002258 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002259 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002260 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002261 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002262 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002263 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002264 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002265 "output_metadata_path=",
Tianjie Xu1b079832019-08-28 12:19:23 -07002266 "disable_fec_computation",
Dan Albert8b72aef2015-03-23 19:13:21 -07002267 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002268
2269 if len(args) != 2:
2270 common.Usage(__doc__)
2271 sys.exit(1)
2272
Tao Bao32fcdab2018-10-12 10:30:39 -07002273 common.InitLogging()
2274
Tao Bao5d182562016-02-23 11:38:39 -08002275 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002276 # We should only allow downgrading incrementals (as opposed to full).
2277 # Otherwise the device may go back from arbitrary build with this full
2278 # OTA package.
2279 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002280 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002281
Tao Bao2db13852018-01-08 22:28:57 -08002282 # Load the build info dicts from the zip directly or the extracted input
2283 # directory. We don't need to unzip the entire target-files zips, because they
2284 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2285 # When loading the info dicts, we don't need to provide the second parameter
2286 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2287 # some properties with their actual paths, such as 'selinux_fc',
2288 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002289 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002290 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002291 else:
Tao Bao2db13852018-01-08 22:28:57 -08002292 with zipfile.ZipFile(args[0], 'r') as input_zip:
2293 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002294
Tao Bao32fcdab2018-10-12 10:30:39 -07002295 logger.info("--- target info ---")
2296 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002297
2298 # Load the source build dict if applicable.
2299 if OPTIONS.incremental_source is not None:
2300 OPTIONS.target_info_dict = OPTIONS.info_dict
2301 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2302 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2303
Tao Bao32fcdab2018-10-12 10:30:39 -07002304 logger.info("--- source info ---")
2305 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002306
2307 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002308 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2309
Yifan Hong50e79542018-11-08 17:44:12 -08002310 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002311 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002312 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002313 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2314 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002315 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2316 raise common.ExternalError(
2317 "Expect to generate incremental OTA for retrofitting dynamic "
2318 "partitions, but dynamic_partition_retrofit is not set in target "
2319 "build.")
2320 logger.info("Implicitly generating retrofit incremental OTA.")
2321 OPTIONS.retrofit_dynamic_partitions = True
2322
2323 # Skip postinstall for retrofitting dynamic partitions.
2324 if OPTIONS.retrofit_dynamic_partitions:
2325 OPTIONS.skip_postinstall = True
2326
Tao Baoc098e9e2016-01-07 13:03:56 -08002327 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2328
Christian Oderf63e2cd2017-05-01 22:30:15 +02002329 # Use the default key to sign the package if not specified with package_key.
2330 # package_keys are needed on ab_updates, so always define them if an
2331 # ab_update is getting created.
2332 if not OPTIONS.no_signing or ab_update:
2333 if OPTIONS.package_key is None:
2334 OPTIONS.package_key = OPTIONS.info_dict.get(
2335 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002336 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002337 # Get signing keys
2338 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2339
Tao Baoc098e9e2016-01-07 13:03:56 -08002340 if ab_update:
Tao Baof0c4aa22018-04-30 20:29:30 -07002341 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002342 target_file=args[0],
2343 output_file=args[1],
2344 source_file=OPTIONS.incremental_source)
2345
Dan Willemsencea5cd22017-03-21 14:44:27 -07002346 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002347 GenerateNonAbOtaPackage(
2348 target_file=args[0],
2349 output_file=args[1],
2350 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002351
Tao Baof0c4aa22018-04-30 20:29:30 -07002352 # Post OTA generation works.
2353 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2354 logger.info("Generating diff logs...")
2355 logger.info("Unzipping target-files for diffing...")
2356 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2357 source_dir = common.UnzipTemp(
2358 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002359
Tao Baof0c4aa22018-04-30 20:29:30 -07002360 with open(OPTIONS.log_diff, 'w') as out_file:
2361 import target_files_diff
2362 target_files_diff.recursiveDiff(
2363 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002364
Tao Bao32fcdab2018-10-12 10:30:39 -07002365 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002366
2367
2368if __name__ == '__main__':
2369 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002370 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002371 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002372 except common.ExternalError:
2373 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002374 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002375 finally:
2376 common.Cleanup()