blob: c6c859c85624e1cb3dc1220b2b9e6c09e6dbfaec [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
Tao Bao30df8b42018-04-23 15:32:53 -070018Given a target-files zipfile, produces an OTA package that installs that build.
19An incremental OTA is produced if -i is given, otherwise a full OTA is produced.
Doug Zongkereef39442009-04-02 12:14:19 -070020
Tao Bao30df8b42018-04-23 15:32:53 -070021Usage: ota_from_target_files [options] input_target_files output_ota_package
Doug Zongkereef39442009-04-02 12:14:19 -070022
Tao Bao30df8b42018-04-23 15:32:53 -070023Common options that apply to both of non-A/B and A/B OTAs
24
25 --downgrade
26 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070027 to an older one (e.g. downgrading from P preview back to O MR1).
28 "ota-downgrade=yes" will be set in the package metadata file. A data wipe
29 will always be enforced when using this flag, so "ota-wipe=yes" will also
30 be included in the metadata file. The update-binary in the source build
31 will be used in the OTA package, unless --binary flag is specified. Please
32 also check the comment for --override_timestamp below.
Tao Bao30df8b42018-04-23 15:32:53 -070033
34 -i (--incremental_from) <file>
35 Generate an incremental OTA using the given target-files zip as the
36 starting build.
37
38 -k (--package_key) <key>
39 Key to use to sign the package (default is the value of
40 default_system_dev_certificate from the input target-files's
Tao Bao59cf0c52019-06-25 10:04:24 -070041 META/misc_info.txt, or "build/make/target/product/security/testkey" if
42 that value is not specified).
Doug Zongkerafb32ea2011-09-22 10:28:04 -070043
44 For incremental OTAs, the default value is based on the source
45 target-file, not the target build.
Doug Zongkereef39442009-04-02 12:14:19 -070046
Tao Bao30df8b42018-04-23 15:32:53 -070047 --override_timestamp
48 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070049 to an older one (based on timestamp comparison), by setting the downgrade
50 flag in the package metadata. This differs from --downgrade flag, as we
51 don't enforce a data wipe with this flag. Because we know for sure this is
52 NOT an actual downgrade case, but two builds happen to be cut in a reverse
53 order (e.g. from two branches). A legit use case is that we cut a new
54 build C (after having A and B), but want to enfore an update path of A ->
55 C -> B. Specifying --downgrade may not help since that would enforce a
56 data wipe for C -> B update.
57
58 We used to set a fake timestamp in the package metadata for this flow. But
59 now we consolidate the two cases (i.e. an actual downgrade, or a downgrade
60 based on timestamp) with the same "ota-downgrade=yes" flag, with the
61 difference being whether "ota-wipe=yes" is set.
Doug Zongkereef39442009-04-02 12:14:19 -070062
Tao Bao30df8b42018-04-23 15:32:53 -070063 --wipe_user_data
64 Generate an OTA package that will wipe the user data partition when
65 installed.
66
Yifan Hong50e79542018-11-08 17:44:12 -080067 --retrofit_dynamic_partitions
68 Generates an OTA package that updates a device to support dynamic
69 partitions (default False). This flag is implied when generating
70 an incremental OTA where the base build does not support dynamic
71 partitions but the target build does. For A/B, when this flag is set,
72 --skip_postinstall is implied.
73
xunchangabfa2652019-02-19 16:27:10 -080074 --skip_compatibility_check
75 Skip adding the compatibility package to the generated OTA package.
76
xunchang1cfe2512019-02-19 14:14:48 -080077 --output_metadata_path
78 Write a copy of the metadata to a separate file. Therefore, users can
79 read the post build fingerprint without extracting the OTA package.
80
Tao Bao30df8b42018-04-23 15:32:53 -070081Non-A/B OTA specific options
82
83 -b (--binary) <file>
84 Use the given binary as the update-binary in the output package, instead
85 of the binary in the build's target_files. Use for development only.
86
87 --block
88 Generate a block-based OTA for non-A/B device. We have deprecated the
89 support for file-based OTA since O. Block-based OTA will be used by
90 default for all non-A/B devices. Keeping this flag here to not break
91 existing callers.
92
93 -e (--extra_script) <file>
94 Insert the contents of file at the end of the update script.
Tao Bao43078aa2015-04-21 14:32:35 -070095
leozwangaa6c1a12015-08-14 10:57:58 -070096 --full_bootloader
97 Similar to --full_radio. When generating an incremental OTA, always
98 include a full copy of bootloader image.
99
Tao Bao30df8b42018-04-23 15:32:53 -0700100 --full_radio
101 When generating an incremental OTA, always include a full copy of radio
102 image. This option is only meaningful when -i is specified, because a full
103 radio is always included in a full OTA if applicable.
Michael Runge63f01de2014-10-28 19:24:19 -0700104
Tao Bao30df8b42018-04-23 15:32:53 -0700105 --log_diff <file>
106 Generate a log file that shows the differences in the source and target
107 builds for an incremental package. This option is only meaningful when -i
108 is specified.
109
110 -o (--oem_settings) <main_file[,additional_files...]>
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800111 Comma seperated list of files used to specify the expected OEM-specific
Tao Bao481bab82017-12-21 11:23:09 -0800112 properties on the OEM partition of the intended device. Multiple expected
113 values can be used by providing multiple files. Only the first dict will
114 be used to compute fingerprint, while the rest will be used to assert
115 OEM-specific properties.
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800116
Tao Bao8608cde2016-02-25 19:49:55 -0800117 --oem_no_mount
Tao Bao30df8b42018-04-23 15:32:53 -0700118 For devices with OEM-specific properties but without an OEM partition, do
119 not mount the OEM partition in the updater-script. This should be very
120 rarely used, since it's expected to have a dedicated OEM partition for
121 OEM-specific properties. Only meaningful when -o is specified.
Tao Bao8608cde2016-02-25 19:49:55 -0800122
Tao Bao30df8b42018-04-23 15:32:53 -0700123 --stash_threshold <float>
124 Specify the threshold that will be used to compute the maximum allowed
125 stash size (defaults to 0.8).
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700126
Tao Bao30df8b42018-04-23 15:32:53 -0700127 -t (--worker_threads) <int>
128 Specify the number of worker-threads that will be used when generating
129 patches for incremental updates (defaults to 3).
Tao Bao3e6161a2017-02-28 11:48:48 -0800130
Tao Bao30df8b42018-04-23 15:32:53 -0700131 --verify
132 Verify the checksums of the updated system and vendor (if any) partitions.
133 Non-A/B incremental OTAs only.
Doug Zongker1c390a22009-05-14 19:06:36 -0700134
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800135 -2 (--two_step)
Tao Bao30df8b42018-04-23 15:32:53 -0700136 Generate a 'two-step' OTA package, where recovery is updated first, so
137 that any changes made to the system partition are done using the new
138 recovery (new kernel, etc.).
139
140A/B OTA specific options
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800141
Tao Baof7140c02018-01-30 17:09:24 -0800142 --include_secondary
143 Additionally include the payload for secondary slot images (default:
144 False). Only meaningful when generating A/B OTAs.
145
146 By default, an A/B OTA package doesn't contain the images for the
147 secondary slot (e.g. system_other.img). Specifying this flag allows
148 generating a separate payload that will install secondary slot images.
149
150 Such a package needs to be applied in a two-stage manner, with a reboot
151 in-between. During the first stage, the updater applies the primary
152 payload only. Upon finishing, it reboots the device into the newly updated
153 slot. It then continues to install the secondary payload to the inactive
154 slot, but without switching the active slot at the end (needs the matching
155 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag).
156
157 Due to the special install procedure, the secondary payload will be always
158 generated as a full payload.
159
Tao Baodea0f8b2016-06-20 17:55:06 -0700160 --payload_signer <signer>
161 Specify the signer when signing the payload and metadata for A/B OTAs.
162 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
163 with the package private key. If the private key cannot be accessed
164 directly, a payload signer that knows how to do that should be specified.
165 The signer will be supplied with "-inkey <path_to_key>",
166 "-in <input_file>" and "-out <output_file>" parameters.
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700167
168 --payload_signer_args <args>
169 Specify the arguments needed for payload signer.
Tao Bao15a146a2018-02-21 16:06:59 -0800170
xunchang376cc7c2019-04-08 23:04:58 -0700171 --payload_signer_key_size <key_size>
172 Specify the key size in bytes of the payload signer.
173
Tao Bao15a146a2018-02-21 16:06:59 -0800174 --skip_postinstall
175 Skip the postinstall hooks when generating an A/B OTA package (default:
176 False). Note that this discards ALL the hooks, including non-optional
177 ones. Should only be used if caller knows it's safe to do so (e.g. all the
178 postinstall work is to dexopt apps and a data wipe will happen immediately
179 after). Only meaningful when generating A/B OTAs.
Doug Zongkereef39442009-04-02 12:14:19 -0700180"""
181
Tao Bao89fbb0f2017-01-10 10:47:58 -0800182from __future__ import print_function
183
Tianjie Xuf67dd802019-05-20 17:50:36 -0700184import collections
Tao Bao32fcdab2018-10-12 10:30:39 -0700185import logging
Doug Zongkerfc44a512014-08-26 13:10:25 -0700186import multiprocessing
Tao Bao2dd1c482017-02-03 16:49:39 -0800187import os.path
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700188import shlex
Tao Bao15a146a2018-02-21 16:06:59 -0800189import shutil
Tao Bao85f16982018-03-08 16:28:33 -0800190import struct
Tao Bao481bab82017-12-21 11:23:09 -0800191import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700192import tempfile
Doug Zongkereef39442009-04-02 12:14:19 -0700193import zipfile
194
195import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700196import edify_generator
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700197import verity_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700198
Tao Bao481bab82017-12-21 11:23:09 -0800199if sys.hexversion < 0x02070000:
200 print("Python 2.7 or newer is required.", file=sys.stderr)
201 sys.exit(1)
202
Tao Bao32fcdab2018-10-12 10:30:39 -0700203logger = logging.getLogger(__name__)
Tao Bao481bab82017-12-21 11:23:09 -0800204
Doug Zongkereef39442009-04-02 12:14:19 -0700205OPTIONS = common.OPTIONS
Doug Zongkerafb32ea2011-09-22 10:28:04 -0700206OPTIONS.package_key = None
Doug Zongkereef39442009-04-02 12:14:19 -0700207OPTIONS.incremental_source = None
Michael Runge63f01de2014-10-28 19:24:19 -0700208OPTIONS.verify = False
Doug Zongkereef39442009-04-02 12:14:19 -0700209OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700210OPTIONS.wipe_user_data = False
Tao Bao5d182562016-02-23 11:38:39 -0800211OPTIONS.downgrade = False
Doug Zongker1c390a22009-05-14 19:06:36 -0700212OPTIONS.extra_script = None
Doug Zongkerfc44a512014-08-26 13:10:25 -0700213OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
214if OPTIONS.worker_threads == 0:
215 OPTIONS.worker_threads = 1
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800216OPTIONS.two_step = False
Tao Baof7140c02018-01-30 17:09:24 -0800217OPTIONS.include_secondary = False
Takeshi Kanemotoe153b342013-11-14 17:20:50 +0900218OPTIONS.no_signing = False
Tao Bao457cbf62017-03-06 09:56:01 -0800219OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -0800220OPTIONS.updater_binary = None
Michael Runge6e836112014-04-15 17:40:21 -0700221OPTIONS.oem_source = None
Tao Bao8608cde2016-02-25 19:49:55 -0800222OPTIONS.oem_no_mount = False
Tao Bao43078aa2015-04-21 14:32:35 -0700223OPTIONS.full_radio = False
leozwangaa6c1a12015-08-14 10:57:58 -0700224OPTIONS.full_bootloader = False
Tao Baod47d8e12015-05-21 14:09:49 -0700225# Stash size cannot exceed cache_size * threshold.
226OPTIONS.cache_size = None
227OPTIONS.stash_threshold = 0.8
Tao Baod62c6032015-11-30 09:40:20 -0800228OPTIONS.log_diff = None
Tao Baodea0f8b2016-06-20 17:55:06 -0700229OPTIONS.payload_signer = None
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700230OPTIONS.payload_signer_args = []
xunchang376cc7c2019-04-08 23:04:58 -0700231OPTIONS.payload_signer_key_size = None
Tao Bao5f8ff932017-03-21 22:35:00 -0700232OPTIONS.extracted_input = None
Christian Oderf63e2cd2017-05-01 22:30:15 +0200233OPTIONS.key_passwords = []
Tao Bao15a146a2018-02-21 16:06:59 -0800234OPTIONS.skip_postinstall = False
Yifan Hong50e79542018-11-08 17:44:12 -0800235OPTIONS.retrofit_dynamic_partitions = False
xunchangabfa2652019-02-19 16:27:10 -0800236OPTIONS.skip_compatibility_check = False
xunchang1cfe2512019-02-19 14:14:48 -0800237OPTIONS.output_metadata_path = None
Tao Bao15a146a2018-02-21 16:06:59 -0800238
Tao Bao8dcf7382015-05-21 14:09:49 -0700239
Tao Bao2dd1c482017-02-03 16:49:39 -0800240METADATA_NAME = 'META-INF/com/android/metadata'
Tao Bao15a146a2018-02-21 16:06:59 -0800241POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
Yifan Hong50e79542018-11-08 17:44:12 -0800242DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
Yifan Hongb433eba2019-03-06 12:42:53 -0800243AB_PARTITIONS = 'META/ab_partitions.txt'
Tao Bao04808502019-07-25 23:11:41 -0700244UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Tao Baof0c4aa22018-04-30 20:29:30 -0700245# Files to be unzipped for target diffing purpose.
246TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
247 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800248RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tao Bao6b0b2f92017-03-05 11:38:11 -0800249
Tao Bao2dd1c482017-02-03 16:49:39 -0800250
Tao Bao481bab82017-12-21 11:23:09 -0800251class BuildInfo(object):
252 """A class that holds the information for a given build.
253
254 This class wraps up the property querying for a given source or target build.
255 It abstracts away the logic of handling OEM-specific properties, and caches
256 the commonly used properties such as fingerprint.
257
258 There are two types of info dicts: a) build-time info dict, which is generated
259 at build time (i.e. included in a target_files zip); b) OEM info dict that is
260 specified at package generation time (via command line argument
261 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
262 having "oem_fingerprint_properties" in build-time info dict), all the queries
263 would be answered based on build-time info dict only. Otherwise if using
264 OEM-specific properties, some of them will be calculated from two info dicts.
265
266 Users can query properties similarly as using a dict() (e.g. info['fstab']),
267 or to query build properties via GetBuildProp() or GetVendorBuildProp().
268
269 Attributes:
270 info_dict: The build-time info dict.
271 is_ab: Whether it's a build that uses A/B OTA.
272 oem_dicts: A list of OEM dicts.
273 oem_props: A list of OEM properties that should be read from OEM dicts; None
274 if the build doesn't use any OEM-specific property.
275 fingerprint: The fingerprint of the build, which would be calculated based
276 on OEM properties if applicable.
277 device: The device name, which could come from OEM dicts if applicable.
278 """
279
Steven Laver9e73e822019-01-29 20:20:08 -0800280 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
281 "ro.product.manufacturer", "ro.product.model",
282 "ro.product.name"]
Justin Yun6151e3f2019-06-25 15:58:13 +0900283 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
284 "system_ext", "system"]
Steven Laver9e73e822019-01-29 20:20:08 -0800285
Tao Bao481bab82017-12-21 11:23:09 -0800286 def __init__(self, info_dict, oem_dicts):
287 """Initializes a BuildInfo instance with the given dicts.
288
Tao Bao667c7532018-07-06 10:13:59 -0700289 Note that it only wraps up the given dicts, without making copies.
290
Tao Bao481bab82017-12-21 11:23:09 -0800291 Arguments:
292 info_dict: The build-time info dict.
293 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
294 that it always uses the first dict to calculate the fingerprint or the
295 device name. The rest would be used for asserting OEM properties only
Tao Bao667c7532018-07-06 10:13:59 -0700296 (e.g. one package can be installed on one of these devices).
Tao Bao481bab82017-12-21 11:23:09 -0800297 """
298 self.info_dict = info_dict
299 self.oem_dicts = oem_dicts
300
301 self._is_ab = info_dict.get("ab_update") == "true"
302 self._oem_props = info_dict.get("oem_fingerprint_properties")
303
304 if self._oem_props:
305 assert oem_dicts, "OEM source required for this build"
306
307 # These two should be computed only after setting self._oem_props.
308 self._device = self.GetOemProperty("ro.product.device")
309 self._fingerprint = self.CalculateFingerprint()
310
311 @property
312 def is_ab(self):
313 return self._is_ab
314
315 @property
316 def device(self):
317 return self._device
318
319 @property
320 def fingerprint(self):
321 return self._fingerprint
322
323 @property
Tao Baoea6cbd02018-09-05 13:06:37 -0700324 def vendor_fingerprint(self):
Yifan Hong51d37562019-04-23 17:06:46 -0700325 return self._fingerprint_of("vendor")
326
327 @property
328 def product_fingerprint(self):
329 return self._fingerprint_of("product")
330
331 @property
332 def odm_fingerprint(self):
333 return self._fingerprint_of("odm")
334
335 def _fingerprint_of(self, partition):
336 if partition + ".build.prop" not in self.info_dict:
Tao Baoea6cbd02018-09-05 13:06:37 -0700337 return None
Yifan Hong51d37562019-04-23 17:06:46 -0700338 build_prop = self.info_dict[partition + ".build.prop"]
339 if "ro." + partition + ".build.fingerprint" in build_prop:
340 return build_prop["ro." + partition + ".build.fingerprint"]
341 if "ro." + partition + ".build.thumbprint" in build_prop:
342 return build_prop["ro." + partition + ".build.thumbprint"]
Tao Baoea6cbd02018-09-05 13:06:37 -0700343 return None
344
345 @property
Tao Bao481bab82017-12-21 11:23:09 -0800346 def oem_props(self):
347 return self._oem_props
348
349 def __getitem__(self, key):
350 return self.info_dict[key]
351
Tao Bao667c7532018-07-06 10:13:59 -0700352 def __setitem__(self, key, value):
353 self.info_dict[key] = value
354
Tao Bao481bab82017-12-21 11:23:09 -0800355 def get(self, key, default=None):
356 return self.info_dict.get(key, default)
357
Tao Bao667c7532018-07-06 10:13:59 -0700358 def items(self):
359 return self.info_dict.items()
360
Tao Bao481bab82017-12-21 11:23:09 -0800361 def GetBuildProp(self, prop):
362 """Returns the inquired build property."""
Steven Laver9e73e822019-01-29 20:20:08 -0800363 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
364 return self._ResolveRoProductBuildProp(prop)
365
Tao Bao481bab82017-12-21 11:23:09 -0800366 try:
367 return self.info_dict.get("build.prop", {})[prop]
368 except KeyError:
369 raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
370
Steven Laver9e73e822019-01-29 20:20:08 -0800371 def _ResolveRoProductBuildProp(self, prop):
372 """Resolves the inquired ro.product.* build property"""
373 prop_val = self.info_dict.get("build.prop", {}).get(prop)
374 if prop_val:
375 return prop_val
376
377 source_order_val = self.info_dict.get("build.prop", {}).get(
Tao Bao59cf0c52019-06-25 10:04:24 -0700378 "ro.product.property_source_order")
Steven Laver9e73e822019-01-29 20:20:08 -0800379 if source_order_val:
380 source_order = source_order_val.split(",")
381 else:
382 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
383
384 # Check that all sources in ro.product.property_source_order are valid
385 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
386 for x in source_order]):
387 raise common.ExternalError(
Tao Bao59cf0c52019-06-25 10:04:24 -0700388 "Invalid ro.product.property_source_order '{}'".format(source_order))
Steven Laver9e73e822019-01-29 20:20:08 -0800389
390 for source in source_order:
Tao Bao59cf0c52019-06-25 10:04:24 -0700391 source_prop = prop.replace(
392 "ro.product", "ro.product.{}".format(source), 1)
393 prop_val = self.info_dict.get(
394 "{}.build.prop".format(source), {}).get(source_prop)
Steven Laver9e73e822019-01-29 20:20:08 -0800395 if prop_val:
396 return prop_val
397
398 raise common.ExternalError("couldn't resolve {}".format(prop))
399
Tao Bao481bab82017-12-21 11:23:09 -0800400 def GetVendorBuildProp(self, prop):
401 """Returns the inquired vendor build property."""
402 try:
403 return self.info_dict.get("vendor.build.prop", {})[prop]
404 except KeyError:
405 raise common.ExternalError(
406 "couldn't find %s in vendor.build.prop" % (prop,))
407
408 def GetOemProperty(self, key):
409 if self.oem_props is not None and key in self.oem_props:
410 return self.oem_dicts[0][key]
411 return self.GetBuildProp(key)
412
413 def CalculateFingerprint(self):
414 if self.oem_props is None:
Steven Laver9e73e822019-01-29 20:20:08 -0800415 try:
416 return self.GetBuildProp("ro.build.fingerprint")
417 except common.ExternalError:
418 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
Tao Bao59cf0c52019-06-25 10:04:24 -0700419 self.GetBuildProp("ro.product.brand"),
420 self.GetBuildProp("ro.product.name"),
421 self.GetBuildProp("ro.product.device"),
422 self.GetBuildProp("ro.build.version.release"),
423 self.GetBuildProp("ro.build.id"),
424 self.GetBuildProp("ro.build.version.incremental"),
425 self.GetBuildProp("ro.build.type"),
426 self.GetBuildProp("ro.build.tags"))
Tao Bao481bab82017-12-21 11:23:09 -0800427 return "%s/%s/%s:%s" % (
428 self.GetOemProperty("ro.product.brand"),
429 self.GetOemProperty("ro.product.name"),
430 self.GetOemProperty("ro.product.device"),
431 self.GetBuildProp("ro.build.thumbprint"))
432
433 def WriteMountOemScript(self, script):
434 assert self.oem_props is not None
435 recovery_mount_options = self.info_dict.get("recovery_mount_options")
436 script.Mount("/oem", recovery_mount_options)
437
438 def WriteDeviceAssertions(self, script, oem_no_mount):
439 # Read the property directly if not using OEM properties.
440 if not self.oem_props:
441 script.AssertDevice(self.device)
442 return
443
444 # Otherwise assert OEM properties.
445 if not self.oem_dicts:
446 raise common.ExternalError(
447 "No OEM file provided to answer expected assertions")
448
449 for prop in self.oem_props.split():
450 values = []
451 for oem_dict in self.oem_dicts:
452 if prop in oem_dict:
453 values.append(oem_dict[prop])
454 if not values:
455 raise common.ExternalError(
456 "The OEM file is missing the property %s" % (prop,))
457 script.AssertOemProperty(prop, values, oem_no_mount)
458
459
Tao Baofabe0832018-01-17 15:52:28 -0800460class PayloadSigner(object):
461 """A class that wraps the payload signing works.
462
463 When generating a Payload, hashes of the payload and metadata files will be
464 signed with the device key, either by calling an external payload signer or
465 by calling openssl with the package key. This class provides a unified
466 interface, so that callers can just call PayloadSigner.Sign().
467
468 If an external payload signer has been specified (OPTIONS.payload_signer), it
469 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
470 that the signing key should be provided as part of the payload_signer_args.
471 Otherwise without an external signer, it uses the package key
472 (OPTIONS.package_key) and calls openssl for the signing works.
473 """
474
475 def __init__(self):
476 if OPTIONS.payload_signer is None:
477 # Prepare the payload signing key.
478 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
479 pw = OPTIONS.key_passwords[OPTIONS.package_key]
480
481 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
482 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
483 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
484 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700485 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800486
487 self.signer = "openssl"
488 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
489 "-pkeyopt", "digest:sha256"]
xunchang376cc7c2019-04-08 23:04:58 -0700490 self.key_size = self._GetKeySizeInBytes(signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800491 else:
492 self.signer = OPTIONS.payload_signer
493 self.signer_args = OPTIONS.payload_signer_args
xunchang376cc7c2019-04-08 23:04:58 -0700494 if OPTIONS.payload_signer_key_size:
495 self.key_size = int(OPTIONS.payload_signer_key_size)
496 assert self.key_size == 256 or self.key_size == 512, \
497 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size)
498 else:
499 self.key_size = 256
500
501 @staticmethod
502 def _GetKeySizeInBytes(signing_key):
503 modulus_file = common.MakeTempFile(prefix="modulus-")
504 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus",
505 "-noout", "-out", modulus_file]
506 common.RunAndCheckOutput(cmd, verbose=False)
507
508 with open(modulus_file) as f:
509 modulus_string = f.read()
510 # The modulus string has the format "Modulus=$data", where $data is the
511 # concatenation of hex dump of the modulus.
512 MODULUS_PREFIX = "Modulus="
513 assert modulus_string.startswith(MODULUS_PREFIX)
514 modulus_string = modulus_string[len(MODULUS_PREFIX):]
Tao Bao59cf0c52019-06-25 10:04:24 -0700515 key_size = len(modulus_string) // 2
xunchang376cc7c2019-04-08 23:04:58 -0700516 assert key_size == 256 or key_size == 512, \
517 "Unsupported key size {}".format(key_size)
518 return key_size
Tao Baofabe0832018-01-17 15:52:28 -0800519
520 def Sign(self, in_file):
521 """Signs the given input file. Returns the output filename."""
522 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
523 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700524 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800525 return out_file
526
527
Tao Bao40b18822018-01-30 18:19:04 -0800528class Payload(object):
529 """Manages the creation and the signing of an A/B OTA Payload."""
530
531 PAYLOAD_BIN = 'payload.bin'
532 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800533 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
534 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800535
Tao Bao667ff572018-02-10 00:02:40 -0800536 def __init__(self, secondary=False):
537 """Initializes a Payload instance.
538
539 Args:
540 secondary: Whether it's generating a secondary payload (default: False).
541 """
Tao Bao40b18822018-01-30 18:19:04 -0800542 self.payload_file = None
543 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800544 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800545
Tao Baof0c4aa22018-04-30 20:29:30 -0700546 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700547 # Don't pipe (buffer) the output if verbose is set. Let
548 # brillo_update_payload write to stdout/stderr directly, so its progress can
549 # be monitored.
550 if OPTIONS.verbose:
551 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
552 else:
553 common.RunAndCheckOutput(cmd)
554
Tao Bao40b18822018-01-30 18:19:04 -0800555 def Generate(self, target_file, source_file=None, additional_args=None):
556 """Generates a payload from the given target-files zip(s).
557
558 Args:
559 target_file: The filename of the target build target-files zip.
560 source_file: The filename of the source build target-files zip; or None if
561 generating a full OTA.
562 additional_args: A list of additional args that should be passed to
563 brillo_update_payload script; or None.
564 """
565 if additional_args is None:
566 additional_args = []
567
568 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
569 cmd = ["brillo_update_payload", "generate",
570 "--payload", payload_file,
571 "--target_image", target_file]
572 if source_file is not None:
573 cmd.extend(["--source_image", source_file])
574 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700575 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800576
577 self.payload_file = payload_file
578 self.payload_properties = None
579
580 def Sign(self, payload_signer):
581 """Generates and signs the hashes of the payload and metadata.
582
583 Args:
584 payload_signer: A PayloadSigner() instance that serves the signing work.
585
586 Raises:
587 AssertionError: On any failure when calling brillo_update_payload script.
588 """
589 assert isinstance(payload_signer, PayloadSigner)
590
591 # 1. Generate hashes of the payload and metadata files.
592 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
593 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
594 cmd = ["brillo_update_payload", "hash",
595 "--unsigned_payload", self.payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700596 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800597 "--metadata_hash_file", metadata_sig_file,
598 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700599 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800600
601 # 2. Sign the hashes.
602 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
603 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
604
605 # 3. Insert the signatures back into the payload file.
606 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
607 suffix=".bin")
608 cmd = ["brillo_update_payload", "sign",
609 "--unsigned_payload", self.payload_file,
610 "--payload", signed_payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700611 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800612 "--metadata_signature_file", signed_metadata_sig_file,
613 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700614 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800615
616 # 4. Dump the signed payload properties.
617 properties_file = common.MakeTempFile(prefix="payload-properties-",
618 suffix=".txt")
619 cmd = ["brillo_update_payload", "properties",
620 "--payload", signed_payload_file,
621 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700622 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800623
Tao Bao667ff572018-02-10 00:02:40 -0800624 if self.secondary:
625 with open(properties_file, "a") as f:
626 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
627
Tao Bao40b18822018-01-30 18:19:04 -0800628 if OPTIONS.wipe_user_data:
629 with open(properties_file, "a") as f:
630 f.write("POWERWASH=1\n")
631
632 self.payload_file = signed_payload_file
633 self.payload_properties = properties_file
634
Tao Bao667ff572018-02-10 00:02:40 -0800635 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800636 """Writes the payload to the given zip.
637
638 Args:
639 output_zip: The output ZipFile instance.
640 """
641 assert self.payload_file is not None
642 assert self.payload_properties is not None
643
Tao Bao667ff572018-02-10 00:02:40 -0800644 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800645 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
646 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
647 else:
648 payload_arcname = Payload.PAYLOAD_BIN
649 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
650
Tao Bao40b18822018-01-30 18:19:04 -0800651 # Add the signed payload file and properties into the zip. In order to
652 # support streaming, we pack them as ZIP_STORED. So these entries can be
653 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800654 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800655 compress_type=zipfile.ZIP_STORED)
656 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800657 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800658 compress_type=zipfile.ZIP_STORED)
659
660
Doug Zongkereef39442009-04-02 12:14:19 -0700661def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200662 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700663
Doug Zongker951495f2009-08-14 12:44:19 -0700664 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
665 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700666
667
Tao Bao481bab82017-12-21 11:23:09 -0800668def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800669 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800670 if not oem_source:
671 return None
672
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800673 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800674 for oem_file in oem_source:
675 with open(oem_file) as fp:
676 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800677 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700678
Doug Zongkereef39442009-04-02 12:14:19 -0700679
Tao Baod42e97e2016-11-30 12:11:57 -0800680def _WriteRecoveryImageToBoot(script, output_zip):
681 """Find and write recovery image to /boot in two-step OTA.
682
683 In two-step OTAs, we write recovery image to /boot as the first step so that
684 we can reboot to there and install a new recovery image to /recovery.
685 A special "recovery-two-step.img" will be preferred, which encodes the correct
686 path of "/boot". Otherwise the device may show "device is corrupt" message
687 when booting into /boot.
688
689 Fall back to using the regular recovery.img if the two-step recovery image
690 doesn't exist. Note that rebuilding the special image at this point may be
691 infeasible, because we don't have the desired boot signer and keys when
692 calling ota_from_target_files.py.
693 """
694
695 recovery_two_step_img_name = "recovery-two-step.img"
696 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700697 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800698 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700699 common.ZipWrite(
700 output_zip,
701 recovery_two_step_img_path,
702 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700703 logger.info(
704 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800705 script.WriteRawImage("/boot", recovery_two_step_img_name)
706 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700707 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800708 # The "recovery.img" entry has been written into package earlier.
709 script.WriteRawImage("/boot", "recovery.img")
710
711
Doug Zongkerc9253822014-02-04 12:17:58 -0800712def HasRecoveryPatch(target_files_zip):
Tao Baof2cffbd2015-07-22 12:33:18 -0700713 namelist = [name for name in target_files_zip.namelist()]
714 return ("SYSTEM/recovery-from-boot.p" in namelist or
715 "SYSTEM/etc/recovery.img" in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700716
Tao Bao457cbf62017-03-06 09:56:01 -0800717
Yifan Hong51d37562019-04-23 17:06:46 -0700718def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700719 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700720 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700721 return True
722 except KeyError:
723 return False
724
Tao Bao457cbf62017-03-06 09:56:01 -0800725
Yifan Hong51d37562019-04-23 17:06:46 -0700726def HasVendorPartition(target_files_zip):
727 return HasPartition(target_files_zip, "vendor")
728
729
730def HasProductPartition(target_files_zip):
731 return HasPartition(target_files_zip, "product")
732
733
734def HasOdmPartition(target_files_zip):
735 return HasPartition(target_files_zip, "odm")
736
737
Tao Bao481bab82017-12-21 11:23:09 -0800738def HasTrebleEnabled(target_files_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700739 return (HasVendorPartition(target_files_zip) and
Tao Bao481bab82017-12-21 11:23:09 -0800740 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700741
742
Tao Bao481bab82017-12-21 11:23:09 -0800743def WriteFingerprintAssertion(script, target_info, source_info):
744 source_oem_props = source_info.oem_props
745 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700746
Tao Bao481bab82017-12-21 11:23:09 -0800747 if source_oem_props is None and target_oem_props is None:
748 script.AssertSomeFingerprint(
749 source_info.fingerprint, target_info.fingerprint)
750 elif source_oem_props is not None and target_oem_props is not None:
751 script.AssertSomeThumbprint(
752 target_info.GetBuildProp("ro.build.thumbprint"),
753 source_info.GetBuildProp("ro.build.thumbprint"))
754 elif source_oem_props is None and target_oem_props is not None:
755 script.AssertFingerprintOrThumbprint(
756 source_info.fingerprint,
757 target_info.GetBuildProp("ro.build.thumbprint"))
758 else:
759 script.AssertFingerprintOrThumbprint(
760 target_info.fingerprint,
761 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700762
Doug Zongkerfc44a512014-08-26 13:10:25 -0700763
Tao Bao481bab82017-12-21 11:23:09 -0800764def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info,
765 source_info=None):
Tao Baobcd1d162017-08-26 13:10:26 -0700766 """Adds compatibility info into the output zip if it's Treble-enabled target.
Tao Bao21803d32017-04-19 10:16:09 -0700767
768 Metadata used for on-device compatibility verification is retrieved from
769 target_zip then added to compatibility.zip which is added to the output_zip
770 archive.
771
Tao Baobcd1d162017-08-26 13:10:26 -0700772 Compatibility archive should only be included for devices that have enabled
773 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700774
775 Args:
776 target_zip: Zip file containing the source files to be included for OTA.
777 output_zip: Zip file that will be sent for OTA.
Tao Bao481bab82017-12-21 11:23:09 -0800778 target_info: The BuildInfo instance that holds the target build info.
779 source_info: The BuildInfo instance that holds the source build info, if
780 generating an incremental OTA; None otherwise.
Tao Bao21803d32017-04-19 10:16:09 -0700781 """
782
Yifan Hong51d37562019-04-23 17:06:46 -0700783 def AddCompatibilityArchive(framework_updated, device_updated):
784 """Adds compatibility info based on update status of both sides of Treble
785 boundary.
Tao Bao21803d32017-04-19 10:16:09 -0700786
Tao Baobcd1d162017-08-26 13:10:26 -0700787 Args:
Yifan Hong51d37562019-04-23 17:06:46 -0700788 framework_updated: If True, the system / product image will be updated
789 and therefore their metadata should be included.
790 device_updated: If True, the vendor / odm image will be updated and
791 therefore their metadata should be included.
Tao Baobcd1d162017-08-26 13:10:26 -0700792 """
793 # Determine what metadata we need. Files are names relative to META/.
794 compatibility_files = []
Yifan Hong51d37562019-04-23 17:06:46 -0700795 device_metadata = ("vendor_manifest.xml", "vendor_matrix.xml")
796 framework_metadata = ("system_manifest.xml", "system_matrix.xml")
797 if device_updated:
798 compatibility_files += device_metadata
799 if framework_updated:
800 compatibility_files += framework_metadata
Tao Bao21803d32017-04-19 10:16:09 -0700801
Tao Baobcd1d162017-08-26 13:10:26 -0700802 # Create new archive.
803 compatibility_archive = tempfile.NamedTemporaryFile()
Tao Bao481bab82017-12-21 11:23:09 -0800804 compatibility_archive_zip = zipfile.ZipFile(
805 compatibility_archive, "w", compression=zipfile.ZIP_DEFLATED)
Tao Bao21803d32017-04-19 10:16:09 -0700806
Tao Baobcd1d162017-08-26 13:10:26 -0700807 # Add metadata.
808 for file_name in compatibility_files:
809 target_file_name = "META/" + file_name
Tao Bao21803d32017-04-19 10:16:09 -0700810
Tao Baobcd1d162017-08-26 13:10:26 -0700811 if target_file_name in target_zip.namelist():
812 data = target_zip.read(target_file_name)
813 common.ZipWriteStr(compatibility_archive_zip, file_name, data)
Tao Bao21803d32017-04-19 10:16:09 -0700814
Tao Baobcd1d162017-08-26 13:10:26 -0700815 # Ensure files are written before we copy into output_zip.
816 compatibility_archive_zip.close()
817
818 # Only add the archive if we have any compatibility info.
819 if compatibility_archive_zip.namelist():
820 common.ZipWrite(output_zip, compatibility_archive.name,
821 arcname="compatibility.zip",
822 compress_type=zipfile.ZIP_STORED)
823
Yifan Hong51d37562019-04-23 17:06:46 -0700824 def FingerprintChanged(source_fp, target_fp):
825 if source_fp is None or target_fp is None:
826 return True
827 return source_fp != target_fp
828
Tao Baobcd1d162017-08-26 13:10:26 -0700829 # Will only proceed if the target has enabled the Treble support (as well as
830 # having a /vendor partition).
Tao Bao481bab82017-12-21 11:23:09 -0800831 if not HasTrebleEnabled(target_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700832 return
833
xunchangabfa2652019-02-19 16:27:10 -0800834 # Skip adding the compatibility package as a workaround for b/114240221. The
835 # compatibility will always fail on devices without qualified kernels.
836 if OPTIONS.skip_compatibility_check:
837 return
838
Yifan Hong51d37562019-04-23 17:06:46 -0700839 # Full OTA carries the info for system/vendor/product/odm
Tao Bao481bab82017-12-21 11:23:09 -0800840 if source_info is None:
Tao Baobcd1d162017-08-26 13:10:26 -0700841 AddCompatibilityArchive(True, True)
842 return
843
Tao Bao481bab82017-12-21 11:23:09 -0800844 source_fp = source_info.fingerprint
845 target_fp = target_info.fingerprint
Tao Baobcd1d162017-08-26 13:10:26 -0700846 system_updated = source_fp != target_fp
847
Yifan Hong51d37562019-04-23 17:06:46 -0700848 # other build fingerprints could be possibly blacklisted at build time. For
849 # such a case, we consider those images being changed.
850 vendor_updated = FingerprintChanged(source_info.vendor_fingerprint,
851 target_info.vendor_fingerprint)
852 product_updated = HasProductPartition(target_zip) and \
853 FingerprintChanged(source_info.product_fingerprint,
854 target_info.product_fingerprint)
855 odm_updated = HasOdmPartition(target_zip) and \
856 FingerprintChanged(source_info.odm_fingerprint,
857 target_info.odm_fingerprint)
Tao Baobcd1d162017-08-26 13:10:26 -0700858
Yifan Hong51d37562019-04-23 17:06:46 -0700859 AddCompatibilityArchive(system_updated or product_updated,
860 vendor_updated or odm_updated)
Tao Bao21803d32017-04-19 10:16:09 -0700861
862
Tianjie Xuf67dd802019-05-20 17:50:36 -0700863def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
864 device_specific):
865 """Returns a ordered dict of block differences with partition name as key."""
866
867 def GetIncrementalBlockDifferenceForPartition(name):
868 if not HasPartition(source_zip, name):
869 raise RuntimeError("can't generate incremental that adds {}".format(name))
870
871 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
872 info_dict=source_info,
873 allow_shared_blocks=allow_shared_blocks)
874
875 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
876 name, 4096, target_info)
877 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
878 info_dict=target_info,
879 allow_shared_blocks=allow_shared_blocks,
880 hashtree_info_generator=
881 hashtree_info_generator)
882
883 # Check the first block of the source system partition for remount R/W only
884 # if the filesystem is ext4.
885 partition_source_info = source_info["fstab"]["/" + name]
886 check_first_block = partition_source_info.fs_type == "ext4"
887 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
888 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
889 # b) the blocks listed in block map may not contain all the bytes for a
890 # given file (because they're rounded to be 4K-aligned).
891 partition_target_info = target_info["fstab"]["/" + name]
892 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
893 partition_target_info.fs_type == "squashfs")
894 return common.BlockDifference(name, partition_src, partition_tgt,
895 check_first_block,
896 version=blockimgdiff_version,
897 disable_imgdiff=disable_imgdiff)
898
899 if source_zip:
900 # See notes in common.GetUserImage()
901 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
902 target_info.get('ext4_share_dup_blocks') == "true")
903 blockimgdiff_version = max(
904 int(i) for i in target_info.get(
905 "blockimgdiff_versions", "1").split(","))
906 assert blockimgdiff_version >= 3
907
908 block_diff_dict = collections.OrderedDict()
909 partition_names = ["system", "vendor", "product", "odm", "system_ext"]
910 for partition in partition_names:
911 if not HasPartition(target_zip, partition):
912 continue
913 # Full OTA update.
914 if not source_zip:
915 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
916 info_dict=target_info,
917 reset_file_map=True)
918 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
919 src=None)
920 # Incremental OTA update.
921 else:
922 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
923 partition)
924 assert "system" in block_diff_dict
925
926 # Get the block diffs from the device specific script. If there is a
927 # duplicate block diff for a partition, ignore the diff in the generic script
928 # and use the one in the device specific script instead.
929 if source_zip:
930 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
931 function_name = "IncrementalOTA_GetBlockDifferences"
932 else:
933 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
934 function_name = "FullOTA_GetBlockDifferences"
935
936 if device_specific_diffs:
937 assert all(isinstance(diff, common.BlockDifference)
938 for diff in device_specific_diffs), \
939 "{} is not returning a list of BlockDifference objects".format(
940 function_name)
941 for diff in device_specific_diffs:
942 if diff.partition in block_diff_dict:
943 logger.warning("Duplicate block difference found. Device specific block"
944 " diff for partition '%s' overrides the one in generic"
945 " script.", diff.partition)
946 block_diff_dict[diff.partition] = diff
947
948 return block_diff_dict
949
950
Tao Bao491d7e22018-02-21 13:17:22 -0800951def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800952 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700953
Tao Bao481bab82017-12-21 11:23:09 -0800954 # We don't know what version it will be installed on top of. We expect the API
955 # just won't change very often. Similarly for fstab, it might have changed in
956 # the target build.
957 target_api_version = target_info["recovery_api_version"]
958 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700959
Tao Bao481bab82017-12-21 11:23:09 -0800960 if target_info.oem_props and not OPTIONS.oem_no_mount:
961 target_info.WriteMountOemScript(script)
962
Tao Baodf3a48b2018-01-10 16:30:43 -0800963 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700964
Tao Bao491d7e22018-02-21 13:17:22 -0800965 if not OPTIONS.no_signing:
966 staging_file = common.MakeTempFile(suffix='.zip')
967 else:
968 staging_file = output_file
969
970 output_zip = zipfile.ZipFile(
971 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
972
Doug Zongker05d3dea2009-06-22 11:32:31 -0700973 device_specific = common.DeviceSpecificParams(
974 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800975 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700976 output_zip=output_zip,
977 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700978 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700979 metadata=metadata,
980 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700981
Tao Bao457cbf62017-03-06 09:56:01 -0800982 assert HasRecoveryPatch(input_zip)
Doug Zongkerc9253822014-02-04 12:17:58 -0800983
Tao Bao481bab82017-12-21 11:23:09 -0800984 # Assertions (e.g. downgrade check, device properties check).
985 ts = target_info.GetBuildProp("ro.build.date.utc")
986 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700987 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700988
Tao Bao481bab82017-12-21 11:23:09 -0800989 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700990 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800991
Tianjie Xuf67dd802019-05-20 17:50:36 -0700992 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
993 target_info=target_info,
994 source_info=None,
995 device_specific=device_specific)
996
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800997 # Two-step package strategy (in chronological order, which is *not*
998 # the order in which the generated script has things):
999 #
1000 # if stage is not "2/3" or "3/3":
1001 # write recovery image to boot partition
1002 # set stage to "2/3"
1003 # reboot to boot partition and restart recovery
1004 # else if stage is "2/3":
1005 # write recovery image to recovery partition
1006 # set stage to "3/3"
1007 # reboot to recovery partition and restart recovery
1008 # else:
1009 # (stage must be "3/3")
1010 # set stage to ""
1011 # do normal full package installation:
1012 # wipe and install system, boot image, etc.
1013 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001014 # complete script normally
1015 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001016
1017 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
1018 OPTIONS.input_tmp, "RECOVERY")
1019 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001020 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001021 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -08001022 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001023 assert fs.fs_type.upper() == "EMMC", \
1024 "two-step packages only supported on devices with EMMC /misc partitions"
1025 bcb_dev = {"bcb_dev": fs.device}
1026 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
1027 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001028if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001029""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001030
1031 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1032 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001033 script.WriteRawImage("/recovery", "recovery.img")
1034 script.AppendExtra("""
1035set_stage("%(bcb_dev)s", "3/3");
1036reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001037else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001038""" % bcb_dev)
1039
Tao Baod42e97e2016-11-30 12:11:57 -08001040 # Stage 3/3: Make changes.
1041 script.Comment("Stage 3/3")
1042
Tao Bao6c55a8a2015-04-08 15:30:27 -07001043 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001044 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001045
Doug Zongkere5ff5902012-01-17 10:55:37 -08001046 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -07001047
Tianjie Xuf67dd802019-05-20 17:50:36 -07001048 # All other partitions as well as the data wipe use 10% of the progress, and
1049 # the update of the system partition takes the remaining progress.
1050 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -07001051 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -08001052 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -07001053 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1054 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001055
Yifan Hong10c530d2018-12-27 17:34:18 -08001056 if target_info.get('use_dynamic_partitions') == "true":
1057 # Use empty source_info_dict to indicate that all partitions / groups must
1058 # be re-added.
1059 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1060 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001061 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001062 progress_dict=progress_dict)
1063 dynamic_partitions_diff.WriteScript(script, output_zip,
1064 write_verify_script=OPTIONS.verify)
1065 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001066 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001067 block_diff.WriteScript(script, output_zip,
1068 progress=progress_dict.get(block_diff.partition),
1069 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001070
Tao Bao481bab82017-12-21 11:23:09 -08001071 AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001072
Yifan Hong10c530d2018-12-27 17:34:18 -08001073 boot_img = common.GetBootableImage(
1074 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001075 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001076 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001077
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001078 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001079
Tianjie Xuf67dd802019-05-20 17:50:36 -07001080 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001081 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001082
Doug Zongker1c390a22009-05-14 19:06:36 -07001083 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001084 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001085
Doug Zongker14833602010-02-02 13:12:04 -08001086 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001087
Doug Zongker922206e2014-03-04 13:16:24 -08001088 if OPTIONS.wipe_user_data:
1089 script.ShowProgress(0.1, 10)
1090 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001091
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001092 if OPTIONS.two_step:
1093 script.AppendExtra("""
1094set_stage("%(bcb_dev)s", "");
1095""" % bcb_dev)
1096 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001097
1098 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1099 script.Comment("Stage 1/3")
1100 _WriteRecoveryImageToBoot(script, output_zip)
1101
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001102 script.AppendExtra("""
1103set_stage("%(bcb_dev)s", "2/3");
1104reboot_now("%(bcb_dev)s", "");
1105endif;
1106endif;
1107""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001108
Tao Bao5d182562016-02-23 11:38:39 -08001109 script.SetProgress(1)
1110 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001111 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001112
1113 # We haven't written the metadata entry, which will be done in
1114 # FinalizeMetadata.
1115 common.ZipClose(output_zip)
1116
1117 needed_property_files = (
1118 NonAbOtaPropertyFiles(),
1119 )
1120 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001121
Doug Zongkerfc44a512014-08-26 13:10:25 -07001122
xunchang1cfe2512019-02-19 14:14:48 -08001123def WriteMetadata(metadata, output):
1124 """Writes the metadata to the zip archive or a file.
1125
1126 Args:
1127 metadata: The metadata dict for the package.
1128 output: A ZipFile object or a string of the output file path.
1129 """
1130
Tao Bao59cf0c52019-06-25 10:04:24 -07001131 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001132 if isinstance(output, zipfile.ZipFile):
1133 common.ZipWriteStr(output, METADATA_NAME, value,
1134 compress_type=zipfile.ZIP_STORED)
1135 return
1136
1137 with open(output, 'w') as f:
1138 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001139
Doug Zongkerfc44a512014-08-26 13:10:25 -07001140
Tao Bao481bab82017-12-21 11:23:09 -08001141def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001142 # Only incremental OTAs are allowed to reach here.
1143 assert OPTIONS.incremental_source is not None
1144
Tao Bao481bab82017-12-21 11:23:09 -08001145 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1146 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001147 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001148
1149 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001150 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001151 raise RuntimeError(
1152 "--downgrade or --override_timestamp specified but no downgrade "
1153 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001154 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001155 else:
1156 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001157 raise RuntimeError(
1158 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1159 "Need to specify --override_timestamp OR --downgrade to allow "
1160 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001161
1162
Tao Baodf3a48b2018-01-10 16:30:43 -08001163def GetPackageMetadata(target_info, source_info=None):
1164 """Generates and returns the metadata dict.
1165
1166 It generates a dict() that contains the info to be written into an OTA
1167 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001168 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001169
1170 Args:
1171 target_info: The BuildInfo instance that holds the target build info.
1172 source_info: The BuildInfo instance that holds the source build info, or
1173 None if generating full OTA.
1174
1175 Returns:
1176 A dict to be written into package metadata entry.
1177 """
1178 assert isinstance(target_info, BuildInfo)
1179 assert source_info is None or isinstance(source_info, BuildInfo)
1180
1181 metadata = {
1182 'post-build' : target_info.fingerprint,
1183 'post-build-incremental' : target_info.GetBuildProp(
1184 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001185 'post-sdk-level' : target_info.GetBuildProp(
1186 'ro.build.version.sdk'),
1187 'post-security-patch-level' : target_info.GetBuildProp(
1188 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001189 }
1190
1191 if target_info.is_ab:
1192 metadata['ota-type'] = 'AB'
1193 metadata['ota-required-cache'] = '0'
1194 else:
1195 metadata['ota-type'] = 'BLOCK'
1196
1197 if OPTIONS.wipe_user_data:
1198 metadata['ota-wipe'] = 'yes'
1199
Tao Bao393eeb42019-03-06 16:00:38 -08001200 if OPTIONS.retrofit_dynamic_partitions:
1201 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1202
Tao Baodf3a48b2018-01-10 16:30:43 -08001203 is_incremental = source_info is not None
1204 if is_incremental:
1205 metadata['pre-build'] = source_info.fingerprint
1206 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1207 'ro.build.version.incremental')
1208 metadata['pre-device'] = source_info.device
1209 else:
1210 metadata['pre-device'] = target_info.device
1211
Tao Baofaa8e0b2018-04-12 14:31:43 -07001212 # Use the actual post-timestamp, even for a downgrade case.
1213 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1214
1215 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001216 if is_incremental:
1217 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001218
1219 return metadata
1220
1221
Tao Baod3fc38a2018-03-08 16:09:01 -08001222class PropertyFiles(object):
1223 """A class that computes the property-files string for an OTA package.
1224
1225 A property-files string is a comma-separated string that contains the
1226 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1227 can be fetched directly with the package URL along with the offset/size info.
1228 These strings can be used for streaming A/B OTAs, or allowing an updater to
1229 download package metadata entry directly, without paying the cost of
1230 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001231
Tao Baocc8e2662018-03-01 19:30:00 -08001232 Computing the final property-files string requires two passes. Because doing
1233 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1234 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1235 values.
1236
1237 This class provides functions to be called for each pass. The general flow is
1238 as follows.
1239
Tao Baod3fc38a2018-03-08 16:09:01 -08001240 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001241 # The first pass, which writes placeholders before doing initial signing.
1242 property_files.Compute()
1243 SignOutput()
1244
1245 # The second pass, by replacing the placeholders with actual data.
1246 property_files.Finalize()
1247 SignOutput()
1248
1249 And the caller can additionally verify the final result.
1250
1251 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001252 """
1253
Tao Baocc8e2662018-03-01 19:30:00 -08001254 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001255 self.name = None
1256 self.required = ()
1257 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001258
Tao Baocc8e2662018-03-01 19:30:00 -08001259 def Compute(self, input_zip):
1260 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001261
Tao Baocc8e2662018-03-01 19:30:00 -08001262 We reserve extra space for the offset and size of the metadata entry itself,
1263 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001264
Tao Baocc8e2662018-03-01 19:30:00 -08001265 Args:
1266 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001267
Tao Baocc8e2662018-03-01 19:30:00 -08001268 Returns:
1269 A string with placeholders for the metadata offset/size info, e.g.
1270 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1271 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001272 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001273
Tao Baod2ce2ed2018-03-16 12:59:42 -07001274 class InsufficientSpaceException(Exception):
1275 pass
1276
Tao Baocc8e2662018-03-01 19:30:00 -08001277 def Finalize(self, input_zip, reserved_length):
1278 """Finalizes a property-files string with actual METADATA offset/size info.
1279
1280 The input ZIP file has been signed, with the ZIP entries in the desired
1281 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1282 the ZIP entry offsets and construct the property-files string with actual
1283 data. Note that during this process, we must pad the property-files string
1284 to the reserved length, so that the METADATA entry size remains the same.
1285 Otherwise the entries' offsets and sizes may change again.
1286
1287 Args:
1288 input_zip: The input ZIP file.
1289 reserved_length: The reserved length of the property-files string during
1290 the call to Compute(). The final string must be no more than this
1291 size.
1292
1293 Returns:
1294 A property-files string including the metadata offset/size info, e.g.
1295 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1296
1297 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001298 InsufficientSpaceException: If the reserved length is insufficient to hold
1299 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001300 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001301 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001302 if len(result) > reserved_length:
1303 raise self.InsufficientSpaceException(
1304 'Insufficient reserved space: reserved={}, actual={}'.format(
1305 reserved_length, len(result)))
1306
Tao Baocc8e2662018-03-01 19:30:00 -08001307 result += ' ' * (reserved_length - len(result))
1308 return result
1309
1310 def Verify(self, input_zip, expected):
1311 """Verifies the input ZIP file contains the expected property-files string.
1312
1313 Args:
1314 input_zip: The input ZIP file.
1315 expected: The property-files string that's computed from Finalize().
1316
1317 Raises:
1318 AssertionError: On finding a mismatch.
1319 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001320 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001321 assert actual == expected, \
1322 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1323
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001324 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1325 """
1326 Constructs the property-files string per request.
1327
1328 Args:
1329 zip_file: The input ZIP file.
1330 reserved_length: The reserved length of the property-files string.
1331
1332 Returns:
1333 A property-files string including the metadata offset/size info, e.g.
1334 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1335 """
Tao Baocc8e2662018-03-01 19:30:00 -08001336
1337 def ComputeEntryOffsetSize(name):
1338 """Computes the zip entry offset and size."""
1339 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001340 offset = info.header_offset
1341 offset += zipfile.sizeFileHeader
1342 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001343 size = info.file_size
1344 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1345
1346 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001347 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001348 for entry in self.required:
1349 tokens.append(ComputeEntryOffsetSize(entry))
1350 for entry in self.optional:
1351 if entry in zip_file.namelist():
1352 tokens.append(ComputeEntryOffsetSize(entry))
1353
1354 # 'META-INF/com/android/metadata' is required. We don't know its actual
1355 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001356 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1357 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1358 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1359 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001360 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001361 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001362 else:
1363 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1364
1365 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001366
Tao Bao85f16982018-03-08 16:28:33 -08001367 def _GetPrecomputed(self, input_zip):
1368 """Computes the additional tokens to be included into the property-files.
1369
1370 This applies to tokens without actual ZIP entries, such as
1371 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1372 that they can download the payload metadata directly with the info.
1373
1374 Args:
1375 input_zip: The input zip file.
1376
1377 Returns:
1378 A list of strings (tokens) to be added to the property-files string.
1379 """
1380 # pylint: disable=no-self-use
1381 # pylint: disable=unused-argument
1382 return []
1383
Tao Baofe5b69a2018-03-02 09:47:43 -08001384
Tao Baod3fc38a2018-03-08 16:09:01 -08001385class StreamingPropertyFiles(PropertyFiles):
1386 """A subclass for computing the property-files for streaming A/B OTAs."""
1387
1388 def __init__(self):
1389 super(StreamingPropertyFiles, self).__init__()
1390 self.name = 'ota-streaming-property-files'
1391 self.required = (
1392 # payload.bin and payload_properties.txt must exist.
1393 'payload.bin',
1394 'payload_properties.txt',
1395 )
1396 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001397 # care_map is available only if dm-verity is enabled.
1398 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001399 'care_map.txt',
1400 # compatibility.zip is available only if target supports Treble.
1401 'compatibility.zip',
1402 )
1403
1404
Tao Bao85f16982018-03-08 16:28:33 -08001405class AbOtaPropertyFiles(StreamingPropertyFiles):
1406 """The property-files for A/B OTA that includes payload_metadata.bin info.
1407
1408 Since P, we expose one more token (aka property-file), in addition to the ones
1409 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1410 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1411 doesn't exist as a separate ZIP entry, but can be used to verify if the
1412 payload can be applied on the given device.
1413
1414 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1415 and the newly added 'ota-property-files' in P. The new token will only be
1416 available in 'ota-property-files'.
1417 """
1418
1419 def __init__(self):
1420 super(AbOtaPropertyFiles, self).__init__()
1421 self.name = 'ota-property-files'
1422
1423 def _GetPrecomputed(self, input_zip):
1424 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1425 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1426
1427 @staticmethod
1428 def _GetPayloadMetadataOffsetAndSize(input_zip):
1429 """Computes the offset and size of the payload metadata for a given package.
1430
1431 (From system/update_engine/update_metadata.proto)
1432 A delta update file contains all the deltas needed to update a system from
1433 one specific version to another specific version. The update format is
1434 represented by this struct pseudocode:
1435
1436 struct delta_update_file {
1437 char magic[4] = "CrAU";
1438 uint64 file_format_version;
1439 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1440
1441 // Only present if format_version > 1:
1442 uint32 metadata_signature_size;
1443
1444 // The Bzip2 compressed DeltaArchiveManifest
1445 char manifest[metadata_signature_size];
1446
1447 // The signature of the metadata (from the beginning of the payload up to
1448 // this location, not including the signature itself). This is a
1449 // serialized Signatures message.
1450 char medatada_signature_message[metadata_signature_size];
1451
1452 // Data blobs for files, no specific format. The specific offset
1453 // and length of each data blob is recorded in the DeltaArchiveManifest.
1454 struct {
1455 char data[];
1456 } blobs[];
1457
1458 // These two are not signed:
1459 uint64 payload_signatures_message_size;
1460 char payload_signatures_message[];
1461 };
1462
1463 'payload-metadata.bin' contains all the bytes from the beginning of the
1464 payload, till the end of 'medatada_signature_message'.
1465 """
1466 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001467 payload_offset = payload_info.header_offset
1468 payload_offset += zipfile.sizeFileHeader
1469 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001470 payload_size = payload_info.file_size
1471
Tao Bao59cf0c52019-06-25 10:04:24 -07001472 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001473 header_bin = payload_fp.read(24)
1474
1475 # network byte order (big-endian)
1476 header = struct.unpack("!IQQL", header_bin)
1477
1478 # 'CrAU'
1479 magic = header[0]
1480 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1481
1482 manifest_size = header[2]
1483 metadata_signature_size = header[3]
1484 metadata_total = 24 + manifest_size + metadata_signature_size
1485 assert metadata_total < payload_size
1486
1487 return (payload_offset, metadata_total)
1488
1489
Tao Bao491d7e22018-02-21 13:17:22 -08001490class NonAbOtaPropertyFiles(PropertyFiles):
1491 """The property-files for non-A/B OTA.
1492
1493 For non-A/B OTA, the property-files string contains the info for METADATA
1494 entry, with which a system updater can be fetched the package metadata prior
1495 to downloading the entire package.
1496 """
1497
1498 def __init__(self):
1499 super(NonAbOtaPropertyFiles, self).__init__()
1500 self.name = 'ota-property-files'
1501
1502
Tao Baod3fc38a2018-03-08 16:09:01 -08001503def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001504 """Finalizes the metadata and signs an A/B OTA package.
1505
1506 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1507 that contains the offsets and sizes for the ZIP entries. An example
1508 property-files string is as follows.
1509
1510 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1511
1512 OTA server can pass down this string, in addition to the package URL, to the
1513 system update client. System update client can then fetch individual ZIP
1514 entries (ZIP_STORED) directly at the given offset of the URL.
1515
1516 Args:
1517 metadata: The metadata dict for the package.
1518 input_file: The input ZIP filename that doesn't contain the package METADATA
1519 entry yet.
1520 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001521 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001522 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001523
Tao Baod2ce2ed2018-03-16 12:59:42 -07001524 def ComputeAllPropertyFiles(input_file, needed_property_files):
1525 # Write the current metadata entry with placeholders.
1526 with zipfile.ZipFile(input_file) as input_zip:
1527 for property_files in needed_property_files:
1528 metadata[property_files.name] = property_files.Compute(input_zip)
1529 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001530
Tao Baod2ce2ed2018-03-16 12:59:42 -07001531 if METADATA_NAME in namelist:
1532 common.ZipDelete(input_file, METADATA_NAME)
1533 output_zip = zipfile.ZipFile(input_file, 'a')
1534 WriteMetadata(metadata, output_zip)
1535 common.ZipClose(output_zip)
1536
1537 if OPTIONS.no_signing:
1538 return input_file
1539
Tao Bao491d7e22018-02-21 13:17:22 -08001540 prelim_signing = common.MakeTempFile(suffix='.zip')
1541 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001542 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001543
Tao Baod2ce2ed2018-03-16 12:59:42 -07001544 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1545 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1546 for property_files in needed_property_files:
1547 metadata[property_files.name] = property_files.Finalize(
1548 prelim_signing_zip, len(metadata[property_files.name]))
1549
1550 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1551 # entries, as well as padding the entry headers. We do a preliminary signing
1552 # (with an incomplete metadata entry) to allow that to happen. Then compute
1553 # the ZIP entry offsets, write back the final metadata and do the final
1554 # signing.
1555 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1556 try:
1557 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1558 except PropertyFiles.InsufficientSpaceException:
1559 # Even with the preliminary signing, the entry orders may change
1560 # dramatically, which leads to insufficiently reserved space during the
1561 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1562 # preliminary signing works, based on the already ordered ZIP entries, to
1563 # address the issue.
1564 prelim_signing = ComputeAllPropertyFiles(
1565 prelim_signing, needed_property_files)
1566 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001567
1568 # Replace the METADATA entry.
1569 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001570 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001571 WriteMetadata(metadata, output_zip)
1572 common.ZipClose(output_zip)
1573
1574 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001575 if OPTIONS.no_signing:
1576 output_file = prelim_signing
1577 else:
1578 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001579
1580 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001581 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001582 for property_files in needed_property_files:
1583 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001584
xunchang1cfe2512019-02-19 14:14:48 -08001585 # If requested, dump the metadata to a separate file.
1586 output_metadata_path = OPTIONS.output_metadata_path
1587 if output_metadata_path:
1588 WriteMetadata(metadata, output_metadata_path)
1589
Tao Baofe5b69a2018-03-02 09:47:43 -08001590
Tao Bao491d7e22018-02-21 13:17:22 -08001591def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001592 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1593 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001594
Tao Bao481bab82017-12-21 11:23:09 -08001595 target_api_version = target_info["recovery_api_version"]
1596 source_api_version = source_info["recovery_api_version"]
1597 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001598 logger.warning(
1599 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001600
Tao Bao481bab82017-12-21 11:23:09 -08001601 script = edify_generator.EdifyGenerator(
1602 source_api_version, target_info, fstab=source_info["fstab"])
1603
1604 if target_info.oem_props or source_info.oem_props:
1605 if not OPTIONS.oem_no_mount:
1606 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001607
Tao Baodf3a48b2018-01-10 16:30:43 -08001608 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001609
Tao Bao491d7e22018-02-21 13:17:22 -08001610 if not OPTIONS.no_signing:
1611 staging_file = common.MakeTempFile(suffix='.zip')
1612 else:
1613 staging_file = output_file
1614
1615 output_zip = zipfile.ZipFile(
1616 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1617
Geremy Condra36bd3652014-02-06 19:45:10 -08001618 device_specific = common.DeviceSpecificParams(
1619 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001620 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001621 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001622 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001623 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001624 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001625 output_zip=output_zip,
1626 script=script,
1627 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001628 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001629
Geremy Condra36bd3652014-02-06 19:45:10 -08001630 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001631 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001632 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001633 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001634 updating_boot = (not OPTIONS.two_step and
1635 (source_boot.data != target_boot.data))
1636
Geremy Condra36bd3652014-02-06 19:45:10 -08001637 target_recovery = common.GetBootableImage(
1638 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001639
Tianjie Xuf67dd802019-05-20 17:50:36 -07001640 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1641 source_zip=source_zip,
1642 target_info=target_info,
1643 source_info=source_info,
1644 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001645
Tao Baobcd1d162017-08-26 13:10:26 -07001646 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08001647 target_zip, output_zip, target_info, source_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001648
Tao Bao481bab82017-12-21 11:23:09 -08001649 # Assertions (e.g. device properties check).
1650 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001651 device_specific.IncrementalOTA_Assertions()
1652
1653 # Two-step incremental package strategy (in chronological order,
1654 # which is *not* the order in which the generated script has
1655 # things):
1656 #
1657 # if stage is not "2/3" or "3/3":
1658 # do verification on current system
1659 # write recovery image to boot partition
1660 # set stage to "2/3"
1661 # reboot to boot partition and restart recovery
1662 # else if stage is "2/3":
1663 # write recovery image to recovery partition
1664 # set stage to "3/3"
1665 # reboot to recovery partition and restart recovery
1666 # else:
1667 # (stage must be "3/3")
1668 # perform update:
1669 # patch system files, etc.
1670 # force full install of new boot image
1671 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001672 # complete script normally
1673 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001674
1675 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001676 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001677 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001678 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001679 assert fs.fs_type.upper() == "EMMC", \
1680 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001681 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001682 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1683 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001684if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001685""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001686
1687 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1688 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001689 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001690 script.WriteRawImage("/recovery", "recovery.img")
1691 script.AppendExtra("""
1692set_stage("%(bcb_dev)s", "3/3");
1693reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001694else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001695""" % bcb_dev)
1696
Tao Baod42e97e2016-11-30 12:11:57 -08001697 # Stage 1/3: (a) Verify the current system.
1698 script.Comment("Stage 1/3")
1699
Tao Bao6c55a8a2015-04-08 15:30:27 -07001700 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001701 script.Print("Source: {}".format(source_info.fingerprint))
1702 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001703
Geremy Condra36bd3652014-02-06 19:45:10 -08001704 script.Print("Verifying current system...")
1705
1706 device_specific.IncrementalOTA_VerifyBegin()
1707
Tao Bao481bab82017-12-21 11:23:09 -08001708 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001709
Tao Baod8d14be2016-02-04 14:26:02 -08001710 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001711 required_cache_sizes = [diff.required_cache for diff in
1712 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001713 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001714 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001715 d = common.Difference(target_boot, source_boot)
1716 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001717 if d is None:
1718 include_full_boot = True
1719 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1720 else:
1721 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001722
Tao Bao32fcdab2018-10-12 10:30:39 -07001723 logger.info(
1724 "boot target: %d source: %d diff: %d", target_boot.size,
1725 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001726
Tao Bao51216552018-08-26 11:53:15 -07001727 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001728
Tao Bao51216552018-08-26 11:53:15 -07001729 script.PatchPartitionCheck(
1730 "{}:{}:{}:{}".format(
1731 boot_type, boot_device, target_boot.size, target_boot.sha1),
1732 "{}:{}:{}:{}".format(
1733 boot_type, boot_device, source_boot.size, source_boot.sha1))
1734
Tianjie Xuf67dd802019-05-20 17:50:36 -07001735 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001736
Tianjie Xuf67dd802019-05-20 17:50:36 -07001737 if required_cache_sizes:
1738 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1739
1740 # Verify the existing partitions.
1741 for diff in block_diff_dict.values():
1742 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001743
1744 device_specific.IncrementalOTA_VerifyEnd()
1745
1746 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001747 # Stage 1/3: (b) Write recovery image to /boot.
1748 _WriteRecoveryImageToBoot(script, output_zip)
1749
Geremy Condra36bd3652014-02-06 19:45:10 -08001750 script.AppendExtra("""
1751set_stage("%(bcb_dev)s", "2/3");
1752reboot_now("%(bcb_dev)s", "");
1753else
1754""" % bcb_dev)
1755
Tao Baod42e97e2016-11-30 12:11:57 -08001756 # Stage 3/3: Make changes.
1757 script.Comment("Stage 3/3")
1758
Geremy Condra36bd3652014-02-06 19:45:10 -08001759 script.Comment("---- start making changes here ----")
1760
1761 device_specific.IncrementalOTA_InstallBegin()
1762
Tianjie Xuf67dd802019-05-20 17:50:36 -07001763 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1764 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001765
1766 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1767 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1768 raise RuntimeError(
1769 "can't generate incremental that disables dynamic partitions")
1770 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1771 info_dict=OPTIONS.target_info_dict,
1772 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001773 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001774 progress_dict=progress_dict)
1775 dynamic_partitions_diff.WriteScript(
1776 script, output_zip, write_verify_script=OPTIONS.verify)
1777 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001778 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001779 block_diff.WriteScript(script, output_zip,
1780 progress=progress_dict.get(block_diff.partition),
1781 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001782
1783 if OPTIONS.two_step:
1784 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1785 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001786 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001787
1788 if not OPTIONS.two_step:
1789 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001790 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001791 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001792 script.Print("Installing boot image...")
1793 script.WriteRawImage("/boot", "boot.img")
1794 else:
1795 # Produce the boot image by applying a patch to the current
1796 # contents of the boot partition, and write it back to the
1797 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001798 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001799 script.Print("Patching boot image...")
1800 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001801 script.PatchPartition(
1802 '{}:{}:{}:{}'.format(
1803 boot_type, boot_device, target_boot.size, target_boot.sha1),
1804 '{}:{}:{}:{}'.format(
1805 boot_type, boot_device, source_boot.size, source_boot.sha1),
1806 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001807 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001808 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001809
1810 # Do device-specific installation (eg, write radio image).
1811 device_specific.IncrementalOTA_InstallEnd()
1812
1813 if OPTIONS.extra_script is not None:
1814 script.AppendExtra(OPTIONS.extra_script)
1815
Doug Zongker922206e2014-03-04 13:16:24 -08001816 if OPTIONS.wipe_user_data:
1817 script.Print("Erasing user data...")
1818 script.FormatPartition("/data")
1819
Geremy Condra36bd3652014-02-06 19:45:10 -08001820 if OPTIONS.two_step:
1821 script.AppendExtra("""
1822set_stage("%(bcb_dev)s", "");
1823endif;
1824endif;
1825""" % bcb_dev)
1826
1827 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001828 # For downgrade OTAs, we prefer to use the update-binary in the source
1829 # build that is actually newer than the one in the target build.
1830 if OPTIONS.downgrade:
1831 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1832 else:
1833 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001834 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001835
1836 # We haven't written the metadata entry yet, which will be handled in
1837 # FinalizeMetadata().
1838 common.ZipClose(output_zip)
1839
1840 # Sign the generated zip package unless no_signing is specified.
1841 needed_property_files = (
1842 NonAbOtaPropertyFiles(),
1843 )
1844 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001845
Doug Zongker32b527d2014-03-04 10:03:02 -08001846
Tao Bao15a146a2018-02-21 16:06:59 -08001847def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001848 """Returns a target-files.zip file for generating secondary payload.
1849
1850 Although the original target-files.zip already contains secondary slot
1851 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1852 ones without _other suffix. Note that we cannot instead modify the names in
1853 META/ab_partitions.txt, because there are no matching partitions on device.
1854
1855 For the partitions that don't have secondary images, the ones for primary
1856 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1857 bootloader images in the inactive slot.
1858
1859 Args:
1860 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001861 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001862
1863 Returns:
1864 The filename of the target-files.zip for generating secondary payload.
1865 """
1866 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1867 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1868
Tao Baodba59ee2018-01-09 13:21:02 -08001869 with zipfile.ZipFile(input_file, 'r') as input_zip:
1870 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001871
Tao Bao0ff15de2019-03-20 11:26:06 -07001872 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001873 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001874 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1875 if info.filename == 'IMAGES/system_other.img':
1876 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1877
1878 # Primary images and friends need to be skipped explicitly.
1879 elif info.filename in ('IMAGES/system.img',
1880 'IMAGES/system.map'):
1881 pass
1882
Tao Bao15a146a2018-02-21 16:06:59 -08001883 # Skip copying the postinstall config if requested.
1884 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1885 pass
1886
Tao Bao12489802018-07-12 14:47:38 -07001887 elif info.filename.startswith(('META/', 'IMAGES/', 'RADIO/')):
Tao Baof7140c02018-01-30 17:09:24 -08001888 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
1889
Tao Baof7140c02018-01-30 17:09:24 -08001890 common.ZipClose(target_zip)
1891
1892 return target_file
1893
1894
Tao Bao15a146a2018-02-21 16:06:59 -08001895def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1896 """Returns a target-files.zip that's not containing postinstall_config.txt.
1897
1898 This allows brillo_update_payload script to skip writing all the postinstall
1899 hooks in the generated payload. The input target-files.zip file will be
1900 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1901 contain the postinstall_config.txt entry, the input file will be returned.
1902
1903 Args:
1904 input_file: The input target-files.zip filename.
1905
1906 Returns:
1907 The filename of target-files.zip that doesn't contain postinstall config.
1908 """
1909 # We should only make a copy if postinstall_config entry exists.
1910 with zipfile.ZipFile(input_file, 'r') as input_zip:
1911 if POSTINSTALL_CONFIG not in input_zip.namelist():
1912 return input_file
1913
1914 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1915 shutil.copyfile(input_file, target_file)
1916 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1917 return target_file
1918
1919
Yifan Hong50e79542018-11-08 17:44:12 -08001920def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001921 super_block_devices,
1922 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001923 """Returns a target-files.zip for retrofitting dynamic partitions.
1924
1925 This allows brillo_update_payload to generate an OTA based on the exact
1926 bits on the block devices. Postinstall is disabled.
1927
1928 Args:
1929 input_file: The input target-files.zip filename.
1930 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001931 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001932
1933 Returns:
1934 The filename of target-files.zip with *.img replaced with super_*.img for
1935 each block device in super_block_devices.
1936 """
1937 assert super_block_devices, "No super_block_devices are specified."
1938
1939 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001940 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001941
1942 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1943 shutil.copyfile(input_file, target_file)
1944
Tao Baoa3705452019-06-24 15:33:41 -07001945 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001946 namelist = input_zip.namelist()
1947
Yifan Hongb433eba2019-03-06 12:42:53 -08001948 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1949
1950 # Remove partitions from META/ab_partitions.txt that is in
1951 # dynamic_partition_list but not in super_block_devices so that
1952 # brillo_update_payload won't generate update for those logical partitions.
1953 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1954 with open(ab_partitions_file) as f:
1955 ab_partitions_lines = f.readlines()
1956 ab_partitions = [line.strip() for line in ab_partitions_lines]
1957 # Assert that all super_block_devices are in ab_partitions
1958 super_device_not_updated = [partition for partition in super_block_devices
1959 if partition not in ab_partitions]
1960 assert not super_device_not_updated, \
1961 "{} is in super_block_devices but not in {}".format(
1962 super_device_not_updated, AB_PARTITIONS)
1963 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1964 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1965 with open(new_ab_partitions, 'w') as f:
1966 for partition in ab_partitions:
1967 if (partition in dynamic_partition_list and
1968 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001969 logger.info("Dropping %s from ab_partitions.txt", partition)
1970 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001971 f.write(partition + "\n")
1972 to_delete = [AB_PARTITIONS]
1973
Yifan Hong50e79542018-11-08 17:44:12 -08001974 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001975 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001976
1977 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1978 # is a regular update on devices without dynamic partitions support.
1979 to_delete += [DYNAMIC_PARTITION_INFO]
1980
Tao Bao03fecb62018-11-28 10:59:23 -08001981 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001982 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001983 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001984
1985 common.ZipDelete(target_file, to_delete)
1986
Yifan Hong50e79542018-11-08 17:44:12 -08001987 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1988
1989 # Write super_{foo}.img as {foo}.img.
1990 for src, dst in replace.items():
1991 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001992 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001993 unzipped_file = os.path.join(input_tmp, *src.split('/'))
1994 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
1995
Yifan Hongb433eba2019-03-06 12:42:53 -08001996 # Write new ab_partitions.txt file
1997 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
1998
Yifan Hong50e79542018-11-08 17:44:12 -08001999 common.ZipClose(target_zip)
2000
2001 return target_file
2002
2003
Tao Baof0c4aa22018-04-30 20:29:30 -07002004def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002005 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002006 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002007 if not OPTIONS.no_signing:
2008 staging_file = common.MakeTempFile(suffix='.zip')
2009 else:
2010 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002011 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002012 compression=zipfile.ZIP_DEFLATED)
2013
Tao Bao481bab82017-12-21 11:23:09 -08002014 if source_file is not None:
2015 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2016 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2017 else:
2018 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2019 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002020
Tao Bao481bab82017-12-21 11:23:09 -08002021 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002022 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002023
Yifan Hong50e79542018-11-08 17:44:12 -08002024 if OPTIONS.retrofit_dynamic_partitions:
2025 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002026 target_file, target_info.get("super_block_devices").strip().split(),
2027 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002028 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002029 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2030
Tao Bao40b18822018-01-30 18:19:04 -08002031 # Generate payload.
2032 payload = Payload()
2033
2034 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002035 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002036 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002037 else:
2038 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002039 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002040
Tao Bao40b18822018-01-30 18:19:04 -08002041 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002042
Tao Bao40b18822018-01-30 18:19:04 -08002043 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002044 payload_signer = PayloadSigner()
2045 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002046
Tao Bao40b18822018-01-30 18:19:04 -08002047 # Write the payload into output zip.
2048 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002049
Tao Baof7140c02018-01-30 17:09:24 -08002050 # Generate and include the secondary payload that installs secondary images
2051 # (e.g. system_other.img).
2052 if OPTIONS.include_secondary:
2053 # We always include a full payload for the secondary slot, even when
2054 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002055 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2056 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002057 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002058 secondary_payload.Generate(secondary_target_file,
2059 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002060 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002061 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002062
Tianjie Xucfa86222016-03-07 16:31:19 -08002063 # If dm-verity is supported for the device, copy contents of care_map
2064 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002065 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002066 if (target_info.get("verity") == "true" or
2067 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002068 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2069 "META/" + x in target_zip.namelist()]
2070
2071 # Adds care_map if either the protobuf format or the plain text one exists.
2072 if care_map_list:
2073 care_map_name = care_map_list[0]
2074 care_map_data = target_zip.read("META/" + care_map_name)
2075 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002076 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002077 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002078 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002079 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002080 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002081
Tao Baobcd1d162017-08-26 13:10:26 -07002082 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08002083 target_zip, output_zip, target_info, source_info)
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
Tao Baofe5b69a2018-03-02 09:47:43 -08002087 # We haven't written the metadata entry yet, which will be handled in
2088 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002089 common.ZipClose(output_zip)
2090
Tao Bao85f16982018-03-08 16:28:33 -08002091 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2092 # all the info of the latter. However, system updaters and OTA servers need to
2093 # take time to switch to the new flag. We keep both of the flags for
2094 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002095 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002096 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002097 StreamingPropertyFiles(),
2098 )
2099 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002100
Tao Baoc098e9e2016-01-07 13:03:56 -08002101
Tao Baof0c4aa22018-04-30 20:29:30 -07002102def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
2103 """Generates a non-A/B OTA package."""
2104 # Sanity check the loaded info dicts first.
2105 if OPTIONS.info_dict.get("no_recovery") == "true":
2106 raise common.ExternalError(
2107 "--- target build has specified no recovery ---")
2108
2109 # Non-A/B OTAs rely on /cache partition to store temporary files.
2110 cache_size = OPTIONS.info_dict.get("cache_size")
2111 if cache_size is None:
2112 logger.warning("--- can't determine the cache partition size ---")
2113 OPTIONS.cache_size = cache_size
2114
2115 if OPTIONS.extra_script is not None:
2116 with open(OPTIONS.extra_script) as fp:
2117 OPTIONS.extra_script = fp.read()
2118
2119 if OPTIONS.extracted_input is not None:
2120 OPTIONS.input_tmp = OPTIONS.extracted_input
2121 else:
2122 logger.info("unzipping target target-files...")
2123 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
2124 OPTIONS.target_tmp = OPTIONS.input_tmp
2125
2126 # If the caller explicitly specified the device-specific extensions path via
2127 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2128 # is present in the target target_files. Otherwise, take the path of the file
2129 # from 'tool_extensions' in the info dict and look for that in the local
2130 # filesystem, relative to the current directory.
2131 if OPTIONS.device_specific is None:
2132 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2133 if os.path.exists(from_input):
2134 logger.info("(using device-specific extensions from target_files)")
2135 OPTIONS.device_specific = from_input
2136 else:
2137 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
2138
2139 if OPTIONS.device_specific is not None:
2140 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
2141
2142 # Generate a full OTA.
2143 if source_file is None:
2144 with zipfile.ZipFile(target_file) as input_zip:
2145 WriteFullOTAPackage(
2146 input_zip,
2147 output_file)
2148
2149 # Generate an incremental OTA.
2150 else:
2151 logger.info("unzipping source target-files...")
2152 OPTIONS.source_tmp = common.UnzipTemp(
2153 OPTIONS.incremental_source, UNZIP_PATTERN)
2154 with zipfile.ZipFile(target_file) as input_zip, \
2155 zipfile.ZipFile(source_file) as source_zip:
2156 WriteBlockIncrementalOTAPackage(
2157 input_zip,
2158 source_zip,
2159 output_file)
2160
2161
Doug Zongkereef39442009-04-02 12:14:19 -07002162def main(argv):
2163
2164 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002165 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002166 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002167 elif o in ("-i", "--incremental_from"):
2168 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002169 elif o == "--full_radio":
2170 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002171 elif o == "--full_bootloader":
2172 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002173 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002174 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002175 elif o == "--downgrade":
2176 OPTIONS.downgrade = True
2177 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002178 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002179 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002180 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002181 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002182 elif o == "--oem_no_mount":
2183 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002184 elif o in ("-e", "--extra_script"):
2185 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002186 elif o in ("-t", "--worker_threads"):
2187 if a.isdigit():
2188 OPTIONS.worker_threads = int(a)
2189 else:
2190 raise ValueError("Cannot parse value %r for option %r - only "
2191 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002192 elif o in ("-2", "--two_step"):
2193 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002194 elif o == "--include_secondary":
2195 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002196 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002197 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002198 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002199 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002200 elif o == "--block":
2201 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002202 elif o in ("-b", "--binary"):
2203 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002204 elif o == "--stash_threshold":
2205 try:
2206 OPTIONS.stash_threshold = float(a)
2207 except ValueError:
2208 raise ValueError("Cannot parse value %r for option %r - expecting "
2209 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002210 elif o == "--log_diff":
2211 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002212 elif o == "--payload_signer":
2213 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002214 elif o == "--payload_signer_args":
2215 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002216 elif o == "--payload_signer_key_size":
2217 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002218 elif o == "--extracted_input_target_files":
2219 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002220 elif o == "--skip_postinstall":
2221 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002222 elif o == "--retrofit_dynamic_partitions":
2223 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002224 elif o == "--skip_compatibility_check":
2225 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002226 elif o == "--output_metadata_path":
2227 OPTIONS.output_metadata_path = a
Doug Zongkereef39442009-04-02 12:14:19 -07002228 else:
2229 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002230 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002231
2232 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002233 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002234 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002235 "package_key=",
2236 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002237 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002238 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002239 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002240 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002241 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002242 "extra_script=",
2243 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002244 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002245 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002246 "no_signing",
2247 "block",
2248 "binary=",
2249 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002250 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002251 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002252 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002253 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002254 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002255 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002256 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002257 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002258 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002259 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002260 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002261 "output_metadata_path=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002262 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002263
2264 if len(args) != 2:
2265 common.Usage(__doc__)
2266 sys.exit(1)
2267
Tao Bao32fcdab2018-10-12 10:30:39 -07002268 common.InitLogging()
2269
Tao Bao5d182562016-02-23 11:38:39 -08002270 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002271 # We should only allow downgrading incrementals (as opposed to full).
2272 # Otherwise the device may go back from arbitrary build with this full
2273 # OTA package.
2274 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002275 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002276
Tao Bao2db13852018-01-08 22:28:57 -08002277 # Load the build info dicts from the zip directly or the extracted input
2278 # directory. We don't need to unzip the entire target-files zips, because they
2279 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2280 # When loading the info dicts, we don't need to provide the second parameter
2281 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2282 # some properties with their actual paths, such as 'selinux_fc',
2283 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002284 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002285 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002286 else:
Tao Bao2db13852018-01-08 22:28:57 -08002287 with zipfile.ZipFile(args[0], 'r') as input_zip:
2288 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002289
Tao Bao32fcdab2018-10-12 10:30:39 -07002290 logger.info("--- target info ---")
2291 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002292
2293 # Load the source build dict if applicable.
2294 if OPTIONS.incremental_source is not None:
2295 OPTIONS.target_info_dict = OPTIONS.info_dict
2296 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2297 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2298
Tao Bao32fcdab2018-10-12 10:30:39 -07002299 logger.info("--- source info ---")
2300 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002301
2302 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002303 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2304
Yifan Hong50e79542018-11-08 17:44:12 -08002305 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002306 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002307 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002308 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2309 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002310 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2311 raise common.ExternalError(
2312 "Expect to generate incremental OTA for retrofitting dynamic "
2313 "partitions, but dynamic_partition_retrofit is not set in target "
2314 "build.")
2315 logger.info("Implicitly generating retrofit incremental OTA.")
2316 OPTIONS.retrofit_dynamic_partitions = True
2317
2318 # Skip postinstall for retrofitting dynamic partitions.
2319 if OPTIONS.retrofit_dynamic_partitions:
2320 OPTIONS.skip_postinstall = True
2321
Tao Baoc098e9e2016-01-07 13:03:56 -08002322 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2323
Christian Oderf63e2cd2017-05-01 22:30:15 +02002324 # Use the default key to sign the package if not specified with package_key.
2325 # package_keys are needed on ab_updates, so always define them if an
2326 # ab_update is getting created.
2327 if not OPTIONS.no_signing or ab_update:
2328 if OPTIONS.package_key is None:
2329 OPTIONS.package_key = OPTIONS.info_dict.get(
2330 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002331 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002332 # Get signing keys
2333 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2334
Tao Baoc098e9e2016-01-07 13:03:56 -08002335 if ab_update:
Tao Baof0c4aa22018-04-30 20:29:30 -07002336 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002337 target_file=args[0],
2338 output_file=args[1],
2339 source_file=OPTIONS.incremental_source)
2340
Dan Willemsencea5cd22017-03-21 14:44:27 -07002341 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002342 GenerateNonAbOtaPackage(
2343 target_file=args[0],
2344 output_file=args[1],
2345 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002346
Tao Baof0c4aa22018-04-30 20:29:30 -07002347 # Post OTA generation works.
2348 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2349 logger.info("Generating diff logs...")
2350 logger.info("Unzipping target-files for diffing...")
2351 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2352 source_dir = common.UnzipTemp(
2353 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002354
Tao Baof0c4aa22018-04-30 20:29:30 -07002355 with open(OPTIONS.log_diff, 'w') as out_file:
2356 import target_files_diff
2357 target_files_diff.recursiveDiff(
2358 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002359
Tao Bao32fcdab2018-10-12 10:30:39 -07002360 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002361
2362
2363if __name__ == '__main__':
2364 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002365 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002366 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002367 except common.ExternalError:
2368 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002369 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002370 finally:
2371 common.Cleanup()