blob: 9715aa180c4041e392ee9cba5c05ab44a46a60a8 [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
Tao Bao32fcdab2018-10-12 10:30:39 -0700184import logging
Doug Zongkerfc44a512014-08-26 13:10:25 -0700185import multiprocessing
Tao Bao2dd1c482017-02-03 16:49:39 -0800186import os.path
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700187import shlex
Tao Bao15a146a2018-02-21 16:06:59 -0800188import shutil
Tao Bao85f16982018-03-08 16:28:33 -0800189import struct
Tao Bao481bab82017-12-21 11:23:09 -0800190import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700191import tempfile
Doug Zongkereef39442009-04-02 12:14:19 -0700192import zipfile
193
194import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700195import edify_generator
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700196import verity_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700197
Tao Bao481bab82017-12-21 11:23:09 -0800198if sys.hexversion < 0x02070000:
199 print("Python 2.7 or newer is required.", file=sys.stderr)
200 sys.exit(1)
201
Tao Bao32fcdab2018-10-12 10:30:39 -0700202logger = logging.getLogger(__name__)
Tao Bao481bab82017-12-21 11:23:09 -0800203
Doug Zongkereef39442009-04-02 12:14:19 -0700204OPTIONS = common.OPTIONS
Doug Zongkerafb32ea2011-09-22 10:28:04 -0700205OPTIONS.package_key = None
Doug Zongkereef39442009-04-02 12:14:19 -0700206OPTIONS.incremental_source = None
Michael Runge63f01de2014-10-28 19:24:19 -0700207OPTIONS.verify = False
Doug Zongkereef39442009-04-02 12:14:19 -0700208OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700209OPTIONS.wipe_user_data = False
Tao Bao5d182562016-02-23 11:38:39 -0800210OPTIONS.downgrade = False
Doug Zongker1c390a22009-05-14 19:06:36 -0700211OPTIONS.extra_script = None
Doug Zongkerfc44a512014-08-26 13:10:25 -0700212OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
213if OPTIONS.worker_threads == 0:
214 OPTIONS.worker_threads = 1
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800215OPTIONS.two_step = False
Tao Baof7140c02018-01-30 17:09:24 -0800216OPTIONS.include_secondary = False
Takeshi Kanemotoe153b342013-11-14 17:20:50 +0900217OPTIONS.no_signing = False
Tao Bao457cbf62017-03-06 09:56:01 -0800218OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -0800219OPTIONS.updater_binary = None
Michael Runge6e836112014-04-15 17:40:21 -0700220OPTIONS.oem_source = None
Tao Bao8608cde2016-02-25 19:49:55 -0800221OPTIONS.oem_no_mount = False
Tao Bao43078aa2015-04-21 14:32:35 -0700222OPTIONS.full_radio = False
leozwangaa6c1a12015-08-14 10:57:58 -0700223OPTIONS.full_bootloader = False
Tao Baod47d8e12015-05-21 14:09:49 -0700224# Stash size cannot exceed cache_size * threshold.
225OPTIONS.cache_size = None
226OPTIONS.stash_threshold = 0.8
Tao Baod62c6032015-11-30 09:40:20 -0800227OPTIONS.log_diff = None
Tao Baodea0f8b2016-06-20 17:55:06 -0700228OPTIONS.payload_signer = None
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700229OPTIONS.payload_signer_args = []
xunchang376cc7c2019-04-08 23:04:58 -0700230OPTIONS.payload_signer_key_size = None
Tao Bao5f8ff932017-03-21 22:35:00 -0700231OPTIONS.extracted_input = None
Christian Oderf63e2cd2017-05-01 22:30:15 +0200232OPTIONS.key_passwords = []
Tao Bao15a146a2018-02-21 16:06:59 -0800233OPTIONS.skip_postinstall = False
Yifan Hong50e79542018-11-08 17:44:12 -0800234OPTIONS.retrofit_dynamic_partitions = False
xunchangabfa2652019-02-19 16:27:10 -0800235OPTIONS.skip_compatibility_check = False
xunchang1cfe2512019-02-19 14:14:48 -0800236OPTIONS.output_metadata_path = None
Tao Bao15a146a2018-02-21 16:06:59 -0800237
Tao Bao8dcf7382015-05-21 14:09:49 -0700238
Tao Bao2dd1c482017-02-03 16:49:39 -0800239METADATA_NAME = 'META-INF/com/android/metadata'
Tao Bao15a146a2018-02-21 16:06:59 -0800240POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
Yifan Hong50e79542018-11-08 17:44:12 -0800241DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
Yifan Hongb433eba2019-03-06 12:42:53 -0800242AB_PARTITIONS = 'META/ab_partitions.txt'
Tao Bao04808502019-07-25 23:11:41 -0700243UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Tao Baof0c4aa22018-04-30 20:29:30 -0700244# Files to be unzipped for target diffing purpose.
245TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
246 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800247RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tao Bao6b0b2f92017-03-05 11:38:11 -0800248
Tao Bao2dd1c482017-02-03 16:49:39 -0800249
Tao Bao481bab82017-12-21 11:23:09 -0800250class BuildInfo(object):
251 """A class that holds the information for a given build.
252
253 This class wraps up the property querying for a given source or target build.
254 It abstracts away the logic of handling OEM-specific properties, and caches
255 the commonly used properties such as fingerprint.
256
257 There are two types of info dicts: a) build-time info dict, which is generated
258 at build time (i.e. included in a target_files zip); b) OEM info dict that is
259 specified at package generation time (via command line argument
260 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
261 having "oem_fingerprint_properties" in build-time info dict), all the queries
262 would be answered based on build-time info dict only. Otherwise if using
263 OEM-specific properties, some of them will be calculated from two info dicts.
264
265 Users can query properties similarly as using a dict() (e.g. info['fstab']),
266 or to query build properties via GetBuildProp() or GetVendorBuildProp().
267
268 Attributes:
269 info_dict: The build-time info dict.
270 is_ab: Whether it's a build that uses A/B OTA.
271 oem_dicts: A list of OEM dicts.
272 oem_props: A list of OEM properties that should be read from OEM dicts; None
273 if the build doesn't use any OEM-specific property.
274 fingerprint: The fingerprint of the build, which would be calculated based
275 on OEM properties if applicable.
276 device: The device name, which could come from OEM dicts if applicable.
277 """
278
Steven Laver9e73e822019-01-29 20:20:08 -0800279 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
280 "ro.product.manufacturer", "ro.product.model",
281 "ro.product.name"]
Justin Yun6151e3f2019-06-25 15:58:13 +0900282 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
283 "system_ext", "system"]
Steven Laver9e73e822019-01-29 20:20:08 -0800284
Tao Bao481bab82017-12-21 11:23:09 -0800285 def __init__(self, info_dict, oem_dicts):
286 """Initializes a BuildInfo instance with the given dicts.
287
Tao Bao667c7532018-07-06 10:13:59 -0700288 Note that it only wraps up the given dicts, without making copies.
289
Tao Bao481bab82017-12-21 11:23:09 -0800290 Arguments:
291 info_dict: The build-time info dict.
292 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
293 that it always uses the first dict to calculate the fingerprint or the
294 device name. The rest would be used for asserting OEM properties only
Tao Bao667c7532018-07-06 10:13:59 -0700295 (e.g. one package can be installed on one of these devices).
Tao Bao481bab82017-12-21 11:23:09 -0800296 """
297 self.info_dict = info_dict
298 self.oem_dicts = oem_dicts
299
300 self._is_ab = info_dict.get("ab_update") == "true"
301 self._oem_props = info_dict.get("oem_fingerprint_properties")
302
303 if self._oem_props:
304 assert oem_dicts, "OEM source required for this build"
305
306 # These two should be computed only after setting self._oem_props.
307 self._device = self.GetOemProperty("ro.product.device")
308 self._fingerprint = self.CalculateFingerprint()
309
310 @property
311 def is_ab(self):
312 return self._is_ab
313
314 @property
315 def device(self):
316 return self._device
317
318 @property
319 def fingerprint(self):
320 return self._fingerprint
321
322 @property
Tao Baoea6cbd02018-09-05 13:06:37 -0700323 def vendor_fingerprint(self):
Yifan Hong51d37562019-04-23 17:06:46 -0700324 return self._fingerprint_of("vendor")
325
326 @property
327 def product_fingerprint(self):
328 return self._fingerprint_of("product")
329
330 @property
331 def odm_fingerprint(self):
332 return self._fingerprint_of("odm")
333
334 def _fingerprint_of(self, partition):
335 if partition + ".build.prop" not in self.info_dict:
Tao Baoea6cbd02018-09-05 13:06:37 -0700336 return None
Yifan Hong51d37562019-04-23 17:06:46 -0700337 build_prop = self.info_dict[partition + ".build.prop"]
338 if "ro." + partition + ".build.fingerprint" in build_prop:
339 return build_prop["ro." + partition + ".build.fingerprint"]
340 if "ro." + partition + ".build.thumbprint" in build_prop:
341 return build_prop["ro." + partition + ".build.thumbprint"]
Tao Baoea6cbd02018-09-05 13:06:37 -0700342 return None
343
344 @property
Tao Bao481bab82017-12-21 11:23:09 -0800345 def oem_props(self):
346 return self._oem_props
347
348 def __getitem__(self, key):
349 return self.info_dict[key]
350
Tao Bao667c7532018-07-06 10:13:59 -0700351 def __setitem__(self, key, value):
352 self.info_dict[key] = value
353
Tao Bao481bab82017-12-21 11:23:09 -0800354 def get(self, key, default=None):
355 return self.info_dict.get(key, default)
356
Tao Bao667c7532018-07-06 10:13:59 -0700357 def items(self):
358 return self.info_dict.items()
359
Tao Bao481bab82017-12-21 11:23:09 -0800360 def GetBuildProp(self, prop):
361 """Returns the inquired build property."""
Steven Laver9e73e822019-01-29 20:20:08 -0800362 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
363 return self._ResolveRoProductBuildProp(prop)
364
Tao Bao481bab82017-12-21 11:23:09 -0800365 try:
366 return self.info_dict.get("build.prop", {})[prop]
367 except KeyError:
368 raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
369
Steven Laver9e73e822019-01-29 20:20:08 -0800370 def _ResolveRoProductBuildProp(self, prop):
371 """Resolves the inquired ro.product.* build property"""
372 prop_val = self.info_dict.get("build.prop", {}).get(prop)
373 if prop_val:
374 return prop_val
375
376 source_order_val = self.info_dict.get("build.prop", {}).get(
Tao Bao59cf0c52019-06-25 10:04:24 -0700377 "ro.product.property_source_order")
Steven Laver9e73e822019-01-29 20:20:08 -0800378 if source_order_val:
379 source_order = source_order_val.split(",")
380 else:
381 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
382
383 # Check that all sources in ro.product.property_source_order are valid
384 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
385 for x in source_order]):
386 raise common.ExternalError(
Tao Bao59cf0c52019-06-25 10:04:24 -0700387 "Invalid ro.product.property_source_order '{}'".format(source_order))
Steven Laver9e73e822019-01-29 20:20:08 -0800388
389 for source in source_order:
Tao Bao59cf0c52019-06-25 10:04:24 -0700390 source_prop = prop.replace(
391 "ro.product", "ro.product.{}".format(source), 1)
392 prop_val = self.info_dict.get(
393 "{}.build.prop".format(source), {}).get(source_prop)
Steven Laver9e73e822019-01-29 20:20:08 -0800394 if prop_val:
395 return prop_val
396
397 raise common.ExternalError("couldn't resolve {}".format(prop))
398
Tao Bao481bab82017-12-21 11:23:09 -0800399 def GetVendorBuildProp(self, prop):
400 """Returns the inquired vendor build property."""
401 try:
402 return self.info_dict.get("vendor.build.prop", {})[prop]
403 except KeyError:
404 raise common.ExternalError(
405 "couldn't find %s in vendor.build.prop" % (prop,))
406
407 def GetOemProperty(self, key):
408 if self.oem_props is not None and key in self.oem_props:
409 return self.oem_dicts[0][key]
410 return self.GetBuildProp(key)
411
412 def CalculateFingerprint(self):
413 if self.oem_props is None:
Steven Laver9e73e822019-01-29 20:20:08 -0800414 try:
415 return self.GetBuildProp("ro.build.fingerprint")
416 except common.ExternalError:
417 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
Tao Bao59cf0c52019-06-25 10:04:24 -0700418 self.GetBuildProp("ro.product.brand"),
419 self.GetBuildProp("ro.product.name"),
420 self.GetBuildProp("ro.product.device"),
421 self.GetBuildProp("ro.build.version.release"),
422 self.GetBuildProp("ro.build.id"),
423 self.GetBuildProp("ro.build.version.incremental"),
424 self.GetBuildProp("ro.build.type"),
425 self.GetBuildProp("ro.build.tags"))
Tao Bao481bab82017-12-21 11:23:09 -0800426 return "%s/%s/%s:%s" % (
427 self.GetOemProperty("ro.product.brand"),
428 self.GetOemProperty("ro.product.name"),
429 self.GetOemProperty("ro.product.device"),
430 self.GetBuildProp("ro.build.thumbprint"))
431
432 def WriteMountOemScript(self, script):
433 assert self.oem_props is not None
434 recovery_mount_options = self.info_dict.get("recovery_mount_options")
435 script.Mount("/oem", recovery_mount_options)
436
437 def WriteDeviceAssertions(self, script, oem_no_mount):
438 # Read the property directly if not using OEM properties.
439 if not self.oem_props:
440 script.AssertDevice(self.device)
441 return
442
443 # Otherwise assert OEM properties.
444 if not self.oem_dicts:
445 raise common.ExternalError(
446 "No OEM file provided to answer expected assertions")
447
448 for prop in self.oem_props.split():
449 values = []
450 for oem_dict in self.oem_dicts:
451 if prop in oem_dict:
452 values.append(oem_dict[prop])
453 if not values:
454 raise common.ExternalError(
455 "The OEM file is missing the property %s" % (prop,))
456 script.AssertOemProperty(prop, values, oem_no_mount)
457
458
Tao Baofabe0832018-01-17 15:52:28 -0800459class PayloadSigner(object):
460 """A class that wraps the payload signing works.
461
462 When generating a Payload, hashes of the payload and metadata files will be
463 signed with the device key, either by calling an external payload signer or
464 by calling openssl with the package key. This class provides a unified
465 interface, so that callers can just call PayloadSigner.Sign().
466
467 If an external payload signer has been specified (OPTIONS.payload_signer), it
468 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
469 that the signing key should be provided as part of the payload_signer_args.
470 Otherwise without an external signer, it uses the package key
471 (OPTIONS.package_key) and calls openssl for the signing works.
472 """
473
474 def __init__(self):
475 if OPTIONS.payload_signer is None:
476 # Prepare the payload signing key.
477 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
478 pw = OPTIONS.key_passwords[OPTIONS.package_key]
479
480 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
481 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
482 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
483 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700484 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800485
486 self.signer = "openssl"
487 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
488 "-pkeyopt", "digest:sha256"]
xunchang376cc7c2019-04-08 23:04:58 -0700489 self.key_size = self._GetKeySizeInBytes(signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800490 else:
491 self.signer = OPTIONS.payload_signer
492 self.signer_args = OPTIONS.payload_signer_args
xunchang376cc7c2019-04-08 23:04:58 -0700493 if OPTIONS.payload_signer_key_size:
494 self.key_size = int(OPTIONS.payload_signer_key_size)
495 assert self.key_size == 256 or self.key_size == 512, \
496 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size)
497 else:
498 self.key_size = 256
499
500 @staticmethod
501 def _GetKeySizeInBytes(signing_key):
502 modulus_file = common.MakeTempFile(prefix="modulus-")
503 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus",
504 "-noout", "-out", modulus_file]
505 common.RunAndCheckOutput(cmd, verbose=False)
506
507 with open(modulus_file) as f:
508 modulus_string = f.read()
509 # The modulus string has the format "Modulus=$data", where $data is the
510 # concatenation of hex dump of the modulus.
511 MODULUS_PREFIX = "Modulus="
512 assert modulus_string.startswith(MODULUS_PREFIX)
513 modulus_string = modulus_string[len(MODULUS_PREFIX):]
Tao Bao59cf0c52019-06-25 10:04:24 -0700514 key_size = len(modulus_string) // 2
xunchang376cc7c2019-04-08 23:04:58 -0700515 assert key_size == 256 or key_size == 512, \
516 "Unsupported key size {}".format(key_size)
517 return key_size
Tao Baofabe0832018-01-17 15:52:28 -0800518
519 def Sign(self, in_file):
520 """Signs the given input file. Returns the output filename."""
521 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
522 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700523 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800524 return out_file
525
526
Tao Bao40b18822018-01-30 18:19:04 -0800527class Payload(object):
528 """Manages the creation and the signing of an A/B OTA Payload."""
529
530 PAYLOAD_BIN = 'payload.bin'
531 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800532 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
533 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800534
Tao Bao667ff572018-02-10 00:02:40 -0800535 def __init__(self, secondary=False):
536 """Initializes a Payload instance.
537
538 Args:
539 secondary: Whether it's generating a secondary payload (default: False).
540 """
Tao Bao40b18822018-01-30 18:19:04 -0800541 self.payload_file = None
542 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800543 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800544
Tao Baof0c4aa22018-04-30 20:29:30 -0700545 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700546 # Don't pipe (buffer) the output if verbose is set. Let
547 # brillo_update_payload write to stdout/stderr directly, so its progress can
548 # be monitored.
549 if OPTIONS.verbose:
550 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
551 else:
552 common.RunAndCheckOutput(cmd)
553
Tao Bao40b18822018-01-30 18:19:04 -0800554 def Generate(self, target_file, source_file=None, additional_args=None):
555 """Generates a payload from the given target-files zip(s).
556
557 Args:
558 target_file: The filename of the target build target-files zip.
559 source_file: The filename of the source build target-files zip; or None if
560 generating a full OTA.
561 additional_args: A list of additional args that should be passed to
562 brillo_update_payload script; or None.
563 """
564 if additional_args is None:
565 additional_args = []
566
567 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
568 cmd = ["brillo_update_payload", "generate",
569 "--payload", payload_file,
570 "--target_image", target_file]
571 if source_file is not None:
572 cmd.extend(["--source_image", source_file])
573 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700574 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800575
576 self.payload_file = payload_file
577 self.payload_properties = None
578
579 def Sign(self, payload_signer):
580 """Generates and signs the hashes of the payload and metadata.
581
582 Args:
583 payload_signer: A PayloadSigner() instance that serves the signing work.
584
585 Raises:
586 AssertionError: On any failure when calling brillo_update_payload script.
587 """
588 assert isinstance(payload_signer, PayloadSigner)
589
590 # 1. Generate hashes of the payload and metadata files.
591 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
592 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
593 cmd = ["brillo_update_payload", "hash",
594 "--unsigned_payload", self.payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700595 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800596 "--metadata_hash_file", metadata_sig_file,
597 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700598 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800599
600 # 2. Sign the hashes.
601 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
602 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
603
604 # 3. Insert the signatures back into the payload file.
605 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
606 suffix=".bin")
607 cmd = ["brillo_update_payload", "sign",
608 "--unsigned_payload", self.payload_file,
609 "--payload", signed_payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700610 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800611 "--metadata_signature_file", signed_metadata_sig_file,
612 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700613 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800614
615 # 4. Dump the signed payload properties.
616 properties_file = common.MakeTempFile(prefix="payload-properties-",
617 suffix=".txt")
618 cmd = ["brillo_update_payload", "properties",
619 "--payload", signed_payload_file,
620 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700621 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800622
Tao Bao667ff572018-02-10 00:02:40 -0800623 if self.secondary:
624 with open(properties_file, "a") as f:
625 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
626
Tao Bao40b18822018-01-30 18:19:04 -0800627 if OPTIONS.wipe_user_data:
628 with open(properties_file, "a") as f:
629 f.write("POWERWASH=1\n")
630
631 self.payload_file = signed_payload_file
632 self.payload_properties = properties_file
633
Tao Bao667ff572018-02-10 00:02:40 -0800634 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800635 """Writes the payload to the given zip.
636
637 Args:
638 output_zip: The output ZipFile instance.
639 """
640 assert self.payload_file is not None
641 assert self.payload_properties is not None
642
Tao Bao667ff572018-02-10 00:02:40 -0800643 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800644 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
645 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
646 else:
647 payload_arcname = Payload.PAYLOAD_BIN
648 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
649
Tao Bao40b18822018-01-30 18:19:04 -0800650 # Add the signed payload file and properties into the zip. In order to
651 # support streaming, we pack them as ZIP_STORED. So these entries can be
652 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800653 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800654 compress_type=zipfile.ZIP_STORED)
655 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800656 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800657 compress_type=zipfile.ZIP_STORED)
658
659
Doug Zongkereef39442009-04-02 12:14:19 -0700660def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200661 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700662
Doug Zongker951495f2009-08-14 12:44:19 -0700663 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
664 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700665
666
Tao Bao481bab82017-12-21 11:23:09 -0800667def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800668 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800669 if not oem_source:
670 return None
671
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800672 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800673 for oem_file in oem_source:
674 with open(oem_file) as fp:
675 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800676 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700677
Doug Zongkereef39442009-04-02 12:14:19 -0700678
Tao Baod42e97e2016-11-30 12:11:57 -0800679def _WriteRecoveryImageToBoot(script, output_zip):
680 """Find and write recovery image to /boot in two-step OTA.
681
682 In two-step OTAs, we write recovery image to /boot as the first step so that
683 we can reboot to there and install a new recovery image to /recovery.
684 A special "recovery-two-step.img" will be preferred, which encodes the correct
685 path of "/boot". Otherwise the device may show "device is corrupt" message
686 when booting into /boot.
687
688 Fall back to using the regular recovery.img if the two-step recovery image
689 doesn't exist. Note that rebuilding the special image at this point may be
690 infeasible, because we don't have the desired boot signer and keys when
691 calling ota_from_target_files.py.
692 """
693
694 recovery_two_step_img_name = "recovery-two-step.img"
695 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700696 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800697 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700698 common.ZipWrite(
699 output_zip,
700 recovery_two_step_img_path,
701 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700702 logger.info(
703 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800704 script.WriteRawImage("/boot", recovery_two_step_img_name)
705 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700706 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800707 # The "recovery.img" entry has been written into package earlier.
708 script.WriteRawImage("/boot", "recovery.img")
709
710
Doug Zongkerc9253822014-02-04 12:17:58 -0800711def HasRecoveryPatch(target_files_zip):
Tao Baof2cffbd2015-07-22 12:33:18 -0700712 namelist = [name for name in target_files_zip.namelist()]
713 return ("SYSTEM/recovery-from-boot.p" in namelist or
714 "SYSTEM/etc/recovery.img" in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700715
Tao Bao457cbf62017-03-06 09:56:01 -0800716
Yifan Hong51d37562019-04-23 17:06:46 -0700717def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700718 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700719 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700720 return True
721 except KeyError:
722 return False
723
Tao Bao457cbf62017-03-06 09:56:01 -0800724
Yifan Hong51d37562019-04-23 17:06:46 -0700725def HasVendorPartition(target_files_zip):
726 return HasPartition(target_files_zip, "vendor")
727
728
729def HasProductPartition(target_files_zip):
730 return HasPartition(target_files_zip, "product")
731
732
733def HasOdmPartition(target_files_zip):
734 return HasPartition(target_files_zip, "odm")
735
736
Tao Bao481bab82017-12-21 11:23:09 -0800737def HasTrebleEnabled(target_files_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700738 return (HasVendorPartition(target_files_zip) and
Tao Bao481bab82017-12-21 11:23:09 -0800739 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700740
741
Tao Bao481bab82017-12-21 11:23:09 -0800742def WriteFingerprintAssertion(script, target_info, source_info):
743 source_oem_props = source_info.oem_props
744 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700745
Tao Bao481bab82017-12-21 11:23:09 -0800746 if source_oem_props is None and target_oem_props is None:
747 script.AssertSomeFingerprint(
748 source_info.fingerprint, target_info.fingerprint)
749 elif source_oem_props is not None and target_oem_props is not None:
750 script.AssertSomeThumbprint(
751 target_info.GetBuildProp("ro.build.thumbprint"),
752 source_info.GetBuildProp("ro.build.thumbprint"))
753 elif source_oem_props is None and target_oem_props is not None:
754 script.AssertFingerprintOrThumbprint(
755 source_info.fingerprint,
756 target_info.GetBuildProp("ro.build.thumbprint"))
757 else:
758 script.AssertFingerprintOrThumbprint(
759 target_info.fingerprint,
760 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700761
Doug Zongkerfc44a512014-08-26 13:10:25 -0700762
Tao Bao481bab82017-12-21 11:23:09 -0800763def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info,
764 source_info=None):
Tao Baobcd1d162017-08-26 13:10:26 -0700765 """Adds compatibility info into the output zip if it's Treble-enabled target.
Tao Bao21803d32017-04-19 10:16:09 -0700766
767 Metadata used for on-device compatibility verification is retrieved from
768 target_zip then added to compatibility.zip which is added to the output_zip
769 archive.
770
Tao Baobcd1d162017-08-26 13:10:26 -0700771 Compatibility archive should only be included for devices that have enabled
772 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700773
774 Args:
775 target_zip: Zip file containing the source files to be included for OTA.
776 output_zip: Zip file that will be sent for OTA.
Tao Bao481bab82017-12-21 11:23:09 -0800777 target_info: The BuildInfo instance that holds the target build info.
778 source_info: The BuildInfo instance that holds the source build info, if
779 generating an incremental OTA; None otherwise.
Tao Bao21803d32017-04-19 10:16:09 -0700780 """
781
Yifan Hong51d37562019-04-23 17:06:46 -0700782 def AddCompatibilityArchive(framework_updated, device_updated):
783 """Adds compatibility info based on update status of both sides of Treble
784 boundary.
Tao Bao21803d32017-04-19 10:16:09 -0700785
Tao Baobcd1d162017-08-26 13:10:26 -0700786 Args:
Yifan Hong51d37562019-04-23 17:06:46 -0700787 framework_updated: If True, the system / product image will be updated
788 and therefore their metadata should be included.
789 device_updated: If True, the vendor / odm image will be updated and
790 therefore their metadata should be included.
Tao Baobcd1d162017-08-26 13:10:26 -0700791 """
792 # Determine what metadata we need. Files are names relative to META/.
793 compatibility_files = []
Yifan Hong51d37562019-04-23 17:06:46 -0700794 device_metadata = ("vendor_manifest.xml", "vendor_matrix.xml")
795 framework_metadata = ("system_manifest.xml", "system_matrix.xml")
796 if device_updated:
797 compatibility_files += device_metadata
798 if framework_updated:
799 compatibility_files += framework_metadata
Tao Bao21803d32017-04-19 10:16:09 -0700800
Tao Baobcd1d162017-08-26 13:10:26 -0700801 # Create new archive.
802 compatibility_archive = tempfile.NamedTemporaryFile()
Tao Bao481bab82017-12-21 11:23:09 -0800803 compatibility_archive_zip = zipfile.ZipFile(
804 compatibility_archive, "w", compression=zipfile.ZIP_DEFLATED)
Tao Bao21803d32017-04-19 10:16:09 -0700805
Tao Baobcd1d162017-08-26 13:10:26 -0700806 # Add metadata.
807 for file_name in compatibility_files:
808 target_file_name = "META/" + file_name
Tao Bao21803d32017-04-19 10:16:09 -0700809
Tao Baobcd1d162017-08-26 13:10:26 -0700810 if target_file_name in target_zip.namelist():
811 data = target_zip.read(target_file_name)
812 common.ZipWriteStr(compatibility_archive_zip, file_name, data)
Tao Bao21803d32017-04-19 10:16:09 -0700813
Tao Baobcd1d162017-08-26 13:10:26 -0700814 # Ensure files are written before we copy into output_zip.
815 compatibility_archive_zip.close()
816
817 # Only add the archive if we have any compatibility info.
818 if compatibility_archive_zip.namelist():
819 common.ZipWrite(output_zip, compatibility_archive.name,
820 arcname="compatibility.zip",
821 compress_type=zipfile.ZIP_STORED)
822
Yifan Hong51d37562019-04-23 17:06:46 -0700823 def FingerprintChanged(source_fp, target_fp):
824 if source_fp is None or target_fp is None:
825 return True
826 return source_fp != target_fp
827
Tao Baobcd1d162017-08-26 13:10:26 -0700828 # Will only proceed if the target has enabled the Treble support (as well as
829 # having a /vendor partition).
Tao Bao481bab82017-12-21 11:23:09 -0800830 if not HasTrebleEnabled(target_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700831 return
832
xunchangabfa2652019-02-19 16:27:10 -0800833 # Skip adding the compatibility package as a workaround for b/114240221. The
834 # compatibility will always fail on devices without qualified kernels.
835 if OPTIONS.skip_compatibility_check:
836 return
837
Yifan Hong51d37562019-04-23 17:06:46 -0700838 # Full OTA carries the info for system/vendor/product/odm
Tao Bao481bab82017-12-21 11:23:09 -0800839 if source_info is None:
Tao Baobcd1d162017-08-26 13:10:26 -0700840 AddCompatibilityArchive(True, True)
841 return
842
Tao Bao481bab82017-12-21 11:23:09 -0800843 source_fp = source_info.fingerprint
844 target_fp = target_info.fingerprint
Tao Baobcd1d162017-08-26 13:10:26 -0700845 system_updated = source_fp != target_fp
846
Yifan Hong51d37562019-04-23 17:06:46 -0700847 # other build fingerprints could be possibly blacklisted at build time. For
848 # such a case, we consider those images being changed.
849 vendor_updated = FingerprintChanged(source_info.vendor_fingerprint,
850 target_info.vendor_fingerprint)
851 product_updated = HasProductPartition(target_zip) and \
852 FingerprintChanged(source_info.product_fingerprint,
853 target_info.product_fingerprint)
854 odm_updated = HasOdmPartition(target_zip) and \
855 FingerprintChanged(source_info.odm_fingerprint,
856 target_info.odm_fingerprint)
Tao Baobcd1d162017-08-26 13:10:26 -0700857
Yifan Hong51d37562019-04-23 17:06:46 -0700858 AddCompatibilityArchive(system_updated or product_updated,
859 vendor_updated or odm_updated)
Tao Bao21803d32017-04-19 10:16:09 -0700860
861
Tao Bao491d7e22018-02-21 13:17:22 -0800862def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800863 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700864
Tao Bao481bab82017-12-21 11:23:09 -0800865 # We don't know what version it will be installed on top of. We expect the API
866 # just won't change very often. Similarly for fstab, it might have changed in
867 # the target build.
868 target_api_version = target_info["recovery_api_version"]
869 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700870
Tao Bao481bab82017-12-21 11:23:09 -0800871 if target_info.oem_props and not OPTIONS.oem_no_mount:
872 target_info.WriteMountOemScript(script)
873
Tao Baodf3a48b2018-01-10 16:30:43 -0800874 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700875
Tao Bao491d7e22018-02-21 13:17:22 -0800876 if not OPTIONS.no_signing:
877 staging_file = common.MakeTempFile(suffix='.zip')
878 else:
879 staging_file = output_file
880
881 output_zip = zipfile.ZipFile(
882 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
883
Doug Zongker05d3dea2009-06-22 11:32:31 -0700884 device_specific = common.DeviceSpecificParams(
885 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800886 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700887 output_zip=output_zip,
888 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700889 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700890 metadata=metadata,
891 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700892
Tao Bao457cbf62017-03-06 09:56:01 -0800893 assert HasRecoveryPatch(input_zip)
Doug Zongkerc9253822014-02-04 12:17:58 -0800894
Tao Bao481bab82017-12-21 11:23:09 -0800895 # Assertions (e.g. downgrade check, device properties check).
896 ts = target_info.GetBuildProp("ro.build.date.utc")
897 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700898 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700899
Tao Bao481bab82017-12-21 11:23:09 -0800900 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700901 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800902
903 # Two-step package strategy (in chronological order, which is *not*
904 # the order in which the generated script has things):
905 #
906 # if stage is not "2/3" or "3/3":
907 # write recovery image to boot partition
908 # set stage to "2/3"
909 # reboot to boot partition and restart recovery
910 # else if stage is "2/3":
911 # write recovery image to recovery partition
912 # set stage to "3/3"
913 # reboot to recovery partition and restart recovery
914 # else:
915 # (stage must be "3/3")
916 # set stage to ""
917 # do normal full package installation:
918 # wipe and install system, boot image, etc.
919 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700920 # complete script normally
921 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800922
923 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
924 OPTIONS.input_tmp, "RECOVERY")
925 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800926 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800927 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800928 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800929 assert fs.fs_type.upper() == "EMMC", \
930 "two-step packages only supported on devices with EMMC /misc partitions"
931 bcb_dev = {"bcb_dev": fs.device}
932 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
933 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700934if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800935""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800936
937 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
938 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800939 script.WriteRawImage("/recovery", "recovery.img")
940 script.AppendExtra("""
941set_stage("%(bcb_dev)s", "3/3");
942reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700943else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800944""" % bcb_dev)
945
Tao Baod42e97e2016-11-30 12:11:57 -0800946 # Stage 3/3: Make changes.
947 script.Comment("Stage 3/3")
948
Tao Bao6c55a8a2015-04-08 15:30:27 -0700949 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800950 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -0700951
Doug Zongkere5ff5902012-01-17 10:55:37 -0800952 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700953
Doug Zongker01ce19c2014-02-04 13:48:15 -0800954 system_progress = 0.75
Doug Zongkereef39442009-04-02 12:14:19 -0700955
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700956 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -0800957 system_progress -= 0.1
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700958 if HasVendorPartition(input_zip):
959 system_progress -= 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700960
Doug Zongker4b9596f2014-06-09 14:15:45 -0700961 script.ShowProgress(system_progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -0800962
Yifan Hong10c530d2018-12-27 17:34:18 -0800963 def GetBlockDifference(partition):
964 # Full OTA is done as an "incremental" against an empty source image. This
965 # has the effect of writing new data from the package to the entire
966 # partition, but lets us reuse the updater code that writes incrementals to
967 # do it.
Yifan Hong8a66a712019-04-04 15:37:57 -0700968 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, input_zip,
969 info_dict=target_info,
970 reset_file_map=True)
Yifan Hong10c530d2018-12-27 17:34:18 -0800971 diff = common.BlockDifference(partition, tgt, src=None)
972 return diff
Doug Zongkereef39442009-04-02 12:14:19 -0700973
Yifan Hong10c530d2018-12-27 17:34:18 -0800974 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
975 if device_specific_diffs:
976 assert all(isinstance(diff, common.BlockDifference)
977 for diff in device_specific_diffs), \
978 "FullOTA_GetBlockDifferences is not returning a list of " \
979 "BlockDifference objects"
Doug Zongkerc9253822014-02-04 12:17:58 -0800980
Yifan Hong10c530d2018-12-27 17:34:18 -0800981 progress_dict = dict()
982 block_diffs = [GetBlockDifference("system")]
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700983 if HasVendorPartition(input_zip):
Yifan Hong10c530d2018-12-27 17:34:18 -0800984 block_diffs.append(GetBlockDifference("vendor"))
985 progress_dict["vendor"] = 0.1
986 if device_specific_diffs:
987 block_diffs += device_specific_diffs
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700988
Yifan Hong10c530d2018-12-27 17:34:18 -0800989 if target_info.get('use_dynamic_partitions') == "true":
990 # Use empty source_info_dict to indicate that all partitions / groups must
991 # be re-added.
992 dynamic_partitions_diff = common.DynamicPartitionsDifference(
993 info_dict=OPTIONS.info_dict,
994 block_diffs=block_diffs,
995 progress_dict=progress_dict)
996 dynamic_partitions_diff.WriteScript(script, output_zip,
997 write_verify_script=OPTIONS.verify)
998 else:
999 for block_diff in block_diffs:
1000 block_diff.WriteScript(script, output_zip,
1001 progress=progress_dict.get(block_diff.partition),
1002 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001003
Tao Bao481bab82017-12-21 11:23:09 -08001004 AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001005
Yifan Hong10c530d2018-12-27 17:34:18 -08001006 boot_img = common.GetBootableImage(
1007 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001008 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001009 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001010
Doug Zongker01ce19c2014-02-04 13:48:15 -08001011 script.ShowProgress(0.05, 5)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001012 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001013
Doug Zongker01ce19c2014-02-04 13:48:15 -08001014 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001015 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001016
Doug Zongker1c390a22009-05-14 19:06:36 -07001017 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001018 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001019
Doug Zongker14833602010-02-02 13:12:04 -08001020 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001021
Doug Zongker922206e2014-03-04 13:16:24 -08001022 if OPTIONS.wipe_user_data:
1023 script.ShowProgress(0.1, 10)
1024 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001025
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001026 if OPTIONS.two_step:
1027 script.AppendExtra("""
1028set_stage("%(bcb_dev)s", "");
1029""" % bcb_dev)
1030 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001031
1032 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1033 script.Comment("Stage 1/3")
1034 _WriteRecoveryImageToBoot(script, output_zip)
1035
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001036 script.AppendExtra("""
1037set_stage("%(bcb_dev)s", "2/3");
1038reboot_now("%(bcb_dev)s", "");
1039endif;
1040endif;
1041""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001042
Tao Bao5d182562016-02-23 11:38:39 -08001043 script.SetProgress(1)
1044 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001045 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001046
1047 # We haven't written the metadata entry, which will be done in
1048 # FinalizeMetadata.
1049 common.ZipClose(output_zip)
1050
1051 needed_property_files = (
1052 NonAbOtaPropertyFiles(),
1053 )
1054 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001055
Doug Zongkerfc44a512014-08-26 13:10:25 -07001056
xunchang1cfe2512019-02-19 14:14:48 -08001057def WriteMetadata(metadata, output):
1058 """Writes the metadata to the zip archive or a file.
1059
1060 Args:
1061 metadata: The metadata dict for the package.
1062 output: A ZipFile object or a string of the output file path.
1063 """
1064
Tao Bao59cf0c52019-06-25 10:04:24 -07001065 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001066 if isinstance(output, zipfile.ZipFile):
1067 common.ZipWriteStr(output, METADATA_NAME, value,
1068 compress_type=zipfile.ZIP_STORED)
1069 return
1070
1071 with open(output, 'w') as f:
1072 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001073
Doug Zongkerfc44a512014-08-26 13:10:25 -07001074
Tao Bao481bab82017-12-21 11:23:09 -08001075def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001076 # Only incremental OTAs are allowed to reach here.
1077 assert OPTIONS.incremental_source is not None
1078
Tao Bao481bab82017-12-21 11:23:09 -08001079 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1080 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001081 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001082
1083 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001084 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001085 raise RuntimeError(
1086 "--downgrade or --override_timestamp specified but no downgrade "
1087 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001088 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001089 else:
1090 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001091 raise RuntimeError(
1092 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1093 "Need to specify --override_timestamp OR --downgrade to allow "
1094 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001095
1096
Tao Baodf3a48b2018-01-10 16:30:43 -08001097def GetPackageMetadata(target_info, source_info=None):
1098 """Generates and returns the metadata dict.
1099
1100 It generates a dict() that contains the info to be written into an OTA
1101 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001102 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001103
1104 Args:
1105 target_info: The BuildInfo instance that holds the target build info.
1106 source_info: The BuildInfo instance that holds the source build info, or
1107 None if generating full OTA.
1108
1109 Returns:
1110 A dict to be written into package metadata entry.
1111 """
1112 assert isinstance(target_info, BuildInfo)
1113 assert source_info is None or isinstance(source_info, BuildInfo)
1114
1115 metadata = {
1116 'post-build' : target_info.fingerprint,
1117 'post-build-incremental' : target_info.GetBuildProp(
1118 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001119 'post-sdk-level' : target_info.GetBuildProp(
1120 'ro.build.version.sdk'),
1121 'post-security-patch-level' : target_info.GetBuildProp(
1122 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001123 }
1124
1125 if target_info.is_ab:
1126 metadata['ota-type'] = 'AB'
1127 metadata['ota-required-cache'] = '0'
1128 else:
1129 metadata['ota-type'] = 'BLOCK'
1130
1131 if OPTIONS.wipe_user_data:
1132 metadata['ota-wipe'] = 'yes'
1133
Tao Bao393eeb42019-03-06 16:00:38 -08001134 if OPTIONS.retrofit_dynamic_partitions:
1135 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1136
Tao Baodf3a48b2018-01-10 16:30:43 -08001137 is_incremental = source_info is not None
1138 if is_incremental:
1139 metadata['pre-build'] = source_info.fingerprint
1140 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1141 'ro.build.version.incremental')
1142 metadata['pre-device'] = source_info.device
1143 else:
1144 metadata['pre-device'] = target_info.device
1145
Tao Baofaa8e0b2018-04-12 14:31:43 -07001146 # Use the actual post-timestamp, even for a downgrade case.
1147 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1148
1149 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001150 if is_incremental:
1151 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001152
1153 return metadata
1154
1155
Tao Baod3fc38a2018-03-08 16:09:01 -08001156class PropertyFiles(object):
1157 """A class that computes the property-files string for an OTA package.
1158
1159 A property-files string is a comma-separated string that contains the
1160 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1161 can be fetched directly with the package URL along with the offset/size info.
1162 These strings can be used for streaming A/B OTAs, or allowing an updater to
1163 download package metadata entry directly, without paying the cost of
1164 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001165
Tao Baocc8e2662018-03-01 19:30:00 -08001166 Computing the final property-files string requires two passes. Because doing
1167 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1168 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1169 values.
1170
1171 This class provides functions to be called for each pass. The general flow is
1172 as follows.
1173
Tao Baod3fc38a2018-03-08 16:09:01 -08001174 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001175 # The first pass, which writes placeholders before doing initial signing.
1176 property_files.Compute()
1177 SignOutput()
1178
1179 # The second pass, by replacing the placeholders with actual data.
1180 property_files.Finalize()
1181 SignOutput()
1182
1183 And the caller can additionally verify the final result.
1184
1185 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001186 """
1187
Tao Baocc8e2662018-03-01 19:30:00 -08001188 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001189 self.name = None
1190 self.required = ()
1191 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001192
Tao Baocc8e2662018-03-01 19:30:00 -08001193 def Compute(self, input_zip):
1194 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001195
Tao Baocc8e2662018-03-01 19:30:00 -08001196 We reserve extra space for the offset and size of the metadata entry itself,
1197 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001198
Tao Baocc8e2662018-03-01 19:30:00 -08001199 Args:
1200 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001201
Tao Baocc8e2662018-03-01 19:30:00 -08001202 Returns:
1203 A string with placeholders for the metadata offset/size info, e.g.
1204 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1205 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001206 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001207
Tao Baod2ce2ed2018-03-16 12:59:42 -07001208 class InsufficientSpaceException(Exception):
1209 pass
1210
Tao Baocc8e2662018-03-01 19:30:00 -08001211 def Finalize(self, input_zip, reserved_length):
1212 """Finalizes a property-files string with actual METADATA offset/size info.
1213
1214 The input ZIP file has been signed, with the ZIP entries in the desired
1215 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1216 the ZIP entry offsets and construct the property-files string with actual
1217 data. Note that during this process, we must pad the property-files string
1218 to the reserved length, so that the METADATA entry size remains the same.
1219 Otherwise the entries' offsets and sizes may change again.
1220
1221 Args:
1222 input_zip: The input ZIP file.
1223 reserved_length: The reserved length of the property-files string during
1224 the call to Compute(). The final string must be no more than this
1225 size.
1226
1227 Returns:
1228 A property-files string including the metadata offset/size info, e.g.
1229 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1230
1231 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001232 InsufficientSpaceException: If the reserved length is insufficient to hold
1233 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001234 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001235 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001236 if len(result) > reserved_length:
1237 raise self.InsufficientSpaceException(
1238 'Insufficient reserved space: reserved={}, actual={}'.format(
1239 reserved_length, len(result)))
1240
Tao Baocc8e2662018-03-01 19:30:00 -08001241 result += ' ' * (reserved_length - len(result))
1242 return result
1243
1244 def Verify(self, input_zip, expected):
1245 """Verifies the input ZIP file contains the expected property-files string.
1246
1247 Args:
1248 input_zip: The input ZIP file.
1249 expected: The property-files string that's computed from Finalize().
1250
1251 Raises:
1252 AssertionError: On finding a mismatch.
1253 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001254 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001255 assert actual == expected, \
1256 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1257
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001258 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1259 """
1260 Constructs the property-files string per request.
1261
1262 Args:
1263 zip_file: The input ZIP file.
1264 reserved_length: The reserved length of the property-files string.
1265
1266 Returns:
1267 A property-files string including the metadata offset/size info, e.g.
1268 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1269 """
Tao Baocc8e2662018-03-01 19:30:00 -08001270
1271 def ComputeEntryOffsetSize(name):
1272 """Computes the zip entry offset and size."""
1273 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001274 offset = info.header_offset
1275 offset += zipfile.sizeFileHeader
1276 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001277 size = info.file_size
1278 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1279
1280 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001281 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001282 for entry in self.required:
1283 tokens.append(ComputeEntryOffsetSize(entry))
1284 for entry in self.optional:
1285 if entry in zip_file.namelist():
1286 tokens.append(ComputeEntryOffsetSize(entry))
1287
1288 # 'META-INF/com/android/metadata' is required. We don't know its actual
1289 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001290 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1291 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1292 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1293 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001294 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001295 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001296 else:
1297 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1298
1299 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001300
Tao Bao85f16982018-03-08 16:28:33 -08001301 def _GetPrecomputed(self, input_zip):
1302 """Computes the additional tokens to be included into the property-files.
1303
1304 This applies to tokens without actual ZIP entries, such as
1305 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1306 that they can download the payload metadata directly with the info.
1307
1308 Args:
1309 input_zip: The input zip file.
1310
1311 Returns:
1312 A list of strings (tokens) to be added to the property-files string.
1313 """
1314 # pylint: disable=no-self-use
1315 # pylint: disable=unused-argument
1316 return []
1317
Tao Baofe5b69a2018-03-02 09:47:43 -08001318
Tao Baod3fc38a2018-03-08 16:09:01 -08001319class StreamingPropertyFiles(PropertyFiles):
1320 """A subclass for computing the property-files for streaming A/B OTAs."""
1321
1322 def __init__(self):
1323 super(StreamingPropertyFiles, self).__init__()
1324 self.name = 'ota-streaming-property-files'
1325 self.required = (
1326 # payload.bin and payload_properties.txt must exist.
1327 'payload.bin',
1328 'payload_properties.txt',
1329 )
1330 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001331 # care_map is available only if dm-verity is enabled.
1332 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001333 'care_map.txt',
1334 # compatibility.zip is available only if target supports Treble.
1335 'compatibility.zip',
1336 )
1337
1338
Tao Bao85f16982018-03-08 16:28:33 -08001339class AbOtaPropertyFiles(StreamingPropertyFiles):
1340 """The property-files for A/B OTA that includes payload_metadata.bin info.
1341
1342 Since P, we expose one more token (aka property-file), in addition to the ones
1343 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1344 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1345 doesn't exist as a separate ZIP entry, but can be used to verify if the
1346 payload can be applied on the given device.
1347
1348 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1349 and the newly added 'ota-property-files' in P. The new token will only be
1350 available in 'ota-property-files'.
1351 """
1352
1353 def __init__(self):
1354 super(AbOtaPropertyFiles, self).__init__()
1355 self.name = 'ota-property-files'
1356
1357 def _GetPrecomputed(self, input_zip):
1358 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1359 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1360
1361 @staticmethod
1362 def _GetPayloadMetadataOffsetAndSize(input_zip):
1363 """Computes the offset and size of the payload metadata for a given package.
1364
1365 (From system/update_engine/update_metadata.proto)
1366 A delta update file contains all the deltas needed to update a system from
1367 one specific version to another specific version. The update format is
1368 represented by this struct pseudocode:
1369
1370 struct delta_update_file {
1371 char magic[4] = "CrAU";
1372 uint64 file_format_version;
1373 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1374
1375 // Only present if format_version > 1:
1376 uint32 metadata_signature_size;
1377
1378 // The Bzip2 compressed DeltaArchiveManifest
1379 char manifest[metadata_signature_size];
1380
1381 // The signature of the metadata (from the beginning of the payload up to
1382 // this location, not including the signature itself). This is a
1383 // serialized Signatures message.
1384 char medatada_signature_message[metadata_signature_size];
1385
1386 // Data blobs for files, no specific format. The specific offset
1387 // and length of each data blob is recorded in the DeltaArchiveManifest.
1388 struct {
1389 char data[];
1390 } blobs[];
1391
1392 // These two are not signed:
1393 uint64 payload_signatures_message_size;
1394 char payload_signatures_message[];
1395 };
1396
1397 'payload-metadata.bin' contains all the bytes from the beginning of the
1398 payload, till the end of 'medatada_signature_message'.
1399 """
1400 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001401 payload_offset = payload_info.header_offset
1402 payload_offset += zipfile.sizeFileHeader
1403 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001404 payload_size = payload_info.file_size
1405
Tao Bao59cf0c52019-06-25 10:04:24 -07001406 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001407 header_bin = payload_fp.read(24)
1408
1409 # network byte order (big-endian)
1410 header = struct.unpack("!IQQL", header_bin)
1411
1412 # 'CrAU'
1413 magic = header[0]
1414 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1415
1416 manifest_size = header[2]
1417 metadata_signature_size = header[3]
1418 metadata_total = 24 + manifest_size + metadata_signature_size
1419 assert metadata_total < payload_size
1420
1421 return (payload_offset, metadata_total)
1422
1423
Tao Bao491d7e22018-02-21 13:17:22 -08001424class NonAbOtaPropertyFiles(PropertyFiles):
1425 """The property-files for non-A/B OTA.
1426
1427 For non-A/B OTA, the property-files string contains the info for METADATA
1428 entry, with which a system updater can be fetched the package metadata prior
1429 to downloading the entire package.
1430 """
1431
1432 def __init__(self):
1433 super(NonAbOtaPropertyFiles, self).__init__()
1434 self.name = 'ota-property-files'
1435
1436
Tao Baod3fc38a2018-03-08 16:09:01 -08001437def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001438 """Finalizes the metadata and signs an A/B OTA package.
1439
1440 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1441 that contains the offsets and sizes for the ZIP entries. An example
1442 property-files string is as follows.
1443
1444 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1445
1446 OTA server can pass down this string, in addition to the package URL, to the
1447 system update client. System update client can then fetch individual ZIP
1448 entries (ZIP_STORED) directly at the given offset of the URL.
1449
1450 Args:
1451 metadata: The metadata dict for the package.
1452 input_file: The input ZIP filename that doesn't contain the package METADATA
1453 entry yet.
1454 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001455 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001456 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001457
Tao Baod2ce2ed2018-03-16 12:59:42 -07001458 def ComputeAllPropertyFiles(input_file, needed_property_files):
1459 # Write the current metadata entry with placeholders.
1460 with zipfile.ZipFile(input_file) as input_zip:
1461 for property_files in needed_property_files:
1462 metadata[property_files.name] = property_files.Compute(input_zip)
1463 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001464
Tao Baod2ce2ed2018-03-16 12:59:42 -07001465 if METADATA_NAME in namelist:
1466 common.ZipDelete(input_file, METADATA_NAME)
1467 output_zip = zipfile.ZipFile(input_file, 'a')
1468 WriteMetadata(metadata, output_zip)
1469 common.ZipClose(output_zip)
1470
1471 if OPTIONS.no_signing:
1472 return input_file
1473
Tao Bao491d7e22018-02-21 13:17:22 -08001474 prelim_signing = common.MakeTempFile(suffix='.zip')
1475 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001476 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001477
Tao Baod2ce2ed2018-03-16 12:59:42 -07001478 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1479 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1480 for property_files in needed_property_files:
1481 metadata[property_files.name] = property_files.Finalize(
1482 prelim_signing_zip, len(metadata[property_files.name]))
1483
1484 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1485 # entries, as well as padding the entry headers. We do a preliminary signing
1486 # (with an incomplete metadata entry) to allow that to happen. Then compute
1487 # the ZIP entry offsets, write back the final metadata and do the final
1488 # signing.
1489 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1490 try:
1491 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1492 except PropertyFiles.InsufficientSpaceException:
1493 # Even with the preliminary signing, the entry orders may change
1494 # dramatically, which leads to insufficiently reserved space during the
1495 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1496 # preliminary signing works, based on the already ordered ZIP entries, to
1497 # address the issue.
1498 prelim_signing = ComputeAllPropertyFiles(
1499 prelim_signing, needed_property_files)
1500 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001501
1502 # Replace the METADATA entry.
1503 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001504 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001505 WriteMetadata(metadata, output_zip)
1506 common.ZipClose(output_zip)
1507
1508 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001509 if OPTIONS.no_signing:
1510 output_file = prelim_signing
1511 else:
1512 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001513
1514 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001515 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001516 for property_files in needed_property_files:
1517 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001518
xunchang1cfe2512019-02-19 14:14:48 -08001519 # If requested, dump the metadata to a separate file.
1520 output_metadata_path = OPTIONS.output_metadata_path
1521 if output_metadata_path:
1522 WriteMetadata(metadata, output_metadata_path)
1523
Tao Baofe5b69a2018-03-02 09:47:43 -08001524
Tao Bao491d7e22018-02-21 13:17:22 -08001525def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001526 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1527 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001528
Tao Bao481bab82017-12-21 11:23:09 -08001529 target_api_version = target_info["recovery_api_version"]
1530 source_api_version = source_info["recovery_api_version"]
1531 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001532 logger.warning(
1533 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001534
Tao Bao481bab82017-12-21 11:23:09 -08001535 script = edify_generator.EdifyGenerator(
1536 source_api_version, target_info, fstab=source_info["fstab"])
1537
1538 if target_info.oem_props or source_info.oem_props:
1539 if not OPTIONS.oem_no_mount:
1540 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001541
Tao Baodf3a48b2018-01-10 16:30:43 -08001542 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001543
Tao Bao491d7e22018-02-21 13:17:22 -08001544 if not OPTIONS.no_signing:
1545 staging_file = common.MakeTempFile(suffix='.zip')
1546 else:
1547 staging_file = output_file
1548
1549 output_zip = zipfile.ZipFile(
1550 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1551
Geremy Condra36bd3652014-02-06 19:45:10 -08001552 device_specific = common.DeviceSpecificParams(
1553 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001554 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001555 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001556 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001557 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001558 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001559 output_zip=output_zip,
1560 script=script,
1561 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001562 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001563
Geremy Condra36bd3652014-02-06 19:45:10 -08001564 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001565 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001566 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001567 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001568 updating_boot = (not OPTIONS.two_step and
1569 (source_boot.data != target_boot.data))
1570
Geremy Condra36bd3652014-02-06 19:45:10 -08001571 target_recovery = common.GetBootableImage(
1572 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001573
Yifan Hong8a66a712019-04-04 15:37:57 -07001574 # See notes in common.GetUserImage()
Tao Baoe709b092018-02-07 12:40:00 -08001575 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
1576 target_info.get('ext4_share_dup_blocks') == "true")
Yifan Hong8a66a712019-04-04 15:37:57 -07001577 system_src = common.GetUserImage("system", OPTIONS.source_tmp, source_zip,
1578 info_dict=source_info,
1579 allow_shared_blocks=allow_shared_blocks)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001580
1581 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
1582 "system", 4096, target_info)
Yifan Hong8a66a712019-04-04 15:37:57 -07001583 system_tgt = common.GetUserImage("system", OPTIONS.target_tmp, target_zip,
1584 info_dict=target_info,
1585 allow_shared_blocks=allow_shared_blocks,
1586 hashtree_info_generator=
1587 hashtree_info_generator)
Tao Baodd2a5892015-03-12 12:32:37 -07001588
Tao Bao0582cb62017-12-21 11:47:01 -08001589 blockimgdiff_version = max(
Tao Bao481bab82017-12-21 11:23:09 -08001590 int(i) for i in target_info.get("blockimgdiff_versions", "1").split(","))
Tao Bao0582cb62017-12-21 11:47:01 -08001591 assert blockimgdiff_version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001592
Tao Baof8acad12016-07-07 09:09:58 -07001593 # Check the first block of the source system partition for remount R/W only
1594 # if the filesystem is ext4.
Tao Bao481bab82017-12-21 11:23:09 -08001595 system_src_partition = source_info["fstab"]["/system"]
Tao Baof8acad12016-07-07 09:09:58 -07001596 check_first_block = system_src_partition.fs_type == "ext4"
Tao Bao293fd132016-06-11 12:19:23 -07001597 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
1598 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
1599 # b) the blocks listed in block map may not contain all the bytes for a given
1600 # file (because they're rounded to be 4K-aligned).
Tao Bao481bab82017-12-21 11:23:09 -08001601 system_tgt_partition = target_info["fstab"]["/system"]
Tao Baof8acad12016-07-07 09:09:58 -07001602 disable_imgdiff = (system_src_partition.fs_type == "squashfs" or
1603 system_tgt_partition.fs_type == "squashfs")
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001604 system_diff = common.BlockDifference("system", system_tgt, system_src,
Tianjie Xufc3422a2015-12-15 11:53:59 -08001605 check_first_block,
Tao Bao293fd132016-06-11 12:19:23 -07001606 version=blockimgdiff_version,
1607 disable_imgdiff=disable_imgdiff)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001608
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001609 if HasVendorPartition(target_zip):
1610 if not HasVendorPartition(source_zip):
1611 raise RuntimeError("can't generate incremental that adds /vendor")
Yifan Hong8a66a712019-04-04 15:37:57 -07001612 vendor_src = common.GetUserImage("vendor", OPTIONS.source_tmp, source_zip,
1613 info_dict=source_info,
1614 allow_shared_blocks=allow_shared_blocks)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001615 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
1616 "vendor", 4096, target_info)
Yifan Hong8a66a712019-04-04 15:37:57 -07001617 vendor_tgt = common.GetUserImage(
1618 "vendor", OPTIONS.target_tmp, target_zip,
1619 info_dict=target_info,
1620 allow_shared_blocks=allow_shared_blocks,
1621 hashtree_info_generator=hashtree_info_generator)
Tianjie Xufc3422a2015-12-15 11:53:59 -08001622
1623 # Check first block of vendor partition for remount R/W only if
1624 # disk type is ext4
Tao Bao481bab82017-12-21 11:23:09 -08001625 vendor_partition = source_info["fstab"]["/vendor"]
Tao Baod8d14be2016-02-04 14:26:02 -08001626 check_first_block = vendor_partition.fs_type == "ext4"
Tao Bao293fd132016-06-11 12:19:23 -07001627 disable_imgdiff = vendor_partition.fs_type == "squashfs"
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001628 vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
Tianjie Xufc3422a2015-12-15 11:53:59 -08001629 check_first_block,
Tao Bao293fd132016-06-11 12:19:23 -07001630 version=blockimgdiff_version,
1631 disable_imgdiff=disable_imgdiff)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001632 else:
1633 vendor_diff = None
Geremy Condra36bd3652014-02-06 19:45:10 -08001634
Tao Baobcd1d162017-08-26 13:10:26 -07001635 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08001636 target_zip, output_zip, target_info, source_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001637
Tao Bao481bab82017-12-21 11:23:09 -08001638 # Assertions (e.g. device properties check).
1639 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001640 device_specific.IncrementalOTA_Assertions()
1641
1642 # Two-step incremental package strategy (in chronological order,
1643 # which is *not* the order in which the generated script has
1644 # things):
1645 #
1646 # if stage is not "2/3" or "3/3":
1647 # do verification on current system
1648 # write recovery image to boot partition
1649 # set stage to "2/3"
1650 # reboot to boot partition and restart recovery
1651 # else if stage is "2/3":
1652 # write recovery image to recovery partition
1653 # set stage to "3/3"
1654 # reboot to recovery partition and restart recovery
1655 # else:
1656 # (stage must be "3/3")
1657 # perform update:
1658 # patch system files, etc.
1659 # force full install of new boot image
1660 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001661 # complete script normally
1662 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001663
1664 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001665 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001666 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001667 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001668 assert fs.fs_type.upper() == "EMMC", \
1669 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001670 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001671 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1672 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001673if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001674""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001675
1676 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1677 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001678 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001679 script.WriteRawImage("/recovery", "recovery.img")
1680 script.AppendExtra("""
1681set_stage("%(bcb_dev)s", "3/3");
1682reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001683else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001684""" % bcb_dev)
1685
Tao Baod42e97e2016-11-30 12:11:57 -08001686 # Stage 1/3: (a) Verify the current system.
1687 script.Comment("Stage 1/3")
1688
Tao Bao6c55a8a2015-04-08 15:30:27 -07001689 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001690 script.Print("Source: {}".format(source_info.fingerprint))
1691 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001692
Geremy Condra36bd3652014-02-06 19:45:10 -08001693 script.Print("Verifying current system...")
1694
1695 device_specific.IncrementalOTA_VerifyBegin()
1696
Tao Bao481bab82017-12-21 11:23:09 -08001697 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001698
Tao Baod8d14be2016-02-04 14:26:02 -08001699 # Check the required cache size (i.e. stashed blocks).
1700 size = []
1701 if system_diff:
1702 size.append(system_diff.required_cache)
1703 if vendor_diff:
1704 size.append(vendor_diff.required_cache)
1705
Geremy Condra36bd3652014-02-06 19:45:10 -08001706 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001707 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001708 d = common.Difference(target_boot, source_boot)
1709 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001710 if d is None:
1711 include_full_boot = True
1712 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1713 else:
1714 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001715
Tao Bao32fcdab2018-10-12 10:30:39 -07001716 logger.info(
1717 "boot target: %d source: %d diff: %d", target_boot.size,
1718 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001719
Tao Bao51216552018-08-26 11:53:15 -07001720 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001721
Tao Bao51216552018-08-26 11:53:15 -07001722 script.PatchPartitionCheck(
1723 "{}:{}:{}:{}".format(
1724 boot_type, boot_device, target_boot.size, target_boot.sha1),
1725 "{}:{}:{}:{}".format(
1726 boot_type, boot_device, source_boot.size, source_boot.sha1))
1727
Tao Baod8d14be2016-02-04 14:26:02 -08001728 size.append(target_boot.size)
1729
1730 if size:
1731 script.CacheFreeSpaceCheck(max(size))
Geremy Condra36bd3652014-02-06 19:45:10 -08001732
1733 device_specific.IncrementalOTA_VerifyEnd()
1734
1735 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001736 # Stage 1/3: (b) Write recovery image to /boot.
1737 _WriteRecoveryImageToBoot(script, output_zip)
1738
Geremy Condra36bd3652014-02-06 19:45:10 -08001739 script.AppendExtra("""
1740set_stage("%(bcb_dev)s", "2/3");
1741reboot_now("%(bcb_dev)s", "");
1742else
1743""" % bcb_dev)
1744
Tao Baod42e97e2016-11-30 12:11:57 -08001745 # Stage 3/3: Make changes.
1746 script.Comment("Stage 3/3")
1747
Jesse Zhao75bcea02015-01-06 10:59:53 -08001748 # Verify the existing partitions.
Tao Baod522bdc2016-04-12 15:53:16 -07001749 system_diff.WriteVerifyScript(script, touched_blocks_only=True)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001750 if vendor_diff:
Tao Baod522bdc2016-04-12 15:53:16 -07001751 vendor_diff.WriteVerifyScript(script, touched_blocks_only=True)
Yifan Hong10c530d2018-12-27 17:34:18 -08001752 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
1753 if device_specific_diffs:
1754 assert all(isinstance(diff, common.BlockDifference)
1755 for diff in device_specific_diffs), \
1756 "IncrementalOTA_GetBlockDifferences is not returning a list of " \
1757 "BlockDifference objects"
1758 for diff in device_specific_diffs:
1759 diff.WriteVerifyScript(script, touched_blocks_only=True)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001760
Geremy Condra36bd3652014-02-06 19:45:10 -08001761 script.Comment("---- start making changes here ----")
1762
1763 device_specific.IncrementalOTA_InstallBegin()
1764
Yifan Hong10c530d2018-12-27 17:34:18 -08001765 block_diffs = [system_diff]
1766 progress_dict = {"system": 0.8 if vendor_diff else 0.9}
Doug Zongkerfc44a512014-08-26 13:10:25 -07001767 if vendor_diff:
Yifan Hong10c530d2018-12-27 17:34:18 -08001768 block_diffs.append(vendor_diff)
1769 progress_dict["vendor"] = 0.1
1770 if device_specific_diffs:
1771 block_diffs += device_specific_diffs
1772
1773 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1774 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1775 raise RuntimeError(
1776 "can't generate incremental that disables dynamic partitions")
1777 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1778 info_dict=OPTIONS.target_info_dict,
1779 source_info_dict=OPTIONS.source_info_dict,
1780 block_diffs=block_diffs,
1781 progress_dict=progress_dict)
1782 dynamic_partitions_diff.WriteScript(
1783 script, output_zip, write_verify_script=OPTIONS.verify)
1784 else:
1785 for block_diff in block_diffs:
1786 block_diff.WriteScript(script, output_zip,
1787 progress=progress_dict.get(block_diff.partition),
1788 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001789
1790 if OPTIONS.two_step:
1791 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1792 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001793 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001794
1795 if not OPTIONS.two_step:
1796 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001797 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001798 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001799 script.Print("Installing boot image...")
1800 script.WriteRawImage("/boot", "boot.img")
1801 else:
1802 # Produce the boot image by applying a patch to the current
1803 # contents of the boot partition, and write it back to the
1804 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001805 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001806 script.Print("Patching boot image...")
1807 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001808 script.PatchPartition(
1809 '{}:{}:{}:{}'.format(
1810 boot_type, boot_device, target_boot.size, target_boot.sha1),
1811 '{}:{}:{}:{}'.format(
1812 boot_type, boot_device, source_boot.size, source_boot.sha1),
1813 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001814 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001815 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001816
1817 # Do device-specific installation (eg, write radio image).
1818 device_specific.IncrementalOTA_InstallEnd()
1819
1820 if OPTIONS.extra_script is not None:
1821 script.AppendExtra(OPTIONS.extra_script)
1822
Doug Zongker922206e2014-03-04 13:16:24 -08001823 if OPTIONS.wipe_user_data:
1824 script.Print("Erasing user data...")
1825 script.FormatPartition("/data")
1826
Geremy Condra36bd3652014-02-06 19:45:10 -08001827 if OPTIONS.two_step:
1828 script.AppendExtra("""
1829set_stage("%(bcb_dev)s", "");
1830endif;
1831endif;
1832""" % bcb_dev)
1833
1834 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001835 # For downgrade OTAs, we prefer to use the update-binary in the source
1836 # build that is actually newer than the one in the target build.
1837 if OPTIONS.downgrade:
1838 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1839 else:
1840 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001841 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001842
1843 # We haven't written the metadata entry yet, which will be handled in
1844 # FinalizeMetadata().
1845 common.ZipClose(output_zip)
1846
1847 # Sign the generated zip package unless no_signing is specified.
1848 needed_property_files = (
1849 NonAbOtaPropertyFiles(),
1850 )
1851 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001852
Doug Zongker32b527d2014-03-04 10:03:02 -08001853
Tao Bao15a146a2018-02-21 16:06:59 -08001854def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001855 """Returns a target-files.zip file for generating secondary payload.
1856
1857 Although the original target-files.zip already contains secondary slot
1858 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1859 ones without _other suffix. Note that we cannot instead modify the names in
1860 META/ab_partitions.txt, because there are no matching partitions on device.
1861
1862 For the partitions that don't have secondary images, the ones for primary
1863 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1864 bootloader images in the inactive slot.
1865
1866 Args:
1867 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001868 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001869
1870 Returns:
1871 The filename of the target-files.zip for generating secondary payload.
1872 """
1873 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1874 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1875
Tao Baodba59ee2018-01-09 13:21:02 -08001876 with zipfile.ZipFile(input_file, 'r') as input_zip:
1877 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001878
Tao Bao0ff15de2019-03-20 11:26:06 -07001879 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001880 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001881 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1882 if info.filename == 'IMAGES/system_other.img':
1883 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1884
1885 # Primary images and friends need to be skipped explicitly.
1886 elif info.filename in ('IMAGES/system.img',
1887 'IMAGES/system.map'):
1888 pass
1889
Tao Bao15a146a2018-02-21 16:06:59 -08001890 # Skip copying the postinstall config if requested.
1891 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1892 pass
1893
Tao Bao12489802018-07-12 14:47:38 -07001894 elif info.filename.startswith(('META/', 'IMAGES/', 'RADIO/')):
Tao Baof7140c02018-01-30 17:09:24 -08001895 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
1896
Tao Baof7140c02018-01-30 17:09:24 -08001897 common.ZipClose(target_zip)
1898
1899 return target_file
1900
1901
Tao Bao15a146a2018-02-21 16:06:59 -08001902def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1903 """Returns a target-files.zip that's not containing postinstall_config.txt.
1904
1905 This allows brillo_update_payload script to skip writing all the postinstall
1906 hooks in the generated payload. The input target-files.zip file will be
1907 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1908 contain the postinstall_config.txt entry, the input file will be returned.
1909
1910 Args:
1911 input_file: The input target-files.zip filename.
1912
1913 Returns:
1914 The filename of target-files.zip that doesn't contain postinstall config.
1915 """
1916 # We should only make a copy if postinstall_config entry exists.
1917 with zipfile.ZipFile(input_file, 'r') as input_zip:
1918 if POSTINSTALL_CONFIG not in input_zip.namelist():
1919 return input_file
1920
1921 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1922 shutil.copyfile(input_file, target_file)
1923 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1924 return target_file
1925
1926
Yifan Hong50e79542018-11-08 17:44:12 -08001927def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001928 super_block_devices,
1929 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001930 """Returns a target-files.zip for retrofitting dynamic partitions.
1931
1932 This allows brillo_update_payload to generate an OTA based on the exact
1933 bits on the block devices. Postinstall is disabled.
1934
1935 Args:
1936 input_file: The input target-files.zip filename.
1937 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001938 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001939
1940 Returns:
1941 The filename of target-files.zip with *.img replaced with super_*.img for
1942 each block device in super_block_devices.
1943 """
1944 assert super_block_devices, "No super_block_devices are specified."
1945
1946 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001947 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001948
1949 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1950 shutil.copyfile(input_file, target_file)
1951
Tao Baoa3705452019-06-24 15:33:41 -07001952 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001953 namelist = input_zip.namelist()
1954
Yifan Hongb433eba2019-03-06 12:42:53 -08001955 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1956
1957 # Remove partitions from META/ab_partitions.txt that is in
1958 # dynamic_partition_list but not in super_block_devices so that
1959 # brillo_update_payload won't generate update for those logical partitions.
1960 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1961 with open(ab_partitions_file) as f:
1962 ab_partitions_lines = f.readlines()
1963 ab_partitions = [line.strip() for line in ab_partitions_lines]
1964 # Assert that all super_block_devices are in ab_partitions
1965 super_device_not_updated = [partition for partition in super_block_devices
1966 if partition not in ab_partitions]
1967 assert not super_device_not_updated, \
1968 "{} is in super_block_devices but not in {}".format(
1969 super_device_not_updated, AB_PARTITIONS)
1970 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1971 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1972 with open(new_ab_partitions, 'w') as f:
1973 for partition in ab_partitions:
1974 if (partition in dynamic_partition_list and
1975 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001976 logger.info("Dropping %s from ab_partitions.txt", partition)
1977 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001978 f.write(partition + "\n")
1979 to_delete = [AB_PARTITIONS]
1980
Yifan Hong50e79542018-11-08 17:44:12 -08001981 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001982 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001983
1984 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1985 # is a regular update on devices without dynamic partitions support.
1986 to_delete += [DYNAMIC_PARTITION_INFO]
1987
Tao Bao03fecb62018-11-28 10:59:23 -08001988 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001989 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001990 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001991
1992 common.ZipDelete(target_file, to_delete)
1993
Yifan Hong50e79542018-11-08 17:44:12 -08001994 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1995
1996 # Write super_{foo}.img as {foo}.img.
1997 for src, dst in replace.items():
1998 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001999 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08002000 unzipped_file = os.path.join(input_tmp, *src.split('/'))
2001 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
2002
Yifan Hongb433eba2019-03-06 12:42:53 -08002003 # Write new ab_partitions.txt file
2004 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
2005
Yifan Hong50e79542018-11-08 17:44:12 -08002006 common.ZipClose(target_zip)
2007
2008 return target_file
2009
2010
Tao Baof0c4aa22018-04-30 20:29:30 -07002011def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002012 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002013 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002014 if not OPTIONS.no_signing:
2015 staging_file = common.MakeTempFile(suffix='.zip')
2016 else:
2017 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002018 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002019 compression=zipfile.ZIP_DEFLATED)
2020
Tao Bao481bab82017-12-21 11:23:09 -08002021 if source_file is not None:
2022 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2023 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2024 else:
2025 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2026 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002027
Tao Bao481bab82017-12-21 11:23:09 -08002028 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002029 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002030
Yifan Hong50e79542018-11-08 17:44:12 -08002031 if OPTIONS.retrofit_dynamic_partitions:
2032 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002033 target_file, target_info.get("super_block_devices").strip().split(),
2034 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002035 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002036 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2037
Tao Bao40b18822018-01-30 18:19:04 -08002038 # Generate payload.
2039 payload = Payload()
2040
2041 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002042 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002043 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002044 else:
2045 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002046 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002047
Tao Bao40b18822018-01-30 18:19:04 -08002048 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002049
Tao Bao40b18822018-01-30 18:19:04 -08002050 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002051 payload_signer = PayloadSigner()
2052 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002053
Tao Bao40b18822018-01-30 18:19:04 -08002054 # Write the payload into output zip.
2055 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002056
Tao Baof7140c02018-01-30 17:09:24 -08002057 # Generate and include the secondary payload that installs secondary images
2058 # (e.g. system_other.img).
2059 if OPTIONS.include_secondary:
2060 # We always include a full payload for the secondary slot, even when
2061 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002062 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2063 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002064 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002065 secondary_payload.Generate(secondary_target_file,
2066 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002067 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002068 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002069
Tianjie Xucfa86222016-03-07 16:31:19 -08002070 # If dm-verity is supported for the device, copy contents of care_map
2071 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002072 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002073 if (target_info.get("verity") == "true" or
2074 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002075 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2076 "META/" + x in target_zip.namelist()]
2077
2078 # Adds care_map if either the protobuf format or the plain text one exists.
2079 if care_map_list:
2080 care_map_name = care_map_list[0]
2081 care_map_data = target_zip.read("META/" + care_map_name)
2082 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002083 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002084 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002085 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002086 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002087 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002088
Tao Baobcd1d162017-08-26 13:10:26 -07002089 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08002090 target_zip, output_zip, target_info, source_info)
Tao Bao21803d32017-04-19 10:16:09 -07002091
Tao Bao21803d32017-04-19 10:16:09 -07002092 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08002093
Tao Baofe5b69a2018-03-02 09:47:43 -08002094 # We haven't written the metadata entry yet, which will be handled in
2095 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002096 common.ZipClose(output_zip)
2097
Tao Bao85f16982018-03-08 16:28:33 -08002098 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2099 # all the info of the latter. However, system updaters and OTA servers need to
2100 # take time to switch to the new flag. We keep both of the flags for
2101 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002102 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002103 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002104 StreamingPropertyFiles(),
2105 )
2106 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002107
Tao Baoc098e9e2016-01-07 13:03:56 -08002108
Tao Baof0c4aa22018-04-30 20:29:30 -07002109def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
2110 """Generates a non-A/B OTA package."""
2111 # Sanity check the loaded info dicts first.
2112 if OPTIONS.info_dict.get("no_recovery") == "true":
2113 raise common.ExternalError(
2114 "--- target build has specified no recovery ---")
2115
2116 # Non-A/B OTAs rely on /cache partition to store temporary files.
2117 cache_size = OPTIONS.info_dict.get("cache_size")
2118 if cache_size is None:
2119 logger.warning("--- can't determine the cache partition size ---")
2120 OPTIONS.cache_size = cache_size
2121
2122 if OPTIONS.extra_script is not None:
2123 with open(OPTIONS.extra_script) as fp:
2124 OPTIONS.extra_script = fp.read()
2125
2126 if OPTIONS.extracted_input is not None:
2127 OPTIONS.input_tmp = OPTIONS.extracted_input
2128 else:
2129 logger.info("unzipping target target-files...")
2130 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
2131 OPTIONS.target_tmp = OPTIONS.input_tmp
2132
2133 # If the caller explicitly specified the device-specific extensions path via
2134 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2135 # is present in the target target_files. Otherwise, take the path of the file
2136 # from 'tool_extensions' in the info dict and look for that in the local
2137 # filesystem, relative to the current directory.
2138 if OPTIONS.device_specific is None:
2139 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2140 if os.path.exists(from_input):
2141 logger.info("(using device-specific extensions from target_files)")
2142 OPTIONS.device_specific = from_input
2143 else:
2144 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
2145
2146 if OPTIONS.device_specific is not None:
2147 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
2148
2149 # Generate a full OTA.
2150 if source_file is None:
2151 with zipfile.ZipFile(target_file) as input_zip:
2152 WriteFullOTAPackage(
2153 input_zip,
2154 output_file)
2155
2156 # Generate an incremental OTA.
2157 else:
2158 logger.info("unzipping source target-files...")
2159 OPTIONS.source_tmp = common.UnzipTemp(
2160 OPTIONS.incremental_source, UNZIP_PATTERN)
2161 with zipfile.ZipFile(target_file) as input_zip, \
2162 zipfile.ZipFile(source_file) as source_zip:
2163 WriteBlockIncrementalOTAPackage(
2164 input_zip,
2165 source_zip,
2166 output_file)
2167
2168
Doug Zongkereef39442009-04-02 12:14:19 -07002169def main(argv):
2170
2171 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002172 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002173 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002174 elif o in ("-i", "--incremental_from"):
2175 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002176 elif o == "--full_radio":
2177 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002178 elif o == "--full_bootloader":
2179 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002180 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002181 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002182 elif o == "--downgrade":
2183 OPTIONS.downgrade = True
2184 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002185 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002186 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002187 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002188 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002189 elif o == "--oem_no_mount":
2190 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002191 elif o in ("-e", "--extra_script"):
2192 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002193 elif o in ("-t", "--worker_threads"):
2194 if a.isdigit():
2195 OPTIONS.worker_threads = int(a)
2196 else:
2197 raise ValueError("Cannot parse value %r for option %r - only "
2198 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002199 elif o in ("-2", "--two_step"):
2200 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002201 elif o == "--include_secondary":
2202 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002203 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002204 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002205 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002206 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002207 elif o == "--block":
2208 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002209 elif o in ("-b", "--binary"):
2210 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002211 elif o == "--stash_threshold":
2212 try:
2213 OPTIONS.stash_threshold = float(a)
2214 except ValueError:
2215 raise ValueError("Cannot parse value %r for option %r - expecting "
2216 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002217 elif o == "--log_diff":
2218 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002219 elif o == "--payload_signer":
2220 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002221 elif o == "--payload_signer_args":
2222 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002223 elif o == "--payload_signer_key_size":
2224 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002225 elif o == "--extracted_input_target_files":
2226 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002227 elif o == "--skip_postinstall":
2228 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002229 elif o == "--retrofit_dynamic_partitions":
2230 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002231 elif o == "--skip_compatibility_check":
2232 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002233 elif o == "--output_metadata_path":
2234 OPTIONS.output_metadata_path = a
Doug Zongkereef39442009-04-02 12:14:19 -07002235 else:
2236 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002237 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002238
2239 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002240 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002241 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002242 "package_key=",
2243 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002244 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002245 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002246 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002247 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002248 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002249 "extra_script=",
2250 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002251 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002252 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002253 "no_signing",
2254 "block",
2255 "binary=",
2256 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002257 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002258 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002259 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002260 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002261 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002262 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002263 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002264 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002265 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002266 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002267 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002268 "output_metadata_path=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002269 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002270
2271 if len(args) != 2:
2272 common.Usage(__doc__)
2273 sys.exit(1)
2274
Tao Bao32fcdab2018-10-12 10:30:39 -07002275 common.InitLogging()
2276
Tao Bao5d182562016-02-23 11:38:39 -08002277 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002278 # We should only allow downgrading incrementals (as opposed to full).
2279 # Otherwise the device may go back from arbitrary build with this full
2280 # OTA package.
2281 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002282 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002283
Tao Bao2db13852018-01-08 22:28:57 -08002284 # Load the build info dicts from the zip directly or the extracted input
2285 # directory. We don't need to unzip the entire target-files zips, because they
2286 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2287 # When loading the info dicts, we don't need to provide the second parameter
2288 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2289 # some properties with their actual paths, such as 'selinux_fc',
2290 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002291 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002292 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002293 else:
Tao Bao2db13852018-01-08 22:28:57 -08002294 with zipfile.ZipFile(args[0], 'r') as input_zip:
2295 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002296
Tao Bao32fcdab2018-10-12 10:30:39 -07002297 logger.info("--- target info ---")
2298 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002299
2300 # Load the source build dict if applicable.
2301 if OPTIONS.incremental_source is not None:
2302 OPTIONS.target_info_dict = OPTIONS.info_dict
2303 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2304 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2305
Tao Bao32fcdab2018-10-12 10:30:39 -07002306 logger.info("--- source info ---")
2307 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002308
2309 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002310 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2311
Yifan Hong50e79542018-11-08 17:44:12 -08002312 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002313 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002314 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002315 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2316 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002317 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2318 raise common.ExternalError(
2319 "Expect to generate incremental OTA for retrofitting dynamic "
2320 "partitions, but dynamic_partition_retrofit is not set in target "
2321 "build.")
2322 logger.info("Implicitly generating retrofit incremental OTA.")
2323 OPTIONS.retrofit_dynamic_partitions = True
2324
2325 # Skip postinstall for retrofitting dynamic partitions.
2326 if OPTIONS.retrofit_dynamic_partitions:
2327 OPTIONS.skip_postinstall = True
2328
Tao Baoc098e9e2016-01-07 13:03:56 -08002329 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2330
Christian Oderf63e2cd2017-05-01 22:30:15 +02002331 # Use the default key to sign the package if not specified with package_key.
2332 # package_keys are needed on ab_updates, so always define them if an
2333 # ab_update is getting created.
2334 if not OPTIONS.no_signing or ab_update:
2335 if OPTIONS.package_key is None:
2336 OPTIONS.package_key = OPTIONS.info_dict.get(
2337 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002338 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002339 # Get signing keys
2340 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2341
Tao Baoc098e9e2016-01-07 13:03:56 -08002342 if ab_update:
Tao Baof0c4aa22018-04-30 20:29:30 -07002343 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002344 target_file=args[0],
2345 output_file=args[1],
2346 source_file=OPTIONS.incremental_source)
2347
Dan Willemsencea5cd22017-03-21 14:44:27 -07002348 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002349 GenerateNonAbOtaPackage(
2350 target_file=args[0],
2351 output_file=args[1],
2352 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002353
Tao Baof0c4aa22018-04-30 20:29:30 -07002354 # Post OTA generation works.
2355 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2356 logger.info("Generating diff logs...")
2357 logger.info("Unzipping target-files for diffing...")
2358 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2359 source_dir = common.UnzipTemp(
2360 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002361
Tao Baof0c4aa22018-04-30 20:29:30 -07002362 with open(OPTIONS.log_diff, 'w') as out_file:
2363 import target_files_diff
2364 target_files_diff.recursiveDiff(
2365 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002366
Tao Bao32fcdab2018-10-12 10:30:39 -07002367 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002368
2369
2370if __name__ == '__main__':
2371 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002372 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002373 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002374 except common.ExternalError:
2375 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002376 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002377 finally:
2378 common.Cleanup()