blob: 82ea53970f96afe4c6e02cb458d48a029eeb2637 [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
Tao Bao30df8b42018-04-23 15:32:53 -070018Given a target-files zipfile, produces an OTA package that installs that build.
19An incremental OTA is produced if -i is given, otherwise a full OTA is produced.
Doug Zongkereef39442009-04-02 12:14:19 -070020
Tao Bao30df8b42018-04-23 15:32:53 -070021Usage: ota_from_target_files [options] input_target_files output_ota_package
Doug Zongkereef39442009-04-02 12:14:19 -070022
Tao Bao30df8b42018-04-23 15:32:53 -070023Common options that apply to both of non-A/B and A/B OTAs
24
25 --downgrade
26 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070027 to an older one (e.g. downgrading from P preview back to O MR1).
28 "ota-downgrade=yes" will be set in the package metadata file. A data wipe
29 will always be enforced when using this flag, so "ota-wipe=yes" will also
30 be included in the metadata file. The update-binary in the source build
31 will be used in the OTA package, unless --binary flag is specified. Please
32 also check the comment for --override_timestamp below.
Tao Bao30df8b42018-04-23 15:32:53 -070033
34 -i (--incremental_from) <file>
35 Generate an incremental OTA using the given target-files zip as the
36 starting build.
37
38 -k (--package_key) <key>
39 Key to use to sign the package (default is the value of
40 default_system_dev_certificate from the input target-files's
Tao Bao59cf0c52019-06-25 10:04:24 -070041 META/misc_info.txt, or "build/make/target/product/security/testkey" if
42 that value is not specified).
Doug Zongkerafb32ea2011-09-22 10:28:04 -070043
44 For incremental OTAs, the default value is based on the source
45 target-file, not the target build.
Doug Zongkereef39442009-04-02 12:14:19 -070046
Tao Bao30df8b42018-04-23 15:32:53 -070047 --override_timestamp
48 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070049 to an older one (based on timestamp comparison), by setting the downgrade
50 flag in the package metadata. This differs from --downgrade flag, as we
51 don't enforce a data wipe with this flag. Because we know for sure this is
52 NOT an actual downgrade case, but two builds happen to be cut in a reverse
53 order (e.g. from two branches). A legit use case is that we cut a new
54 build C (after having A and B), but want to enfore an update path of A ->
55 C -> B. Specifying --downgrade may not help since that would enforce a
56 data wipe for C -> B update.
57
58 We used to set a fake timestamp in the package metadata for this flow. But
59 now we consolidate the two cases (i.e. an actual downgrade, or a downgrade
60 based on timestamp) with the same "ota-downgrade=yes" flag, with the
61 difference being whether "ota-wipe=yes" is set.
Doug Zongkereef39442009-04-02 12:14:19 -070062
Tao Bao30df8b42018-04-23 15:32:53 -070063 --wipe_user_data
64 Generate an OTA package that will wipe the user data partition when
65 installed.
66
Yifan Hong50e79542018-11-08 17:44:12 -080067 --retrofit_dynamic_partitions
68 Generates an OTA package that updates a device to support dynamic
69 partitions (default False). This flag is implied when generating
70 an incremental OTA where the base build does not support dynamic
71 partitions but the target build does. For A/B, when this flag is set,
72 --skip_postinstall is implied.
73
xunchangabfa2652019-02-19 16:27:10 -080074 --skip_compatibility_check
Yifan Hong9276cf02019-08-21 16:37:04 -070075 Skip checking compatibility of the input target files package.
xunchangabfa2652019-02-19 16:27:10 -080076
xunchang1cfe2512019-02-19 14:14:48 -080077 --output_metadata_path
78 Write a copy of the metadata to a separate file. Therefore, users can
79 read the post build fingerprint without extracting the OTA package.
80
Tao Bao30df8b42018-04-23 15:32:53 -070081Non-A/B OTA specific options
82
83 -b (--binary) <file>
84 Use the given binary as the update-binary in the output package, instead
85 of the binary in the build's target_files. Use for development only.
86
87 --block
88 Generate a block-based OTA for non-A/B device. We have deprecated the
89 support for file-based OTA since O. Block-based OTA will be used by
90 default for all non-A/B devices. Keeping this flag here to not break
91 existing callers.
92
93 -e (--extra_script) <file>
94 Insert the contents of file at the end of the update script.
Tao Bao43078aa2015-04-21 14:32:35 -070095
leozwangaa6c1a12015-08-14 10:57:58 -070096 --full_bootloader
97 Similar to --full_radio. When generating an incremental OTA, always
98 include a full copy of bootloader image.
99
Tao Bao30df8b42018-04-23 15:32:53 -0700100 --full_radio
101 When generating an incremental OTA, always include a full copy of radio
102 image. This option is only meaningful when -i is specified, because a full
103 radio is always included in a full OTA if applicable.
Michael Runge63f01de2014-10-28 19:24:19 -0700104
Tao Bao30df8b42018-04-23 15:32:53 -0700105 --log_diff <file>
106 Generate a log file that shows the differences in the source and target
107 builds for an incremental package. This option is only meaningful when -i
108 is specified.
109
110 -o (--oem_settings) <main_file[,additional_files...]>
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800111 Comma seperated list of files used to specify the expected OEM-specific
Tao Bao481bab82017-12-21 11:23:09 -0800112 properties on the OEM partition of the intended device. Multiple expected
113 values can be used by providing multiple files. Only the first dict will
114 be used to compute fingerprint, while the rest will be used to assert
115 OEM-specific properties.
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800116
Tao Bao8608cde2016-02-25 19:49:55 -0800117 --oem_no_mount
Tao Bao30df8b42018-04-23 15:32:53 -0700118 For devices with OEM-specific properties but without an OEM partition, do
119 not mount the OEM partition in the updater-script. This should be very
120 rarely used, since it's expected to have a dedicated OEM partition for
121 OEM-specific properties. Only meaningful when -o is specified.
Tao Bao8608cde2016-02-25 19:49:55 -0800122
Tao Bao30df8b42018-04-23 15:32:53 -0700123 --stash_threshold <float>
124 Specify the threshold that will be used to compute the maximum allowed
125 stash size (defaults to 0.8).
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700126
Tao Bao30df8b42018-04-23 15:32:53 -0700127 -t (--worker_threads) <int>
128 Specify the number of worker-threads that will be used when generating
129 patches for incremental updates (defaults to 3).
Tao Bao3e6161a2017-02-28 11:48:48 -0800130
Tao Bao30df8b42018-04-23 15:32:53 -0700131 --verify
132 Verify the checksums of the updated system and vendor (if any) partitions.
133 Non-A/B incremental OTAs only.
Doug Zongker1c390a22009-05-14 19:06:36 -0700134
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800135 -2 (--two_step)
Tao Bao30df8b42018-04-23 15:32:53 -0700136 Generate a 'two-step' OTA package, where recovery is updated first, so
137 that any changes made to the system partition are done using the new
138 recovery (new kernel, etc.).
139
140A/B OTA specific options
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800141
Tianjie Xu1b079832019-08-28 12:19:23 -0700142 --disable_fec_computation
143 Disable the on device FEC data computation for incremental updates.
144
Tao Baof7140c02018-01-30 17:09:24 -0800145 --include_secondary
146 Additionally include the payload for secondary slot images (default:
147 False). Only meaningful when generating A/B OTAs.
148
149 By default, an A/B OTA package doesn't contain the images for the
150 secondary slot (e.g. system_other.img). Specifying this flag allows
151 generating a separate payload that will install secondary slot images.
152
153 Such a package needs to be applied in a two-stage manner, with a reboot
154 in-between. During the first stage, the updater applies the primary
155 payload only. Upon finishing, it reboots the device into the newly updated
156 slot. It then continues to install the secondary payload to the inactive
157 slot, but without switching the active slot at the end (needs the matching
158 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag).
159
160 Due to the special install procedure, the secondary payload will be always
161 generated as a full payload.
162
Tao Baodea0f8b2016-06-20 17:55:06 -0700163 --payload_signer <signer>
164 Specify the signer when signing the payload and metadata for A/B OTAs.
165 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
166 with the package private key. If the private key cannot be accessed
167 directly, a payload signer that knows how to do that should be specified.
168 The signer will be supplied with "-inkey <path_to_key>",
169 "-in <input_file>" and "-out <output_file>" parameters.
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700170
171 --payload_signer_args <args>
172 Specify the arguments needed for payload signer.
Tao Bao15a146a2018-02-21 16:06:59 -0800173
xunchang376cc7c2019-04-08 23:04:58 -0700174 --payload_signer_key_size <key_size>
175 Specify the key size in bytes of the payload signer.
176
Tao Bao15a146a2018-02-21 16:06:59 -0800177 --skip_postinstall
178 Skip the postinstall hooks when generating an A/B OTA package (default:
179 False). Note that this discards ALL the hooks, including non-optional
180 ones. Should only be used if caller knows it's safe to do so (e.g. all the
181 postinstall work is to dexopt apps and a data wipe will happen immediately
182 after). Only meaningful when generating A/B OTAs.
Doug Zongkereef39442009-04-02 12:14:19 -0700183"""
184
Tao Bao89fbb0f2017-01-10 10:47:58 -0800185from __future__ import print_function
186
Tianjie Xuf67dd802019-05-20 17:50:36 -0700187import collections
Tao Bao32fcdab2018-10-12 10:30:39 -0700188import logging
Doug Zongkerfc44a512014-08-26 13:10:25 -0700189import multiprocessing
Tao Bao2dd1c482017-02-03 16:49:39 -0800190import os.path
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700191import shlex
Tao Bao15a146a2018-02-21 16:06:59 -0800192import shutil
Tao Bao85f16982018-03-08 16:28:33 -0800193import struct
Tao Bao481bab82017-12-21 11:23:09 -0800194import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700195import zipfile
196
Yifan Hong9276cf02019-08-21 16:37:04 -0700197import check_target_files_vintf
Doug Zongkereef39442009-04-02 12:14:19 -0700198import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700199import edify_generator
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700200import verity_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700201
Tao Bao481bab82017-12-21 11:23:09 -0800202if sys.hexversion < 0x02070000:
203 print("Python 2.7 or newer is required.", file=sys.stderr)
204 sys.exit(1)
205
Tao Bao32fcdab2018-10-12 10:30:39 -0700206logger = logging.getLogger(__name__)
Tao Bao481bab82017-12-21 11:23:09 -0800207
Doug Zongkereef39442009-04-02 12:14:19 -0700208OPTIONS = common.OPTIONS
Doug Zongkerafb32ea2011-09-22 10:28:04 -0700209OPTIONS.package_key = None
Doug Zongkereef39442009-04-02 12:14:19 -0700210OPTIONS.incremental_source = None
Michael Runge63f01de2014-10-28 19:24:19 -0700211OPTIONS.verify = False
Doug Zongkereef39442009-04-02 12:14:19 -0700212OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700213OPTIONS.wipe_user_data = False
Tao Bao5d182562016-02-23 11:38:39 -0800214OPTIONS.downgrade = False
Doug Zongker1c390a22009-05-14 19:06:36 -0700215OPTIONS.extra_script = None
Doug Zongkerfc44a512014-08-26 13:10:25 -0700216OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
217if OPTIONS.worker_threads == 0:
218 OPTIONS.worker_threads = 1
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800219OPTIONS.two_step = False
Tao Baof7140c02018-01-30 17:09:24 -0800220OPTIONS.include_secondary = False
Takeshi Kanemotoe153b342013-11-14 17:20:50 +0900221OPTIONS.no_signing = False
Tao Bao457cbf62017-03-06 09:56:01 -0800222OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -0800223OPTIONS.updater_binary = None
Michael Runge6e836112014-04-15 17:40:21 -0700224OPTIONS.oem_source = None
Tao Bao8608cde2016-02-25 19:49:55 -0800225OPTIONS.oem_no_mount = False
Tao Bao43078aa2015-04-21 14:32:35 -0700226OPTIONS.full_radio = False
leozwangaa6c1a12015-08-14 10:57:58 -0700227OPTIONS.full_bootloader = False
Tao Baod47d8e12015-05-21 14:09:49 -0700228# Stash size cannot exceed cache_size * threshold.
229OPTIONS.cache_size = None
230OPTIONS.stash_threshold = 0.8
Tao Baod62c6032015-11-30 09:40:20 -0800231OPTIONS.log_diff = None
Tao Baodea0f8b2016-06-20 17:55:06 -0700232OPTIONS.payload_signer = None
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700233OPTIONS.payload_signer_args = []
xunchang376cc7c2019-04-08 23:04:58 -0700234OPTIONS.payload_signer_key_size = None
Tao Bao5f8ff932017-03-21 22:35:00 -0700235OPTIONS.extracted_input = None
Christian Oderf63e2cd2017-05-01 22:30:15 +0200236OPTIONS.key_passwords = []
Tao Bao15a146a2018-02-21 16:06:59 -0800237OPTIONS.skip_postinstall = False
Yifan Hong50e79542018-11-08 17:44:12 -0800238OPTIONS.retrofit_dynamic_partitions = False
xunchangabfa2652019-02-19 16:27:10 -0800239OPTIONS.skip_compatibility_check = False
xunchang1cfe2512019-02-19 14:14:48 -0800240OPTIONS.output_metadata_path = None
Tianjie Xu1b079832019-08-28 12:19:23 -0700241OPTIONS.disable_fec_computation = False
Tao Bao15a146a2018-02-21 16:06:59 -0800242
Tao Bao8dcf7382015-05-21 14:09:49 -0700243
Tao Bao2dd1c482017-02-03 16:49:39 -0800244METADATA_NAME = 'META-INF/com/android/metadata'
Tao Bao15a146a2018-02-21 16:06:59 -0800245POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
Yifan Hong50e79542018-11-08 17:44:12 -0800246DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
Yifan Hongb433eba2019-03-06 12:42:53 -0800247AB_PARTITIONS = 'META/ab_partitions.txt'
Tao Bao04808502019-07-25 23:11:41 -0700248UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Tao Baof0c4aa22018-04-30 20:29:30 -0700249# Files to be unzipped for target diffing purpose.
250TARGET_DIFFING_UNZIP_PATTERN = ['BOOT', 'RECOVERY', 'SYSTEM/*', 'VENDOR/*',
251 'PRODUCT/*', 'SYSTEM_EXT/*', 'ODM/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800252RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tianjie Xu1c808002019-09-11 00:29:26 -0700253SECONDARY_IMAGES_SKIP_PARTITIONS = ['odm', 'product', 'system_ext', 'vendor']
Tao Bao6b0b2f92017-03-05 11:38:11 -0800254
Tao Bao2dd1c482017-02-03 16:49:39 -0800255
Tao Bao481bab82017-12-21 11:23:09 -0800256class BuildInfo(object):
257 """A class that holds the information for a given build.
258
259 This class wraps up the property querying for a given source or target build.
260 It abstracts away the logic of handling OEM-specific properties, and caches
261 the commonly used properties such as fingerprint.
262
263 There are two types of info dicts: a) build-time info dict, which is generated
264 at build time (i.e. included in a target_files zip); b) OEM info dict that is
265 specified at package generation time (via command line argument
266 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
267 having "oem_fingerprint_properties" in build-time info dict), all the queries
268 would be answered based on build-time info dict only. Otherwise if using
269 OEM-specific properties, some of them will be calculated from two info dicts.
270
271 Users can query properties similarly as using a dict() (e.g. info['fstab']),
272 or to query build properties via GetBuildProp() or GetVendorBuildProp().
273
274 Attributes:
275 info_dict: The build-time info dict.
276 is_ab: Whether it's a build that uses A/B OTA.
277 oem_dicts: A list of OEM dicts.
278 oem_props: A list of OEM properties that should be read from OEM dicts; None
279 if the build doesn't use any OEM-specific property.
280 fingerprint: The fingerprint of the build, which would be calculated based
281 on OEM properties if applicable.
282 device: The device name, which could come from OEM dicts if applicable.
283 """
284
Steven Laver9e73e822019-01-29 20:20:08 -0800285 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
286 "ro.product.manufacturer", "ro.product.model",
287 "ro.product.name"]
Justin Yun6151e3f2019-06-25 15:58:13 +0900288 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
289 "system_ext", "system"]
Steven Laver9e73e822019-01-29 20:20:08 -0800290
Tao Bao481bab82017-12-21 11:23:09 -0800291 def __init__(self, info_dict, oem_dicts):
292 """Initializes a BuildInfo instance with the given dicts.
293
Tao Bao667c7532018-07-06 10:13:59 -0700294 Note that it only wraps up the given dicts, without making copies.
295
Tao Bao481bab82017-12-21 11:23:09 -0800296 Arguments:
297 info_dict: The build-time info dict.
298 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
299 that it always uses the first dict to calculate the fingerprint or the
300 device name. The rest would be used for asserting OEM properties only
Tao Bao667c7532018-07-06 10:13:59 -0700301 (e.g. one package can be installed on one of these devices).
Tao Baoc4011cd72019-09-17 00:14:44 -0700302
303 Raises:
304 ValueError: On invalid inputs.
Tao Bao481bab82017-12-21 11:23:09 -0800305 """
306 self.info_dict = info_dict
307 self.oem_dicts = oem_dicts
308
309 self._is_ab = info_dict.get("ab_update") == "true"
310 self._oem_props = info_dict.get("oem_fingerprint_properties")
311
312 if self._oem_props:
313 assert oem_dicts, "OEM source required for this build"
314
315 # These two should be computed only after setting self._oem_props.
316 self._device = self.GetOemProperty("ro.product.device")
317 self._fingerprint = self.CalculateFingerprint()
318
Tao Baoc4011cd72019-09-17 00:14:44 -0700319 # Sanity check the build fingerprint.
320 if (' ' in self._fingerprint or
321 any(ord(ch) > 127 for ch in self._fingerprint)):
322 raise ValueError(
323 'Invalid build fingerprint: "{}". See the requirement in Android CDD '
324 '3.2.2. Build Parameters.'.format(self._fingerprint))
325
Tao Bao481bab82017-12-21 11:23:09 -0800326 @property
327 def is_ab(self):
328 return self._is_ab
329
330 @property
331 def device(self):
332 return self._device
333
334 @property
335 def fingerprint(self):
336 return self._fingerprint
337
338 @property
Tao Baoea6cbd02018-09-05 13:06:37 -0700339 def vendor_fingerprint(self):
Yifan Hong51d37562019-04-23 17:06:46 -0700340 return self._fingerprint_of("vendor")
341
342 @property
343 def product_fingerprint(self):
344 return self._fingerprint_of("product")
345
346 @property
347 def odm_fingerprint(self):
348 return self._fingerprint_of("odm")
349
350 def _fingerprint_of(self, partition):
351 if partition + ".build.prop" not in self.info_dict:
Tao Baoea6cbd02018-09-05 13:06:37 -0700352 return None
Yifan Hong51d37562019-04-23 17:06:46 -0700353 build_prop = self.info_dict[partition + ".build.prop"]
354 if "ro." + partition + ".build.fingerprint" in build_prop:
355 return build_prop["ro." + partition + ".build.fingerprint"]
356 if "ro." + partition + ".build.thumbprint" in build_prop:
357 return build_prop["ro." + partition + ".build.thumbprint"]
Tao Baoea6cbd02018-09-05 13:06:37 -0700358 return None
359
360 @property
Tao Bao481bab82017-12-21 11:23:09 -0800361 def oem_props(self):
362 return self._oem_props
363
364 def __getitem__(self, key):
365 return self.info_dict[key]
366
Tao Bao667c7532018-07-06 10:13:59 -0700367 def __setitem__(self, key, value):
368 self.info_dict[key] = value
369
Tao Bao481bab82017-12-21 11:23:09 -0800370 def get(self, key, default=None):
371 return self.info_dict.get(key, default)
372
Tao Bao667c7532018-07-06 10:13:59 -0700373 def items(self):
374 return self.info_dict.items()
375
Tao Bao481bab82017-12-21 11:23:09 -0800376 def GetBuildProp(self, prop):
377 """Returns the inquired build property."""
Steven Laver9e73e822019-01-29 20:20:08 -0800378 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
379 return self._ResolveRoProductBuildProp(prop)
380
Tao Bao481bab82017-12-21 11:23:09 -0800381 try:
382 return self.info_dict.get("build.prop", {})[prop]
383 except KeyError:
384 raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
385
Steven Laver9e73e822019-01-29 20:20:08 -0800386 def _ResolveRoProductBuildProp(self, prop):
387 """Resolves the inquired ro.product.* build property"""
388 prop_val = self.info_dict.get("build.prop", {}).get(prop)
389 if prop_val:
390 return prop_val
391
392 source_order_val = self.info_dict.get("build.prop", {}).get(
Tao Bao59cf0c52019-06-25 10:04:24 -0700393 "ro.product.property_source_order")
Steven Laver9e73e822019-01-29 20:20:08 -0800394 if source_order_val:
395 source_order = source_order_val.split(",")
396 else:
397 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
398
399 # Check that all sources in ro.product.property_source_order are valid
400 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
401 for x in source_order]):
402 raise common.ExternalError(
Tao Bao59cf0c52019-06-25 10:04:24 -0700403 "Invalid ro.product.property_source_order '{}'".format(source_order))
Steven Laver9e73e822019-01-29 20:20:08 -0800404
405 for source in source_order:
Tao Bao59cf0c52019-06-25 10:04:24 -0700406 source_prop = prop.replace(
407 "ro.product", "ro.product.{}".format(source), 1)
408 prop_val = self.info_dict.get(
409 "{}.build.prop".format(source), {}).get(source_prop)
Steven Laver9e73e822019-01-29 20:20:08 -0800410 if prop_val:
411 return prop_val
412
413 raise common.ExternalError("couldn't resolve {}".format(prop))
414
Tao Bao481bab82017-12-21 11:23:09 -0800415 def GetVendorBuildProp(self, prop):
416 """Returns the inquired vendor build property."""
417 try:
418 return self.info_dict.get("vendor.build.prop", {})[prop]
419 except KeyError:
420 raise common.ExternalError(
421 "couldn't find %s in vendor.build.prop" % (prop,))
422
423 def GetOemProperty(self, key):
424 if self.oem_props is not None and key in self.oem_props:
425 return self.oem_dicts[0][key]
426 return self.GetBuildProp(key)
427
428 def CalculateFingerprint(self):
429 if self.oem_props is None:
Steven Laver9e73e822019-01-29 20:20:08 -0800430 try:
431 return self.GetBuildProp("ro.build.fingerprint")
432 except common.ExternalError:
433 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
Tao Bao59cf0c52019-06-25 10:04:24 -0700434 self.GetBuildProp("ro.product.brand"),
435 self.GetBuildProp("ro.product.name"),
436 self.GetBuildProp("ro.product.device"),
437 self.GetBuildProp("ro.build.version.release"),
438 self.GetBuildProp("ro.build.id"),
439 self.GetBuildProp("ro.build.version.incremental"),
440 self.GetBuildProp("ro.build.type"),
441 self.GetBuildProp("ro.build.tags"))
Tao Bao481bab82017-12-21 11:23:09 -0800442 return "%s/%s/%s:%s" % (
443 self.GetOemProperty("ro.product.brand"),
444 self.GetOemProperty("ro.product.name"),
445 self.GetOemProperty("ro.product.device"),
446 self.GetBuildProp("ro.build.thumbprint"))
447
448 def WriteMountOemScript(self, script):
449 assert self.oem_props is not None
450 recovery_mount_options = self.info_dict.get("recovery_mount_options")
451 script.Mount("/oem", recovery_mount_options)
452
453 def WriteDeviceAssertions(self, script, oem_no_mount):
454 # Read the property directly if not using OEM properties.
455 if not self.oem_props:
456 script.AssertDevice(self.device)
457 return
458
459 # Otherwise assert OEM properties.
460 if not self.oem_dicts:
461 raise common.ExternalError(
462 "No OEM file provided to answer expected assertions")
463
464 for prop in self.oem_props.split():
465 values = []
466 for oem_dict in self.oem_dicts:
467 if prop in oem_dict:
468 values.append(oem_dict[prop])
469 if not values:
470 raise common.ExternalError(
471 "The OEM file is missing the property %s" % (prop,))
472 script.AssertOemProperty(prop, values, oem_no_mount)
473
474
Tao Baofabe0832018-01-17 15:52:28 -0800475class PayloadSigner(object):
476 """A class that wraps the payload signing works.
477
478 When generating a Payload, hashes of the payload and metadata files will be
479 signed with the device key, either by calling an external payload signer or
480 by calling openssl with the package key. This class provides a unified
481 interface, so that callers can just call PayloadSigner.Sign().
482
483 If an external payload signer has been specified (OPTIONS.payload_signer), it
484 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
485 that the signing key should be provided as part of the payload_signer_args.
486 Otherwise without an external signer, it uses the package key
487 (OPTIONS.package_key) and calls openssl for the signing works.
488 """
489
490 def __init__(self):
491 if OPTIONS.payload_signer is None:
492 # Prepare the payload signing key.
493 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
494 pw = OPTIONS.key_passwords[OPTIONS.package_key]
495
496 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
497 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
498 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
499 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700500 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800501
502 self.signer = "openssl"
503 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
504 "-pkeyopt", "digest:sha256"]
xunchang376cc7c2019-04-08 23:04:58 -0700505 self.key_size = self._GetKeySizeInBytes(signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800506 else:
507 self.signer = OPTIONS.payload_signer
508 self.signer_args = OPTIONS.payload_signer_args
xunchang376cc7c2019-04-08 23:04:58 -0700509 if OPTIONS.payload_signer_key_size:
510 self.key_size = int(OPTIONS.payload_signer_key_size)
511 assert self.key_size == 256 or self.key_size == 512, \
512 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size)
513 else:
514 self.key_size = 256
515
516 @staticmethod
517 def _GetKeySizeInBytes(signing_key):
518 modulus_file = common.MakeTempFile(prefix="modulus-")
519 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus",
520 "-noout", "-out", modulus_file]
521 common.RunAndCheckOutput(cmd, verbose=False)
522
523 with open(modulus_file) as f:
524 modulus_string = f.read()
525 # The modulus string has the format "Modulus=$data", where $data is the
526 # concatenation of hex dump of the modulus.
527 MODULUS_PREFIX = "Modulus="
528 assert modulus_string.startswith(MODULUS_PREFIX)
529 modulus_string = modulus_string[len(MODULUS_PREFIX):]
Tao Bao59cf0c52019-06-25 10:04:24 -0700530 key_size = len(modulus_string) // 2
xunchang376cc7c2019-04-08 23:04:58 -0700531 assert key_size == 256 or key_size == 512, \
532 "Unsupported key size {}".format(key_size)
533 return key_size
Tao Baofabe0832018-01-17 15:52:28 -0800534
535 def Sign(self, in_file):
536 """Signs the given input file. Returns the output filename."""
537 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
538 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700539 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800540 return out_file
541
542
Tao Bao40b18822018-01-30 18:19:04 -0800543class Payload(object):
544 """Manages the creation and the signing of an A/B OTA Payload."""
545
546 PAYLOAD_BIN = 'payload.bin'
547 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800548 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
549 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800550
Tao Bao667ff572018-02-10 00:02:40 -0800551 def __init__(self, secondary=False):
552 """Initializes a Payload instance.
553
554 Args:
555 secondary: Whether it's generating a secondary payload (default: False).
556 """
Tao Bao40b18822018-01-30 18:19:04 -0800557 self.payload_file = None
558 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800559 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800560
Tao Baof0c4aa22018-04-30 20:29:30 -0700561 def _Run(self, cmd): # pylint: disable=no-self-use
Tao Bao718faed2019-08-02 13:24:19 -0700562 # Don't pipe (buffer) the output if verbose is set. Let
563 # brillo_update_payload write to stdout/stderr directly, so its progress can
564 # be monitored.
565 if OPTIONS.verbose:
566 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
567 else:
568 common.RunAndCheckOutput(cmd)
569
Tao Bao40b18822018-01-30 18:19:04 -0800570 def Generate(self, target_file, source_file=None, additional_args=None):
571 """Generates a payload from the given target-files zip(s).
572
573 Args:
574 target_file: The filename of the target build target-files zip.
575 source_file: The filename of the source build target-files zip; or None if
576 generating a full OTA.
577 additional_args: A list of additional args that should be passed to
578 brillo_update_payload script; or None.
579 """
580 if additional_args is None:
581 additional_args = []
582
583 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
584 cmd = ["brillo_update_payload", "generate",
585 "--payload", payload_file,
586 "--target_image", target_file]
587 if source_file is not None:
588 cmd.extend(["--source_image", source_file])
Tianjie Xu1b079832019-08-28 12:19:23 -0700589 if OPTIONS.disable_fec_computation:
590 cmd.extend(["--disable_fec_computation", "true"])
Tao Bao40b18822018-01-30 18:19:04 -0800591 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700592 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800593
594 self.payload_file = payload_file
595 self.payload_properties = None
596
597 def Sign(self, payload_signer):
598 """Generates and signs the hashes of the payload and metadata.
599
600 Args:
601 payload_signer: A PayloadSigner() instance that serves the signing work.
602
603 Raises:
604 AssertionError: On any failure when calling brillo_update_payload script.
605 """
606 assert isinstance(payload_signer, PayloadSigner)
607
608 # 1. Generate hashes of the payload and metadata files.
609 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
610 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
611 cmd = ["brillo_update_payload", "hash",
612 "--unsigned_payload", self.payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700613 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800614 "--metadata_hash_file", metadata_sig_file,
615 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700616 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800617
618 # 2. Sign the hashes.
619 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
620 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
621
622 # 3. Insert the signatures back into the payload file.
623 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
624 suffix=".bin")
625 cmd = ["brillo_update_payload", "sign",
626 "--unsigned_payload", self.payload_file,
627 "--payload", signed_payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700628 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800629 "--metadata_signature_file", signed_metadata_sig_file,
630 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700631 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800632
633 # 4. Dump the signed payload properties.
634 properties_file = common.MakeTempFile(prefix="payload-properties-",
635 suffix=".txt")
636 cmd = ["brillo_update_payload", "properties",
637 "--payload", signed_payload_file,
638 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700639 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800640
Tao Bao667ff572018-02-10 00:02:40 -0800641 if self.secondary:
642 with open(properties_file, "a") as f:
643 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
644
Tao Bao40b18822018-01-30 18:19:04 -0800645 if OPTIONS.wipe_user_data:
646 with open(properties_file, "a") as f:
647 f.write("POWERWASH=1\n")
648
649 self.payload_file = signed_payload_file
650 self.payload_properties = properties_file
651
Tao Bao667ff572018-02-10 00:02:40 -0800652 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800653 """Writes the payload to the given zip.
654
655 Args:
656 output_zip: The output ZipFile instance.
657 """
658 assert self.payload_file is not None
659 assert self.payload_properties is not None
660
Tao Bao667ff572018-02-10 00:02:40 -0800661 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800662 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
663 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
664 else:
665 payload_arcname = Payload.PAYLOAD_BIN
666 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
667
Tao Bao40b18822018-01-30 18:19:04 -0800668 # Add the signed payload file and properties into the zip. In order to
669 # support streaming, we pack them as ZIP_STORED. So these entries can be
670 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800671 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800672 compress_type=zipfile.ZIP_STORED)
673 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800674 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800675 compress_type=zipfile.ZIP_STORED)
676
677
Doug Zongkereef39442009-04-02 12:14:19 -0700678def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200679 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700680
Doug Zongker951495f2009-08-14 12:44:19 -0700681 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
682 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700683
684
Tao Bao481bab82017-12-21 11:23:09 -0800685def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800686 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800687 if not oem_source:
688 return None
689
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800690 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800691 for oem_file in oem_source:
692 with open(oem_file) as fp:
693 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800694 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700695
Doug Zongkereef39442009-04-02 12:14:19 -0700696
Tao Baod42e97e2016-11-30 12:11:57 -0800697def _WriteRecoveryImageToBoot(script, output_zip):
698 """Find and write recovery image to /boot in two-step OTA.
699
700 In two-step OTAs, we write recovery image to /boot as the first step so that
701 we can reboot to there and install a new recovery image to /recovery.
702 A special "recovery-two-step.img" will be preferred, which encodes the correct
703 path of "/boot". Otherwise the device may show "device is corrupt" message
704 when booting into /boot.
705
706 Fall back to using the regular recovery.img if the two-step recovery image
707 doesn't exist. Note that rebuilding the special image at this point may be
708 infeasible, because we don't have the desired boot signer and keys when
709 calling ota_from_target_files.py.
710 """
711
712 recovery_two_step_img_name = "recovery-two-step.img"
713 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700714 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800715 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700716 common.ZipWrite(
717 output_zip,
718 recovery_two_step_img_path,
719 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700720 logger.info(
721 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800722 script.WriteRawImage("/boot", recovery_two_step_img_name)
723 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700724 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800725 # The "recovery.img" entry has been written into package earlier.
726 script.WriteRawImage("/boot", "recovery.img")
727
728
Doug Zongkerc9253822014-02-04 12:17:58 -0800729def HasRecoveryPatch(target_files_zip):
Tao Baof2cffbd2015-07-22 12:33:18 -0700730 namelist = [name for name in target_files_zip.namelist()]
731 return ("SYSTEM/recovery-from-boot.p" in namelist or
732 "SYSTEM/etc/recovery.img" in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700733
Tao Bao457cbf62017-03-06 09:56:01 -0800734
Yifan Hong51d37562019-04-23 17:06:46 -0700735def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700736 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700737 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700738 return True
739 except KeyError:
740 return False
741
Tao Bao457cbf62017-03-06 09:56:01 -0800742
Yifan Hong9276cf02019-08-21 16:37:04 -0700743def HasTrebleEnabled(target_files, target_info):
744 def HasVendorPartition(target_files):
745 if os.path.isdir(target_files):
746 return os.path.isdir(os.path.join(target_files, "VENDOR"))
747 if zipfile.is_zipfile(target_files):
748 return HasPartition(zipfile.ZipFile(target_files), "vendor")
749 raise ValueError("Unknown target_files argument")
Yifan Hong51d37562019-04-23 17:06:46 -0700750
Yifan Hong9276cf02019-08-21 16:37:04 -0700751 return (HasVendorPartition(target_files) and
Tao Bao481bab82017-12-21 11:23:09 -0800752 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700753
754
Tao Bao481bab82017-12-21 11:23:09 -0800755def WriteFingerprintAssertion(script, target_info, source_info):
756 source_oem_props = source_info.oem_props
757 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700758
Tao Bao481bab82017-12-21 11:23:09 -0800759 if source_oem_props is None and target_oem_props is None:
760 script.AssertSomeFingerprint(
761 source_info.fingerprint, target_info.fingerprint)
762 elif source_oem_props is not None and target_oem_props is not None:
763 script.AssertSomeThumbprint(
764 target_info.GetBuildProp("ro.build.thumbprint"),
765 source_info.GetBuildProp("ro.build.thumbprint"))
766 elif source_oem_props is None and target_oem_props is not None:
767 script.AssertFingerprintOrThumbprint(
768 source_info.fingerprint,
769 target_info.GetBuildProp("ro.build.thumbprint"))
770 else:
771 script.AssertFingerprintOrThumbprint(
772 target_info.fingerprint,
773 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700774
Doug Zongkerfc44a512014-08-26 13:10:25 -0700775
Yifan Hong9276cf02019-08-21 16:37:04 -0700776def CheckVintfIfTrebleEnabled(target_files, target_info):
777 """Checks compatibility info of the input target files.
Tao Bao21803d32017-04-19 10:16:09 -0700778
Yifan Hong9276cf02019-08-21 16:37:04 -0700779 Metadata used for compatibility verification is retrieved from target_zip.
Tao Bao21803d32017-04-19 10:16:09 -0700780
Yifan Hong9276cf02019-08-21 16:37:04 -0700781 Compatibility should only be checked for devices that have enabled
Tao Baobcd1d162017-08-26 13:10:26 -0700782 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700783
784 Args:
Yifan Hong9276cf02019-08-21 16:37:04 -0700785 target_files: Path to zip file containing the source files to be included
786 for OTA. Can also be the path to extracted directory.
Tao Bao481bab82017-12-21 11:23:09 -0800787 target_info: The BuildInfo instance that holds the target build info.
Tao Bao21803d32017-04-19 10:16:09 -0700788 """
789
Tao Baobcd1d162017-08-26 13:10:26 -0700790 # Will only proceed if the target has enabled the Treble support (as well as
791 # having a /vendor partition).
Yifan Hong9276cf02019-08-21 16:37:04 -0700792 if not HasTrebleEnabled(target_files, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700793 return
794
xunchangabfa2652019-02-19 16:27:10 -0800795 # Skip adding the compatibility package as a workaround for b/114240221. The
796 # compatibility will always fail on devices without qualified kernels.
797 if OPTIONS.skip_compatibility_check:
798 return
799
Yifan Hong9276cf02019-08-21 16:37:04 -0700800 if not check_target_files_vintf.CheckVintf(target_files, target_info):
801 raise RuntimeError("VINTF compatibility check failed")
Tao Bao21803d32017-04-19 10:16:09 -0700802
803
Tianjie Xuf67dd802019-05-20 17:50:36 -0700804def GetBlockDifferences(target_zip, source_zip, target_info, source_info,
805 device_specific):
806 """Returns a ordered dict of block differences with partition name as key."""
807
808 def GetIncrementalBlockDifferenceForPartition(name):
809 if not HasPartition(source_zip, name):
810 raise RuntimeError("can't generate incremental that adds {}".format(name))
811
812 partition_src = common.GetUserImage(name, OPTIONS.source_tmp, source_zip,
813 info_dict=source_info,
814 allow_shared_blocks=allow_shared_blocks)
815
816 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
817 name, 4096, target_info)
818 partition_tgt = common.GetUserImage(name, OPTIONS.target_tmp, target_zip,
819 info_dict=target_info,
820 allow_shared_blocks=allow_shared_blocks,
821 hashtree_info_generator=
822 hashtree_info_generator)
823
824 # Check the first block of the source system partition for remount R/W only
825 # if the filesystem is ext4.
826 partition_source_info = source_info["fstab"]["/" + name]
827 check_first_block = partition_source_info.fs_type == "ext4"
828 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
829 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
830 # b) the blocks listed in block map may not contain all the bytes for a
831 # given file (because they're rounded to be 4K-aligned).
832 partition_target_info = target_info["fstab"]["/" + name]
833 disable_imgdiff = (partition_source_info.fs_type == "squashfs" or
834 partition_target_info.fs_type == "squashfs")
835 return common.BlockDifference(name, partition_src, partition_tgt,
836 check_first_block,
837 version=blockimgdiff_version,
838 disable_imgdiff=disable_imgdiff)
839
840 if source_zip:
841 # See notes in common.GetUserImage()
842 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
843 target_info.get('ext4_share_dup_blocks') == "true")
844 blockimgdiff_version = max(
845 int(i) for i in target_info.get(
846 "blockimgdiff_versions", "1").split(","))
847 assert blockimgdiff_version >= 3
848
849 block_diff_dict = collections.OrderedDict()
850 partition_names = ["system", "vendor", "product", "odm", "system_ext"]
851 for partition in partition_names:
852 if not HasPartition(target_zip, partition):
853 continue
854 # Full OTA update.
855 if not source_zip:
856 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, target_zip,
857 info_dict=target_info,
858 reset_file_map=True)
859 block_diff_dict[partition] = common.BlockDifference(partition, tgt,
860 src=None)
861 # Incremental OTA update.
862 else:
863 block_diff_dict[partition] = GetIncrementalBlockDifferenceForPartition(
864 partition)
865 assert "system" in block_diff_dict
866
867 # Get the block diffs from the device specific script. If there is a
868 # duplicate block diff for a partition, ignore the diff in the generic script
869 # and use the one in the device specific script instead.
870 if source_zip:
871 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
872 function_name = "IncrementalOTA_GetBlockDifferences"
873 else:
874 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
875 function_name = "FullOTA_GetBlockDifferences"
876
877 if device_specific_diffs:
878 assert all(isinstance(diff, common.BlockDifference)
879 for diff in device_specific_diffs), \
880 "{} is not returning a list of BlockDifference objects".format(
881 function_name)
882 for diff in device_specific_diffs:
883 if diff.partition in block_diff_dict:
884 logger.warning("Duplicate block difference found. Device specific block"
885 " diff for partition '%s' overrides the one in generic"
886 " script.", diff.partition)
887 block_diff_dict[diff.partition] = diff
888
889 return block_diff_dict
890
891
Tao Bao491d7e22018-02-21 13:17:22 -0800892def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800893 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700894
Tao Bao481bab82017-12-21 11:23:09 -0800895 # We don't know what version it will be installed on top of. We expect the API
896 # just won't change very often. Similarly for fstab, it might have changed in
897 # the target build.
898 target_api_version = target_info["recovery_api_version"]
899 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700900
Tao Bao481bab82017-12-21 11:23:09 -0800901 if target_info.oem_props and not OPTIONS.oem_no_mount:
902 target_info.WriteMountOemScript(script)
903
Tao Baodf3a48b2018-01-10 16:30:43 -0800904 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700905
Tao Bao491d7e22018-02-21 13:17:22 -0800906 if not OPTIONS.no_signing:
907 staging_file = common.MakeTempFile(suffix='.zip')
908 else:
909 staging_file = output_file
910
911 output_zip = zipfile.ZipFile(
912 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
913
Doug Zongker05d3dea2009-06-22 11:32:31 -0700914 device_specific = common.DeviceSpecificParams(
915 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800916 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700917 output_zip=output_zip,
918 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700919 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700920 metadata=metadata,
921 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700922
Tao Bao457cbf62017-03-06 09:56:01 -0800923 assert HasRecoveryPatch(input_zip)
Doug Zongkerc9253822014-02-04 12:17:58 -0800924
Tao Bao481bab82017-12-21 11:23:09 -0800925 # Assertions (e.g. downgrade check, device properties check).
926 ts = target_info.GetBuildProp("ro.build.date.utc")
927 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700928 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700929
Tao Bao481bab82017-12-21 11:23:09 -0800930 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700931 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800932
Tianjie Xuf67dd802019-05-20 17:50:36 -0700933 block_diff_dict = GetBlockDifferences(target_zip=input_zip, source_zip=None,
934 target_info=target_info,
935 source_info=None,
936 device_specific=device_specific)
937
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800938 # Two-step package strategy (in chronological order, which is *not*
939 # the order in which the generated script has things):
940 #
941 # if stage is not "2/3" or "3/3":
942 # write recovery image to boot partition
943 # set stage to "2/3"
944 # reboot to boot partition and restart recovery
945 # else if stage is "2/3":
946 # write recovery image to recovery partition
947 # set stage to "3/3"
948 # reboot to recovery partition and restart recovery
949 # else:
950 # (stage must be "3/3")
951 # set stage to ""
952 # do normal full package installation:
953 # wipe and install system, boot image, etc.
954 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700955 # complete script normally
956 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800957
958 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
959 OPTIONS.input_tmp, "RECOVERY")
960 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800961 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800962 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800963 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800964 assert fs.fs_type.upper() == "EMMC", \
965 "two-step packages only supported on devices with EMMC /misc partitions"
966 bcb_dev = {"bcb_dev": fs.device}
967 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
968 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700969if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800970""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800971
972 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
973 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800974 script.WriteRawImage("/recovery", "recovery.img")
975 script.AppendExtra("""
976set_stage("%(bcb_dev)s", "3/3");
977reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700978else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800979""" % bcb_dev)
980
Tao Baod42e97e2016-11-30 12:11:57 -0800981 # Stage 3/3: Make changes.
982 script.Comment("Stage 3/3")
983
Tao Bao6c55a8a2015-04-08 15:30:27 -0700984 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800985 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -0700986
Doug Zongkere5ff5902012-01-17 10:55:37 -0800987 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700988
Tianjie Xuf67dd802019-05-20 17:50:36 -0700989 # All other partitions as well as the data wipe use 10% of the progress, and
990 # the update of the system partition takes the remaining progress.
991 system_progress = 0.9 - (len(block_diff_dict) - 1) * 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700992 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -0800993 system_progress -= 0.1
Tianjie Xuf67dd802019-05-20 17:50:36 -0700994 progress_dict = {partition: 0.1 for partition in block_diff_dict}
995 progress_dict["system"] = system_progress
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700996
Yifan Hong10c530d2018-12-27 17:34:18 -0800997 if target_info.get('use_dynamic_partitions') == "true":
998 # Use empty source_info_dict to indicate that all partitions / groups must
999 # be re-added.
1000 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1001 info_dict=OPTIONS.info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001002 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001003 progress_dict=progress_dict)
1004 dynamic_partitions_diff.WriteScript(script, output_zip,
1005 write_verify_script=OPTIONS.verify)
1006 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001007 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001008 block_diff.WriteScript(script, output_zip,
1009 progress=progress_dict.get(block_diff.partition),
1010 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001011
Yifan Hong9276cf02019-08-21 16:37:04 -07001012 CheckVintfIfTrebleEnabled(OPTIONS.input_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001013
Yifan Hong10c530d2018-12-27 17:34:18 -08001014 boot_img = common.GetBootableImage(
1015 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001016 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001017 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001018
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001019 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001020
Tianjie Xuf67dd802019-05-20 17:50:36 -07001021 script.ShowProgress(0.1, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001022 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001023
Doug Zongker1c390a22009-05-14 19:06:36 -07001024 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001025 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001026
Doug Zongker14833602010-02-02 13:12:04 -08001027 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001028
Doug Zongker922206e2014-03-04 13:16:24 -08001029 if OPTIONS.wipe_user_data:
1030 script.ShowProgress(0.1, 10)
1031 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001032
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001033 if OPTIONS.two_step:
1034 script.AppendExtra("""
1035set_stage("%(bcb_dev)s", "");
1036""" % bcb_dev)
1037 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001038
1039 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1040 script.Comment("Stage 1/3")
1041 _WriteRecoveryImageToBoot(script, output_zip)
1042
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001043 script.AppendExtra("""
1044set_stage("%(bcb_dev)s", "2/3");
1045reboot_now("%(bcb_dev)s", "");
1046endif;
1047endif;
1048""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001049
Tao Bao5d182562016-02-23 11:38:39 -08001050 script.SetProgress(1)
1051 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001052 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001053
1054 # We haven't written the metadata entry, which will be done in
1055 # FinalizeMetadata.
1056 common.ZipClose(output_zip)
1057
1058 needed_property_files = (
1059 NonAbOtaPropertyFiles(),
1060 )
1061 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001062
Doug Zongkerfc44a512014-08-26 13:10:25 -07001063
xunchang1cfe2512019-02-19 14:14:48 -08001064def WriteMetadata(metadata, output):
1065 """Writes the metadata to the zip archive or a file.
1066
1067 Args:
1068 metadata: The metadata dict for the package.
1069 output: A ZipFile object or a string of the output file path.
1070 """
1071
Tao Bao59cf0c52019-06-25 10:04:24 -07001072 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001073 if isinstance(output, zipfile.ZipFile):
1074 common.ZipWriteStr(output, METADATA_NAME, value,
1075 compress_type=zipfile.ZIP_STORED)
1076 return
1077
1078 with open(output, 'w') as f:
1079 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001080
Doug Zongkerfc44a512014-08-26 13:10:25 -07001081
Tao Bao481bab82017-12-21 11:23:09 -08001082def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001083 # Only incremental OTAs are allowed to reach here.
1084 assert OPTIONS.incremental_source is not None
1085
Tao Bao481bab82017-12-21 11:23:09 -08001086 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1087 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001088 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001089
1090 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001091 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001092 raise RuntimeError(
1093 "--downgrade or --override_timestamp specified but no downgrade "
1094 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001095 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001096 else:
1097 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001098 raise RuntimeError(
1099 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1100 "Need to specify --override_timestamp OR --downgrade to allow "
1101 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001102
1103
Tao Baodf3a48b2018-01-10 16:30:43 -08001104def GetPackageMetadata(target_info, source_info=None):
1105 """Generates and returns the metadata dict.
1106
1107 It generates a dict() that contains the info to be written into an OTA
1108 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001109 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001110
1111 Args:
1112 target_info: The BuildInfo instance that holds the target build info.
1113 source_info: The BuildInfo instance that holds the source build info, or
1114 None if generating full OTA.
1115
1116 Returns:
1117 A dict to be written into package metadata entry.
1118 """
1119 assert isinstance(target_info, BuildInfo)
1120 assert source_info is None or isinstance(source_info, BuildInfo)
1121
1122 metadata = {
1123 'post-build' : target_info.fingerprint,
1124 'post-build-incremental' : target_info.GetBuildProp(
1125 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001126 'post-sdk-level' : target_info.GetBuildProp(
1127 'ro.build.version.sdk'),
1128 'post-security-patch-level' : target_info.GetBuildProp(
1129 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001130 }
1131
1132 if target_info.is_ab:
1133 metadata['ota-type'] = 'AB'
1134 metadata['ota-required-cache'] = '0'
1135 else:
1136 metadata['ota-type'] = 'BLOCK'
1137
1138 if OPTIONS.wipe_user_data:
1139 metadata['ota-wipe'] = 'yes'
1140
Tao Bao393eeb42019-03-06 16:00:38 -08001141 if OPTIONS.retrofit_dynamic_partitions:
1142 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1143
Tao Baodf3a48b2018-01-10 16:30:43 -08001144 is_incremental = source_info is not None
1145 if is_incremental:
1146 metadata['pre-build'] = source_info.fingerprint
1147 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1148 'ro.build.version.incremental')
1149 metadata['pre-device'] = source_info.device
1150 else:
1151 metadata['pre-device'] = target_info.device
1152
Tao Baofaa8e0b2018-04-12 14:31:43 -07001153 # Use the actual post-timestamp, even for a downgrade case.
1154 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1155
1156 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001157 if is_incremental:
1158 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001159
1160 return metadata
1161
1162
Tao Baod3fc38a2018-03-08 16:09:01 -08001163class PropertyFiles(object):
1164 """A class that computes the property-files string for an OTA package.
1165
1166 A property-files string is a comma-separated string that contains the
1167 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1168 can be fetched directly with the package URL along with the offset/size info.
1169 These strings can be used for streaming A/B OTAs, or allowing an updater to
1170 download package metadata entry directly, without paying the cost of
1171 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001172
Tao Baocc8e2662018-03-01 19:30:00 -08001173 Computing the final property-files string requires two passes. Because doing
1174 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1175 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1176 values.
1177
1178 This class provides functions to be called for each pass. The general flow is
1179 as follows.
1180
Tao Baod3fc38a2018-03-08 16:09:01 -08001181 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001182 # The first pass, which writes placeholders before doing initial signing.
1183 property_files.Compute()
1184 SignOutput()
1185
1186 # The second pass, by replacing the placeholders with actual data.
1187 property_files.Finalize()
1188 SignOutput()
1189
1190 And the caller can additionally verify the final result.
1191
1192 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001193 """
1194
Tao Baocc8e2662018-03-01 19:30:00 -08001195 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001196 self.name = None
1197 self.required = ()
1198 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001199
Tao Baocc8e2662018-03-01 19:30:00 -08001200 def Compute(self, input_zip):
1201 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001202
Tao Baocc8e2662018-03-01 19:30:00 -08001203 We reserve extra space for the offset and size of the metadata entry itself,
1204 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001205
Tao Baocc8e2662018-03-01 19:30:00 -08001206 Args:
1207 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001208
Tao Baocc8e2662018-03-01 19:30:00 -08001209 Returns:
1210 A string with placeholders for the metadata offset/size info, e.g.
1211 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1212 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001213 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001214
Tao Baod2ce2ed2018-03-16 12:59:42 -07001215 class InsufficientSpaceException(Exception):
1216 pass
1217
Tao Baocc8e2662018-03-01 19:30:00 -08001218 def Finalize(self, input_zip, reserved_length):
1219 """Finalizes a property-files string with actual METADATA offset/size info.
1220
1221 The input ZIP file has been signed, with the ZIP entries in the desired
1222 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1223 the ZIP entry offsets and construct the property-files string with actual
1224 data. Note that during this process, we must pad the property-files string
1225 to the reserved length, so that the METADATA entry size remains the same.
1226 Otherwise the entries' offsets and sizes may change again.
1227
1228 Args:
1229 input_zip: The input ZIP file.
1230 reserved_length: The reserved length of the property-files string during
1231 the call to Compute(). The final string must be no more than this
1232 size.
1233
1234 Returns:
1235 A property-files string including the metadata offset/size info, e.g.
1236 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1237
1238 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001239 InsufficientSpaceException: If the reserved length is insufficient to hold
1240 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001241 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001242 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001243 if len(result) > reserved_length:
1244 raise self.InsufficientSpaceException(
1245 'Insufficient reserved space: reserved={}, actual={}'.format(
1246 reserved_length, len(result)))
1247
Tao Baocc8e2662018-03-01 19:30:00 -08001248 result += ' ' * (reserved_length - len(result))
1249 return result
1250
1251 def Verify(self, input_zip, expected):
1252 """Verifies the input ZIP file contains the expected property-files string.
1253
1254 Args:
1255 input_zip: The input ZIP file.
1256 expected: The property-files string that's computed from Finalize().
1257
1258 Raises:
1259 AssertionError: On finding a mismatch.
1260 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001261 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001262 assert actual == expected, \
1263 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1264
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001265 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1266 """
1267 Constructs the property-files string per request.
1268
1269 Args:
1270 zip_file: The input ZIP file.
1271 reserved_length: The reserved length of the property-files string.
1272
1273 Returns:
1274 A property-files string including the metadata offset/size info, e.g.
1275 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1276 """
Tao Baocc8e2662018-03-01 19:30:00 -08001277
1278 def ComputeEntryOffsetSize(name):
1279 """Computes the zip entry offset and size."""
1280 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001281 offset = info.header_offset
1282 offset += zipfile.sizeFileHeader
1283 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001284 size = info.file_size
1285 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1286
1287 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001288 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001289 for entry in self.required:
1290 tokens.append(ComputeEntryOffsetSize(entry))
1291 for entry in self.optional:
1292 if entry in zip_file.namelist():
1293 tokens.append(ComputeEntryOffsetSize(entry))
1294
1295 # 'META-INF/com/android/metadata' is required. We don't know its actual
1296 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001297 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1298 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1299 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1300 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001301 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001302 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001303 else:
1304 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1305
1306 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001307
Tao Bao85f16982018-03-08 16:28:33 -08001308 def _GetPrecomputed(self, input_zip):
1309 """Computes the additional tokens to be included into the property-files.
1310
1311 This applies to tokens without actual ZIP entries, such as
1312 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1313 that they can download the payload metadata directly with the info.
1314
1315 Args:
1316 input_zip: The input zip file.
1317
1318 Returns:
1319 A list of strings (tokens) to be added to the property-files string.
1320 """
1321 # pylint: disable=no-self-use
1322 # pylint: disable=unused-argument
1323 return []
1324
Tao Baofe5b69a2018-03-02 09:47:43 -08001325
Tao Baod3fc38a2018-03-08 16:09:01 -08001326class StreamingPropertyFiles(PropertyFiles):
1327 """A subclass for computing the property-files for streaming A/B OTAs."""
1328
1329 def __init__(self):
1330 super(StreamingPropertyFiles, self).__init__()
1331 self.name = 'ota-streaming-property-files'
1332 self.required = (
1333 # payload.bin and payload_properties.txt must exist.
1334 'payload.bin',
1335 'payload_properties.txt',
1336 )
1337 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001338 # care_map is available only if dm-verity is enabled.
1339 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001340 'care_map.txt',
1341 # compatibility.zip is available only if target supports Treble.
1342 'compatibility.zip',
1343 )
1344
1345
Tao Bao85f16982018-03-08 16:28:33 -08001346class AbOtaPropertyFiles(StreamingPropertyFiles):
1347 """The property-files for A/B OTA that includes payload_metadata.bin info.
1348
1349 Since P, we expose one more token (aka property-file), in addition to the ones
1350 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1351 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1352 doesn't exist as a separate ZIP entry, but can be used to verify if the
1353 payload can be applied on the given device.
1354
1355 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1356 and the newly added 'ota-property-files' in P. The new token will only be
1357 available in 'ota-property-files'.
1358 """
1359
1360 def __init__(self):
1361 super(AbOtaPropertyFiles, self).__init__()
1362 self.name = 'ota-property-files'
1363
1364 def _GetPrecomputed(self, input_zip):
1365 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1366 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1367
1368 @staticmethod
1369 def _GetPayloadMetadataOffsetAndSize(input_zip):
1370 """Computes the offset and size of the payload metadata for a given package.
1371
1372 (From system/update_engine/update_metadata.proto)
1373 A delta update file contains all the deltas needed to update a system from
1374 one specific version to another specific version. The update format is
1375 represented by this struct pseudocode:
1376
1377 struct delta_update_file {
1378 char magic[4] = "CrAU";
1379 uint64 file_format_version;
1380 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1381
1382 // Only present if format_version > 1:
1383 uint32 metadata_signature_size;
1384
1385 // The Bzip2 compressed DeltaArchiveManifest
1386 char manifest[metadata_signature_size];
1387
1388 // The signature of the metadata (from the beginning of the payload up to
1389 // this location, not including the signature itself). This is a
1390 // serialized Signatures message.
1391 char medatada_signature_message[metadata_signature_size];
1392
1393 // Data blobs for files, no specific format. The specific offset
1394 // and length of each data blob is recorded in the DeltaArchiveManifest.
1395 struct {
1396 char data[];
1397 } blobs[];
1398
1399 // These two are not signed:
1400 uint64 payload_signatures_message_size;
1401 char payload_signatures_message[];
1402 };
1403
1404 'payload-metadata.bin' contains all the bytes from the beginning of the
1405 payload, till the end of 'medatada_signature_message'.
1406 """
1407 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001408 payload_offset = payload_info.header_offset
1409 payload_offset += zipfile.sizeFileHeader
1410 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001411 payload_size = payload_info.file_size
1412
Tao Bao59cf0c52019-06-25 10:04:24 -07001413 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001414 header_bin = payload_fp.read(24)
1415
1416 # network byte order (big-endian)
1417 header = struct.unpack("!IQQL", header_bin)
1418
1419 # 'CrAU'
1420 magic = header[0]
1421 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1422
1423 manifest_size = header[2]
1424 metadata_signature_size = header[3]
1425 metadata_total = 24 + manifest_size + metadata_signature_size
1426 assert metadata_total < payload_size
1427
1428 return (payload_offset, metadata_total)
1429
1430
Tao Bao491d7e22018-02-21 13:17:22 -08001431class NonAbOtaPropertyFiles(PropertyFiles):
1432 """The property-files for non-A/B OTA.
1433
1434 For non-A/B OTA, the property-files string contains the info for METADATA
1435 entry, with which a system updater can be fetched the package metadata prior
1436 to downloading the entire package.
1437 """
1438
1439 def __init__(self):
1440 super(NonAbOtaPropertyFiles, self).__init__()
1441 self.name = 'ota-property-files'
1442
1443
Tao Baod3fc38a2018-03-08 16:09:01 -08001444def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001445 """Finalizes the metadata and signs an A/B OTA package.
1446
1447 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1448 that contains the offsets and sizes for the ZIP entries. An example
1449 property-files string is as follows.
1450
1451 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1452
1453 OTA server can pass down this string, in addition to the package URL, to the
1454 system update client. System update client can then fetch individual ZIP
1455 entries (ZIP_STORED) directly at the given offset of the URL.
1456
1457 Args:
1458 metadata: The metadata dict for the package.
1459 input_file: The input ZIP filename that doesn't contain the package METADATA
1460 entry yet.
1461 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001462 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001463 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001464
Tao Baod2ce2ed2018-03-16 12:59:42 -07001465 def ComputeAllPropertyFiles(input_file, needed_property_files):
1466 # Write the current metadata entry with placeholders.
1467 with zipfile.ZipFile(input_file) as input_zip:
1468 for property_files in needed_property_files:
1469 metadata[property_files.name] = property_files.Compute(input_zip)
1470 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001471
Tao Baod2ce2ed2018-03-16 12:59:42 -07001472 if METADATA_NAME in namelist:
1473 common.ZipDelete(input_file, METADATA_NAME)
1474 output_zip = zipfile.ZipFile(input_file, 'a')
1475 WriteMetadata(metadata, output_zip)
1476 common.ZipClose(output_zip)
1477
1478 if OPTIONS.no_signing:
1479 return input_file
1480
Tao Bao491d7e22018-02-21 13:17:22 -08001481 prelim_signing = common.MakeTempFile(suffix='.zip')
1482 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001483 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001484
Tao Baod2ce2ed2018-03-16 12:59:42 -07001485 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1486 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1487 for property_files in needed_property_files:
1488 metadata[property_files.name] = property_files.Finalize(
1489 prelim_signing_zip, len(metadata[property_files.name]))
1490
1491 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1492 # entries, as well as padding the entry headers. We do a preliminary signing
1493 # (with an incomplete metadata entry) to allow that to happen. Then compute
1494 # the ZIP entry offsets, write back the final metadata and do the final
1495 # signing.
1496 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1497 try:
1498 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1499 except PropertyFiles.InsufficientSpaceException:
1500 # Even with the preliminary signing, the entry orders may change
1501 # dramatically, which leads to insufficiently reserved space during the
1502 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1503 # preliminary signing works, based on the already ordered ZIP entries, to
1504 # address the issue.
1505 prelim_signing = ComputeAllPropertyFiles(
1506 prelim_signing, needed_property_files)
1507 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001508
1509 # Replace the METADATA entry.
1510 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001511 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001512 WriteMetadata(metadata, output_zip)
1513 common.ZipClose(output_zip)
1514
1515 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001516 if OPTIONS.no_signing:
1517 output_file = prelim_signing
1518 else:
1519 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001520
1521 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001522 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001523 for property_files in needed_property_files:
1524 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001525
xunchang1cfe2512019-02-19 14:14:48 -08001526 # If requested, dump the metadata to a separate file.
1527 output_metadata_path = OPTIONS.output_metadata_path
1528 if output_metadata_path:
1529 WriteMetadata(metadata, output_metadata_path)
1530
Tao Baofe5b69a2018-03-02 09:47:43 -08001531
Tao Bao491d7e22018-02-21 13:17:22 -08001532def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001533 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1534 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001535
Tao Bao481bab82017-12-21 11:23:09 -08001536 target_api_version = target_info["recovery_api_version"]
1537 source_api_version = source_info["recovery_api_version"]
1538 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001539 logger.warning(
1540 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001541
Tao Bao481bab82017-12-21 11:23:09 -08001542 script = edify_generator.EdifyGenerator(
1543 source_api_version, target_info, fstab=source_info["fstab"])
1544
1545 if target_info.oem_props or source_info.oem_props:
1546 if not OPTIONS.oem_no_mount:
1547 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001548
Tao Baodf3a48b2018-01-10 16:30:43 -08001549 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001550
Tao Bao491d7e22018-02-21 13:17:22 -08001551 if not OPTIONS.no_signing:
1552 staging_file = common.MakeTempFile(suffix='.zip')
1553 else:
1554 staging_file = output_file
1555
1556 output_zip = zipfile.ZipFile(
1557 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1558
Geremy Condra36bd3652014-02-06 19:45:10 -08001559 device_specific = common.DeviceSpecificParams(
1560 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001561 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001562 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001563 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001564 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001565 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001566 output_zip=output_zip,
1567 script=script,
1568 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001569 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001570
Geremy Condra36bd3652014-02-06 19:45:10 -08001571 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001572 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001573 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001574 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001575 updating_boot = (not OPTIONS.two_step and
1576 (source_boot.data != target_boot.data))
1577
Geremy Condra36bd3652014-02-06 19:45:10 -08001578 target_recovery = common.GetBootableImage(
1579 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001580
Tianjie Xuf67dd802019-05-20 17:50:36 -07001581 block_diff_dict = GetBlockDifferences(target_zip=target_zip,
1582 source_zip=source_zip,
1583 target_info=target_info,
1584 source_info=source_info,
1585 device_specific=device_specific)
Geremy Condra36bd3652014-02-06 19:45:10 -08001586
Yifan Hong9276cf02019-08-21 16:37:04 -07001587 CheckVintfIfTrebleEnabled(OPTIONS.target_tmp, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001588
Tao Bao481bab82017-12-21 11:23:09 -08001589 # Assertions (e.g. device properties check).
1590 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001591 device_specific.IncrementalOTA_Assertions()
1592
1593 # Two-step incremental package strategy (in chronological order,
1594 # which is *not* the order in which the generated script has
1595 # things):
1596 #
1597 # if stage is not "2/3" or "3/3":
1598 # do verification on current system
1599 # write recovery image to boot partition
1600 # set stage to "2/3"
1601 # reboot to boot partition and restart recovery
1602 # else if stage is "2/3":
1603 # write recovery image to recovery partition
1604 # set stage to "3/3"
1605 # reboot to recovery partition and restart recovery
1606 # else:
1607 # (stage must be "3/3")
1608 # perform update:
1609 # patch system files, etc.
1610 # force full install of new boot image
1611 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001612 # complete script normally
1613 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001614
1615 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001616 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001617 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001618 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001619 assert fs.fs_type.upper() == "EMMC", \
1620 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001621 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001622 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1623 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001624if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001625""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001626
1627 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1628 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001629 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001630 script.WriteRawImage("/recovery", "recovery.img")
1631 script.AppendExtra("""
1632set_stage("%(bcb_dev)s", "3/3");
1633reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001634else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001635""" % bcb_dev)
1636
Tao Baod42e97e2016-11-30 12:11:57 -08001637 # Stage 1/3: (a) Verify the current system.
1638 script.Comment("Stage 1/3")
1639
Tao Bao6c55a8a2015-04-08 15:30:27 -07001640 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001641 script.Print("Source: {}".format(source_info.fingerprint))
1642 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001643
Geremy Condra36bd3652014-02-06 19:45:10 -08001644 script.Print("Verifying current system...")
1645
1646 device_specific.IncrementalOTA_VerifyBegin()
1647
Tao Bao481bab82017-12-21 11:23:09 -08001648 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001649
Tao Baod8d14be2016-02-04 14:26:02 -08001650 # Check the required cache size (i.e. stashed blocks).
Tianjie Xuf67dd802019-05-20 17:50:36 -07001651 required_cache_sizes = [diff.required_cache for diff in
1652 block_diff_dict.values()]
Geremy Condra36bd3652014-02-06 19:45:10 -08001653 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001654 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001655 d = common.Difference(target_boot, source_boot)
1656 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001657 if d is None:
1658 include_full_boot = True
1659 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1660 else:
1661 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001662
Tao Bao32fcdab2018-10-12 10:30:39 -07001663 logger.info(
1664 "boot target: %d source: %d diff: %d", target_boot.size,
1665 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001666
Tao Bao51216552018-08-26 11:53:15 -07001667 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001668
Tao Bao51216552018-08-26 11:53:15 -07001669 script.PatchPartitionCheck(
1670 "{}:{}:{}:{}".format(
1671 boot_type, boot_device, target_boot.size, target_boot.sha1),
1672 "{}:{}:{}:{}".format(
1673 boot_type, boot_device, source_boot.size, source_boot.sha1))
1674
Tianjie Xuf67dd802019-05-20 17:50:36 -07001675 required_cache_sizes.append(target_boot.size)
Tao Baod8d14be2016-02-04 14:26:02 -08001676
Tianjie Xuf67dd802019-05-20 17:50:36 -07001677 if required_cache_sizes:
1678 script.CacheFreeSpaceCheck(max(required_cache_sizes))
1679
1680 # Verify the existing partitions.
1681 for diff in block_diff_dict.values():
1682 diff.WriteVerifyScript(script, touched_blocks_only=True)
Geremy Condra36bd3652014-02-06 19:45:10 -08001683
1684 device_specific.IncrementalOTA_VerifyEnd()
1685
1686 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001687 # Stage 1/3: (b) Write recovery image to /boot.
1688 _WriteRecoveryImageToBoot(script, output_zip)
1689
Geremy Condra36bd3652014-02-06 19:45:10 -08001690 script.AppendExtra("""
1691set_stage("%(bcb_dev)s", "2/3");
1692reboot_now("%(bcb_dev)s", "");
1693else
1694""" % bcb_dev)
1695
Tao Baod42e97e2016-11-30 12:11:57 -08001696 # Stage 3/3: Make changes.
1697 script.Comment("Stage 3/3")
1698
Geremy Condra36bd3652014-02-06 19:45:10 -08001699 script.Comment("---- start making changes here ----")
1700
1701 device_specific.IncrementalOTA_InstallBegin()
1702
Tianjie Xuf67dd802019-05-20 17:50:36 -07001703 progress_dict = {partition: 0.1 for partition in block_diff_dict}
1704 progress_dict["system"] = 1 - len(block_diff_dict) * 0.1
Yifan Hong10c530d2018-12-27 17:34:18 -08001705
1706 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1707 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1708 raise RuntimeError(
1709 "can't generate incremental that disables dynamic partitions")
1710 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1711 info_dict=OPTIONS.target_info_dict,
1712 source_info_dict=OPTIONS.source_info_dict,
Tianjie Xuf67dd802019-05-20 17:50:36 -07001713 block_diffs=block_diff_dict.values(),
Yifan Hong10c530d2018-12-27 17:34:18 -08001714 progress_dict=progress_dict)
1715 dynamic_partitions_diff.WriteScript(
1716 script, output_zip, write_verify_script=OPTIONS.verify)
1717 else:
Tianjie Xuf67dd802019-05-20 17:50:36 -07001718 for block_diff in block_diff_dict.values():
Yifan Hong10c530d2018-12-27 17:34:18 -08001719 block_diff.WriteScript(script, output_zip,
1720 progress=progress_dict.get(block_diff.partition),
1721 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001722
1723 if OPTIONS.two_step:
1724 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1725 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001726 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001727
1728 if not OPTIONS.two_step:
1729 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001730 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001731 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001732 script.Print("Installing boot image...")
1733 script.WriteRawImage("/boot", "boot.img")
1734 else:
1735 # Produce the boot image by applying a patch to the current
1736 # contents of the boot partition, and write it back to the
1737 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001738 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001739 script.Print("Patching boot image...")
1740 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001741 script.PatchPartition(
1742 '{}:{}:{}:{}'.format(
1743 boot_type, boot_device, target_boot.size, target_boot.sha1),
1744 '{}:{}:{}:{}'.format(
1745 boot_type, boot_device, source_boot.size, source_boot.sha1),
1746 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001747 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001748 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001749
1750 # Do device-specific installation (eg, write radio image).
1751 device_specific.IncrementalOTA_InstallEnd()
1752
1753 if OPTIONS.extra_script is not None:
1754 script.AppendExtra(OPTIONS.extra_script)
1755
Doug Zongker922206e2014-03-04 13:16:24 -08001756 if OPTIONS.wipe_user_data:
1757 script.Print("Erasing user data...")
1758 script.FormatPartition("/data")
1759
Geremy Condra36bd3652014-02-06 19:45:10 -08001760 if OPTIONS.two_step:
1761 script.AppendExtra("""
1762set_stage("%(bcb_dev)s", "");
1763endif;
1764endif;
1765""" % bcb_dev)
1766
1767 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001768 # For downgrade OTAs, we prefer to use the update-binary in the source
1769 # build that is actually newer than the one in the target build.
1770 if OPTIONS.downgrade:
1771 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1772 else:
1773 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001774 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001775
1776 # We haven't written the metadata entry yet, which will be handled in
1777 # FinalizeMetadata().
1778 common.ZipClose(output_zip)
1779
1780 # Sign the generated zip package unless no_signing is specified.
1781 needed_property_files = (
1782 NonAbOtaPropertyFiles(),
1783 )
1784 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001785
Doug Zongker32b527d2014-03-04 10:03:02 -08001786
Tao Bao15a146a2018-02-21 16:06:59 -08001787def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001788 """Returns a target-files.zip file for generating secondary payload.
1789
1790 Although the original target-files.zip already contains secondary slot
1791 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1792 ones without _other suffix. Note that we cannot instead modify the names in
1793 META/ab_partitions.txt, because there are no matching partitions on device.
1794
1795 For the partitions that don't have secondary images, the ones for primary
1796 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1797 bootloader images in the inactive slot.
1798
1799 Args:
1800 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001801 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001802
1803 Returns:
1804 The filename of the target-files.zip for generating secondary payload.
1805 """
Tianjie Xu1c808002019-09-11 00:29:26 -07001806
1807 def GetInfoForSecondaryImages(info_file):
1808 """Updates info file for secondary payload generation.
1809
1810 Scan each line in the info file, and remove the unwanted partitions from
1811 the dynamic partition list in the related properties. e.g.
1812 "super_google_dynamic_partitions_partition_list=system vendor product"
1813 will become "super_google_dynamic_partitions_partition_list=system".
1814
1815 Args:
1816 info_file: The input info file. e.g. misc_info.txt.
1817
1818 Returns:
1819 A string of the updated info content.
1820 """
1821
1822 output_list = []
1823 with open(info_file) as f:
1824 lines = f.read().splitlines()
1825
1826 # The suffix in partition_list variables that follows the name of the
1827 # partition group.
1828 LIST_SUFFIX = 'partition_list'
1829 for line in lines:
1830 if line.startswith('#') or '=' not in line:
1831 output_list.append(line)
1832 continue
1833 key, value = line.strip().split('=', 1)
1834 if key == 'dynamic_partition_list' or key.endswith(LIST_SUFFIX):
1835 partitions = value.split()
1836 partitions = [partition for partition in partitions if partition
1837 not in SECONDARY_IMAGES_SKIP_PARTITIONS]
1838 output_list.append('{}={}'.format(key, ' '.join(partitions)))
1839 else:
1840 output_list.append(line)
1841 return '\n'.join(output_list)
1842
Tao Baof7140c02018-01-30 17:09:24 -08001843 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1844 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1845
Tao Baodba59ee2018-01-09 13:21:02 -08001846 with zipfile.ZipFile(input_file, 'r') as input_zip:
1847 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001848
Tao Bao0ff15de2019-03-20 11:26:06 -07001849 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001850 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001851 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1852 if info.filename == 'IMAGES/system_other.img':
1853 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1854
1855 # Primary images and friends need to be skipped explicitly.
1856 elif info.filename in ('IMAGES/system.img',
1857 'IMAGES/system.map'):
1858 pass
Tianjie Xu1c808002019-09-11 00:29:26 -07001859 # Images like vendor and product are not needed in the secondary payload.
1860 elif info.filename in ['IMAGES/{}.img'.format(partition) for partition in
1861 SECONDARY_IMAGES_SKIP_PARTITIONS]:
1862 pass
Tao Baof7140c02018-01-30 17:09:24 -08001863
Tao Bao15a146a2018-02-21 16:06:59 -08001864 # Skip copying the postinstall config if requested.
1865 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1866 pass
1867
Tianjie Xu1c808002019-09-11 00:29:26 -07001868 elif info.filename.startswith('META/'):
1869 # Remove the unnecessary partitions for secondary images from the
1870 # ab_partitions file.
1871 if info.filename == AB_PARTITIONS:
1872 with open(unzipped_file) as f:
1873 partition_list = f.read().splitlines()
1874 partition_list = [partition for partition in partition_list if partition
1875 and partition not in SECONDARY_IMAGES_SKIP_PARTITIONS]
1876 common.ZipWriteStr(target_zip, info.filename, '\n'.join(partition_list))
1877 # Remove the unnecessary partitions from the dynamic partitions list.
1878 elif (info.filename == 'META/misc_info.txt' or
1879 info.filename == DYNAMIC_PARTITION_INFO):
1880 modified_info = GetInfoForSecondaryImages(unzipped_file)
1881 common.ZipWriteStr(target_zip, info.filename, modified_info)
1882 else:
1883 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
1884 elif info.filename.startswith(('IMAGES/', 'RADIO/')):
Tao Baof7140c02018-01-30 17:09:24 -08001885 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
1886
Tao Baof7140c02018-01-30 17:09:24 -08001887 common.ZipClose(target_zip)
1888
1889 return target_file
1890
1891
Tao Bao15a146a2018-02-21 16:06:59 -08001892def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1893 """Returns a target-files.zip that's not containing postinstall_config.txt.
1894
1895 This allows brillo_update_payload script to skip writing all the postinstall
1896 hooks in the generated payload. The input target-files.zip file will be
1897 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1898 contain the postinstall_config.txt entry, the input file will be returned.
1899
1900 Args:
1901 input_file: The input target-files.zip filename.
1902
1903 Returns:
1904 The filename of target-files.zip that doesn't contain postinstall config.
1905 """
1906 # We should only make a copy if postinstall_config entry exists.
1907 with zipfile.ZipFile(input_file, 'r') as input_zip:
1908 if POSTINSTALL_CONFIG not in input_zip.namelist():
1909 return input_file
1910
1911 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1912 shutil.copyfile(input_file, target_file)
1913 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1914 return target_file
1915
1916
Yifan Hong50e79542018-11-08 17:44:12 -08001917def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001918 super_block_devices,
1919 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001920 """Returns a target-files.zip for retrofitting dynamic partitions.
1921
1922 This allows brillo_update_payload to generate an OTA based on the exact
1923 bits on the block devices. Postinstall is disabled.
1924
1925 Args:
1926 input_file: The input target-files.zip filename.
1927 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001928 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001929
1930 Returns:
1931 The filename of target-files.zip with *.img replaced with super_*.img for
1932 each block device in super_block_devices.
1933 """
1934 assert super_block_devices, "No super_block_devices are specified."
1935
1936 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001937 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001938
1939 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1940 shutil.copyfile(input_file, target_file)
1941
Tao Baoa3705452019-06-24 15:33:41 -07001942 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001943 namelist = input_zip.namelist()
1944
Yifan Hongb433eba2019-03-06 12:42:53 -08001945 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1946
1947 # Remove partitions from META/ab_partitions.txt that is in
1948 # dynamic_partition_list but not in super_block_devices so that
1949 # brillo_update_payload won't generate update for those logical partitions.
1950 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1951 with open(ab_partitions_file) as f:
1952 ab_partitions_lines = f.readlines()
1953 ab_partitions = [line.strip() for line in ab_partitions_lines]
1954 # Assert that all super_block_devices are in ab_partitions
1955 super_device_not_updated = [partition for partition in super_block_devices
1956 if partition not in ab_partitions]
1957 assert not super_device_not_updated, \
1958 "{} is in super_block_devices but not in {}".format(
1959 super_device_not_updated, AB_PARTITIONS)
1960 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1961 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1962 with open(new_ab_partitions, 'w') as f:
1963 for partition in ab_partitions:
1964 if (partition in dynamic_partition_list and
1965 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001966 logger.info("Dropping %s from ab_partitions.txt", partition)
1967 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001968 f.write(partition + "\n")
1969 to_delete = [AB_PARTITIONS]
1970
Yifan Hong50e79542018-11-08 17:44:12 -08001971 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001972 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001973
1974 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1975 # is a regular update on devices without dynamic partitions support.
1976 to_delete += [DYNAMIC_PARTITION_INFO]
1977
Tao Bao03fecb62018-11-28 10:59:23 -08001978 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001979 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001980 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001981
1982 common.ZipDelete(target_file, to_delete)
1983
Yifan Hong50e79542018-11-08 17:44:12 -08001984 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1985
1986 # Write super_{foo}.img as {foo}.img.
1987 for src, dst in replace.items():
1988 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001989 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001990 unzipped_file = os.path.join(input_tmp, *src.split('/'))
1991 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
1992
Yifan Hongb433eba2019-03-06 12:42:53 -08001993 # Write new ab_partitions.txt file
1994 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
1995
Yifan Hong50e79542018-11-08 17:44:12 -08001996 common.ZipClose(target_zip)
1997
1998 return target_file
1999
2000
Tao Baof0c4aa22018-04-30 20:29:30 -07002001def GenerateAbOtaPackage(target_file, output_file, source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002002 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002003 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002004 if not OPTIONS.no_signing:
2005 staging_file = common.MakeTempFile(suffix='.zip')
2006 else:
2007 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002008 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002009 compression=zipfile.ZIP_DEFLATED)
2010
Tao Bao481bab82017-12-21 11:23:09 -08002011 if source_file is not None:
2012 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2013 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2014 else:
2015 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2016 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002017
Tao Bao481bab82017-12-21 11:23:09 -08002018 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002019 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002020
Yifan Hong50e79542018-11-08 17:44:12 -08002021 if OPTIONS.retrofit_dynamic_partitions:
2022 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002023 target_file, target_info.get("super_block_devices").strip().split(),
2024 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002025 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002026 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2027
Tao Bao40b18822018-01-30 18:19:04 -08002028 # Generate payload.
2029 payload = Payload()
2030
2031 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002032 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002033 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002034 else:
2035 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002036 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002037
Tao Bao40b18822018-01-30 18:19:04 -08002038 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002039
Tao Bao40b18822018-01-30 18:19:04 -08002040 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002041 payload_signer = PayloadSigner()
2042 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002043
Tao Bao40b18822018-01-30 18:19:04 -08002044 # Write the payload into output zip.
2045 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002046
Tao Baof7140c02018-01-30 17:09:24 -08002047 # Generate and include the secondary payload that installs secondary images
2048 # (e.g. system_other.img).
2049 if OPTIONS.include_secondary:
2050 # We always include a full payload for the secondary slot, even when
2051 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002052 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2053 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002054 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002055 secondary_payload.Generate(secondary_target_file,
2056 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002057 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002058 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002059
Tianjie Xucfa86222016-03-07 16:31:19 -08002060 # If dm-verity is supported for the device, copy contents of care_map
2061 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002062 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002063 if (target_info.get("verity") == "true" or
2064 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002065 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2066 "META/" + x in target_zip.namelist()]
2067
2068 # Adds care_map if either the protobuf format or the plain text one exists.
2069 if care_map_list:
2070 care_map_name = care_map_list[0]
2071 care_map_data = target_zip.read("META/" + care_map_name)
2072 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002073 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002074 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002075 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002076 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002077 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002078
Tao Bao21803d32017-04-19 10:16:09 -07002079 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08002080
Yifan Hong9276cf02019-08-21 16:37:04 -07002081 CheckVintfIfTrebleEnabled(target_file, target_info)
2082
Tao Baofe5b69a2018-03-02 09:47:43 -08002083 # We haven't written the metadata entry yet, which will be handled in
2084 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002085 common.ZipClose(output_zip)
2086
Tao Bao85f16982018-03-08 16:28:33 -08002087 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2088 # all the info of the latter. However, system updaters and OTA servers need to
2089 # take time to switch to the new flag. We keep both of the flags for
2090 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002091 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002092 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002093 StreamingPropertyFiles(),
2094 )
2095 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002096
Tao Baoc098e9e2016-01-07 13:03:56 -08002097
Tao Baof0c4aa22018-04-30 20:29:30 -07002098def GenerateNonAbOtaPackage(target_file, output_file, source_file=None):
2099 """Generates a non-A/B OTA package."""
2100 # Sanity check the loaded info dicts first.
2101 if OPTIONS.info_dict.get("no_recovery") == "true":
2102 raise common.ExternalError(
2103 "--- target build has specified no recovery ---")
2104
2105 # Non-A/B OTAs rely on /cache partition to store temporary files.
2106 cache_size = OPTIONS.info_dict.get("cache_size")
2107 if cache_size is None:
2108 logger.warning("--- can't determine the cache partition size ---")
2109 OPTIONS.cache_size = cache_size
2110
2111 if OPTIONS.extra_script is not None:
2112 with open(OPTIONS.extra_script) as fp:
2113 OPTIONS.extra_script = fp.read()
2114
2115 if OPTIONS.extracted_input is not None:
2116 OPTIONS.input_tmp = OPTIONS.extracted_input
2117 else:
2118 logger.info("unzipping target target-files...")
2119 OPTIONS.input_tmp = common.UnzipTemp(target_file, UNZIP_PATTERN)
2120 OPTIONS.target_tmp = OPTIONS.input_tmp
2121
2122 # If the caller explicitly specified the device-specific extensions path via
2123 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2124 # is present in the target target_files. Otherwise, take the path of the file
2125 # from 'tool_extensions' in the info dict and look for that in the local
2126 # filesystem, relative to the current directory.
2127 if OPTIONS.device_specific is None:
2128 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2129 if os.path.exists(from_input):
2130 logger.info("(using device-specific extensions from target_files)")
2131 OPTIONS.device_specific = from_input
2132 else:
2133 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
2134
2135 if OPTIONS.device_specific is not None:
2136 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
2137
2138 # Generate a full OTA.
2139 if source_file is None:
2140 with zipfile.ZipFile(target_file) as input_zip:
2141 WriteFullOTAPackage(
2142 input_zip,
2143 output_file)
2144
2145 # Generate an incremental OTA.
2146 else:
2147 logger.info("unzipping source target-files...")
2148 OPTIONS.source_tmp = common.UnzipTemp(
2149 OPTIONS.incremental_source, UNZIP_PATTERN)
2150 with zipfile.ZipFile(target_file) as input_zip, \
2151 zipfile.ZipFile(source_file) as source_zip:
2152 WriteBlockIncrementalOTAPackage(
2153 input_zip,
2154 source_zip,
2155 output_file)
2156
2157
Doug Zongkereef39442009-04-02 12:14:19 -07002158def main(argv):
2159
2160 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002161 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002162 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002163 elif o in ("-i", "--incremental_from"):
2164 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002165 elif o == "--full_radio":
2166 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002167 elif o == "--full_bootloader":
2168 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002169 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002170 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002171 elif o == "--downgrade":
2172 OPTIONS.downgrade = True
2173 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002174 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002175 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002176 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002177 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002178 elif o == "--oem_no_mount":
2179 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002180 elif o in ("-e", "--extra_script"):
2181 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002182 elif o in ("-t", "--worker_threads"):
2183 if a.isdigit():
2184 OPTIONS.worker_threads = int(a)
2185 else:
2186 raise ValueError("Cannot parse value %r for option %r - only "
2187 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002188 elif o in ("-2", "--two_step"):
2189 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002190 elif o == "--include_secondary":
2191 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002192 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002193 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002194 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002195 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002196 elif o == "--block":
2197 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002198 elif o in ("-b", "--binary"):
2199 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002200 elif o == "--stash_threshold":
2201 try:
2202 OPTIONS.stash_threshold = float(a)
2203 except ValueError:
2204 raise ValueError("Cannot parse value %r for option %r - expecting "
2205 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002206 elif o == "--log_diff":
2207 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002208 elif o == "--payload_signer":
2209 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002210 elif o == "--payload_signer_args":
2211 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002212 elif o == "--payload_signer_key_size":
2213 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002214 elif o == "--extracted_input_target_files":
2215 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002216 elif o == "--skip_postinstall":
2217 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002218 elif o == "--retrofit_dynamic_partitions":
2219 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002220 elif o == "--skip_compatibility_check":
2221 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002222 elif o == "--output_metadata_path":
2223 OPTIONS.output_metadata_path = a
Tianjie Xu1b079832019-08-28 12:19:23 -07002224 elif o == "--disable_fec_computation":
2225 OPTIONS.disable_fec_computation = True
Doug Zongkereef39442009-04-02 12:14:19 -07002226 else:
2227 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002228 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002229
2230 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002231 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002232 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002233 "package_key=",
2234 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002235 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002236 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002237 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002238 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002239 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002240 "extra_script=",
2241 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002242 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002243 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002244 "no_signing",
2245 "block",
2246 "binary=",
2247 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002248 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002249 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002250 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002251 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002252 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002253 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002254 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002255 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002256 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002257 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002258 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002259 "output_metadata_path=",
Tianjie Xu1b079832019-08-28 12:19:23 -07002260 "disable_fec_computation",
Dan Albert8b72aef2015-03-23 19:13:21 -07002261 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002262
2263 if len(args) != 2:
2264 common.Usage(__doc__)
2265 sys.exit(1)
2266
Tao Bao32fcdab2018-10-12 10:30:39 -07002267 common.InitLogging()
2268
Tao Bao5d182562016-02-23 11:38:39 -08002269 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002270 # We should only allow downgrading incrementals (as opposed to full).
2271 # Otherwise the device may go back from arbitrary build with this full
2272 # OTA package.
2273 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002274 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002275
Tao Bao2db13852018-01-08 22:28:57 -08002276 # Load the build info dicts from the zip directly or the extracted input
2277 # directory. We don't need to unzip the entire target-files zips, because they
2278 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2279 # When loading the info dicts, we don't need to provide the second parameter
2280 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2281 # some properties with their actual paths, such as 'selinux_fc',
2282 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002283 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002284 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002285 else:
Tao Bao2db13852018-01-08 22:28:57 -08002286 with zipfile.ZipFile(args[0], 'r') as input_zip:
2287 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002288
Tao Bao32fcdab2018-10-12 10:30:39 -07002289 logger.info("--- target info ---")
2290 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002291
2292 # Load the source build dict if applicable.
2293 if OPTIONS.incremental_source is not None:
2294 OPTIONS.target_info_dict = OPTIONS.info_dict
2295 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2296 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2297
Tao Bao32fcdab2018-10-12 10:30:39 -07002298 logger.info("--- source info ---")
2299 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002300
2301 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002302 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2303
Yifan Hong50e79542018-11-08 17:44:12 -08002304 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002305 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002306 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002307 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2308 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002309 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2310 raise common.ExternalError(
2311 "Expect to generate incremental OTA for retrofitting dynamic "
2312 "partitions, but dynamic_partition_retrofit is not set in target "
2313 "build.")
2314 logger.info("Implicitly generating retrofit incremental OTA.")
2315 OPTIONS.retrofit_dynamic_partitions = True
2316
2317 # Skip postinstall for retrofitting dynamic partitions.
2318 if OPTIONS.retrofit_dynamic_partitions:
2319 OPTIONS.skip_postinstall = True
2320
Tao Baoc098e9e2016-01-07 13:03:56 -08002321 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2322
Christian Oderf63e2cd2017-05-01 22:30:15 +02002323 # Use the default key to sign the package if not specified with package_key.
2324 # package_keys are needed on ab_updates, so always define them if an
2325 # ab_update is getting created.
2326 if not OPTIONS.no_signing or ab_update:
2327 if OPTIONS.package_key is None:
2328 OPTIONS.package_key = OPTIONS.info_dict.get(
2329 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002330 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002331 # Get signing keys
2332 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2333
Tao Baoc098e9e2016-01-07 13:03:56 -08002334 if ab_update:
Tao Baof0c4aa22018-04-30 20:29:30 -07002335 GenerateAbOtaPackage(
Tao Baoc098e9e2016-01-07 13:03:56 -08002336 target_file=args[0],
2337 output_file=args[1],
2338 source_file=OPTIONS.incremental_source)
2339
Dan Willemsencea5cd22017-03-21 14:44:27 -07002340 else:
Tao Baof0c4aa22018-04-30 20:29:30 -07002341 GenerateNonAbOtaPackage(
2342 target_file=args[0],
2343 output_file=args[1],
2344 source_file=OPTIONS.incremental_source)
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002345
Tao Baof0c4aa22018-04-30 20:29:30 -07002346 # Post OTA generation works.
2347 if OPTIONS.incremental_source is not None and OPTIONS.log_diff:
2348 logger.info("Generating diff logs...")
2349 logger.info("Unzipping target-files for diffing...")
2350 target_dir = common.UnzipTemp(args[0], TARGET_DIFFING_UNZIP_PATTERN)
2351 source_dir = common.UnzipTemp(
2352 OPTIONS.incremental_source, TARGET_DIFFING_UNZIP_PATTERN)
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002353
Tao Baof0c4aa22018-04-30 20:29:30 -07002354 with open(OPTIONS.log_diff, 'w') as out_file:
2355 import target_files_diff
2356 target_files_diff.recursiveDiff(
2357 '', source_dir, target_dir, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002358
Tao Bao32fcdab2018-10-12 10:30:39 -07002359 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002360
2361
2362if __name__ == '__main__':
2363 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002364 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002365 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002366 except common.ExternalError:
2367 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002368 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002369 finally:
2370 common.Cleanup()