blob: 1a4f7e15b7de1b32062b40b1b32bba20abbc46bc [file] [log] [blame]
Doug Zongkereef39442009-04-02 12:14:19 -07001#!/usr/bin/env python
2#
3# Copyright (C) 2008 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
Tao Bao30df8b42018-04-23 15:32:53 -070018Given a target-files zipfile, produces an OTA package that installs that build.
19An incremental OTA is produced if -i is given, otherwise a full OTA is produced.
Doug Zongkereef39442009-04-02 12:14:19 -070020
Tao Bao30df8b42018-04-23 15:32:53 -070021Usage: ota_from_target_files [options] input_target_files output_ota_package
Doug Zongkereef39442009-04-02 12:14:19 -070022
Tao Bao30df8b42018-04-23 15:32:53 -070023Common options that apply to both of non-A/B and A/B OTAs
24
25 --downgrade
26 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070027 to an older one (e.g. downgrading from P preview back to O MR1).
28 "ota-downgrade=yes" will be set in the package metadata file. A data wipe
29 will always be enforced when using this flag, so "ota-wipe=yes" will also
30 be included in the metadata file. The update-binary in the source build
31 will be used in the OTA package, unless --binary flag is specified. Please
32 also check the comment for --override_timestamp below.
Tao Bao30df8b42018-04-23 15:32:53 -070033
34 -i (--incremental_from) <file>
35 Generate an incremental OTA using the given target-files zip as the
36 starting build.
37
38 -k (--package_key) <key>
39 Key to use to sign the package (default is the value of
40 default_system_dev_certificate from the input target-files's
Tao Bao59cf0c52019-06-25 10:04:24 -070041 META/misc_info.txt, or "build/make/target/product/security/testkey" if
42 that value is not specified).
Doug Zongkerafb32ea2011-09-22 10:28:04 -070043
44 For incremental OTAs, the default value is based on the source
45 target-file, not the target build.
Doug Zongkereef39442009-04-02 12:14:19 -070046
Tao Bao30df8b42018-04-23 15:32:53 -070047 --override_timestamp
48 Intentionally generate an incremental OTA that updates from a newer build
Tao Baofaa8e0b2018-04-12 14:31:43 -070049 to an older one (based on timestamp comparison), by setting the downgrade
50 flag in the package metadata. This differs from --downgrade flag, as we
51 don't enforce a data wipe with this flag. Because we know for sure this is
52 NOT an actual downgrade case, but two builds happen to be cut in a reverse
53 order (e.g. from two branches). A legit use case is that we cut a new
54 build C (after having A and B), but want to enfore an update path of A ->
55 C -> B. Specifying --downgrade may not help since that would enforce a
56 data wipe for C -> B update.
57
58 We used to set a fake timestamp in the package metadata for this flow. But
59 now we consolidate the two cases (i.e. an actual downgrade, or a downgrade
60 based on timestamp) with the same "ota-downgrade=yes" flag, with the
61 difference being whether "ota-wipe=yes" is set.
Doug Zongkereef39442009-04-02 12:14:19 -070062
Tao Bao30df8b42018-04-23 15:32:53 -070063 --wipe_user_data
64 Generate an OTA package that will wipe the user data partition when
65 installed.
66
Yifan Hong50e79542018-11-08 17:44:12 -080067 --retrofit_dynamic_partitions
68 Generates an OTA package that updates a device to support dynamic
69 partitions (default False). This flag is implied when generating
70 an incremental OTA where the base build does not support dynamic
71 partitions but the target build does. For A/B, when this flag is set,
72 --skip_postinstall is implied.
73
xunchangabfa2652019-02-19 16:27:10 -080074 --skip_compatibility_check
75 Skip adding the compatibility package to the generated OTA package.
76
xunchang1cfe2512019-02-19 14:14:48 -080077 --output_metadata_path
78 Write a copy of the metadata to a separate file. Therefore, users can
79 read the post build fingerprint without extracting the OTA package.
80
Tao Bao30df8b42018-04-23 15:32:53 -070081Non-A/B OTA specific options
82
83 -b (--binary) <file>
84 Use the given binary as the update-binary in the output package, instead
85 of the binary in the build's target_files. Use for development only.
86
87 --block
88 Generate a block-based OTA for non-A/B device. We have deprecated the
89 support for file-based OTA since O. Block-based OTA will be used by
90 default for all non-A/B devices. Keeping this flag here to not break
91 existing callers.
92
93 -e (--extra_script) <file>
94 Insert the contents of file at the end of the update script.
Tao Bao43078aa2015-04-21 14:32:35 -070095
leozwangaa6c1a12015-08-14 10:57:58 -070096 --full_bootloader
97 Similar to --full_radio. When generating an incremental OTA, always
98 include a full copy of bootloader image.
99
Tao Bao30df8b42018-04-23 15:32:53 -0700100 --full_radio
101 When generating an incremental OTA, always include a full copy of radio
102 image. This option is only meaningful when -i is specified, because a full
103 radio is always included in a full OTA if applicable.
Michael Runge63f01de2014-10-28 19:24:19 -0700104
Tao Bao30df8b42018-04-23 15:32:53 -0700105 --log_diff <file>
106 Generate a log file that shows the differences in the source and target
107 builds for an incremental package. This option is only meaningful when -i
108 is specified.
109
110 -o (--oem_settings) <main_file[,additional_files...]>
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800111 Comma seperated list of files used to specify the expected OEM-specific
Tao Bao481bab82017-12-21 11:23:09 -0800112 properties on the OEM partition of the intended device. Multiple expected
113 values can be used by providing multiple files. Only the first dict will
114 be used to compute fingerprint, while the rest will be used to assert
115 OEM-specific properties.
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800116
Tao Bao8608cde2016-02-25 19:49:55 -0800117 --oem_no_mount
Tao Bao30df8b42018-04-23 15:32:53 -0700118 For devices with OEM-specific properties but without an OEM partition, do
119 not mount the OEM partition in the updater-script. This should be very
120 rarely used, since it's expected to have a dedicated OEM partition for
121 OEM-specific properties. Only meaningful when -o is specified.
Tao Bao8608cde2016-02-25 19:49:55 -0800122
Tao Bao30df8b42018-04-23 15:32:53 -0700123 --stash_threshold <float>
124 Specify the threshold that will be used to compute the maximum allowed
125 stash size (defaults to 0.8).
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700126
Tao Bao30df8b42018-04-23 15:32:53 -0700127 -t (--worker_threads) <int>
128 Specify the number of worker-threads that will be used when generating
129 patches for incremental updates (defaults to 3).
Tao Bao3e6161a2017-02-28 11:48:48 -0800130
Tao Bao30df8b42018-04-23 15:32:53 -0700131 --verify
132 Verify the checksums of the updated system and vendor (if any) partitions.
133 Non-A/B incremental OTAs only.
Doug Zongker1c390a22009-05-14 19:06:36 -0700134
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800135 -2 (--two_step)
Tao Bao30df8b42018-04-23 15:32:53 -0700136 Generate a 'two-step' OTA package, where recovery is updated first, so
137 that any changes made to the system partition are done using the new
138 recovery (new kernel, etc.).
139
140A/B OTA specific options
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800141
Tao Baof7140c02018-01-30 17:09:24 -0800142 --include_secondary
143 Additionally include the payload for secondary slot images (default:
144 False). Only meaningful when generating A/B OTAs.
145
146 By default, an A/B OTA package doesn't contain the images for the
147 secondary slot (e.g. system_other.img). Specifying this flag allows
148 generating a separate payload that will install secondary slot images.
149
150 Such a package needs to be applied in a two-stage manner, with a reboot
151 in-between. During the first stage, the updater applies the primary
152 payload only. Upon finishing, it reboots the device into the newly updated
153 slot. It then continues to install the secondary payload to the inactive
154 slot, but without switching the active slot at the end (needs the matching
155 support in update_engine, i.e. SWITCH_SLOT_ON_REBOOT flag).
156
157 Due to the special install procedure, the secondary payload will be always
158 generated as a full payload.
159
Tao Baodea0f8b2016-06-20 17:55:06 -0700160 --payload_signer <signer>
161 Specify the signer when signing the payload and metadata for A/B OTAs.
162 By default (i.e. without this flag), it calls 'openssl pkeyutl' to sign
163 with the package private key. If the private key cannot be accessed
164 directly, a payload signer that knows how to do that should be specified.
165 The signer will be supplied with "-inkey <path_to_key>",
166 "-in <input_file>" and "-out <output_file>" parameters.
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700167
168 --payload_signer_args <args>
169 Specify the arguments needed for payload signer.
Tao Bao15a146a2018-02-21 16:06:59 -0800170
xunchang376cc7c2019-04-08 23:04:58 -0700171 --payload_signer_key_size <key_size>
172 Specify the key size in bytes of the payload signer.
173
Tao Bao15a146a2018-02-21 16:06:59 -0800174 --skip_postinstall
175 Skip the postinstall hooks when generating an A/B OTA package (default:
176 False). Note that this discards ALL the hooks, including non-optional
177 ones. Should only be used if caller knows it's safe to do so (e.g. all the
178 postinstall work is to dexopt apps and a data wipe will happen immediately
179 after). Only meaningful when generating A/B OTAs.
Doug Zongkereef39442009-04-02 12:14:19 -0700180"""
181
Tao Bao89fbb0f2017-01-10 10:47:58 -0800182from __future__ import print_function
183
Tao Bao32fcdab2018-10-12 10:30:39 -0700184import logging
Doug Zongkerfc44a512014-08-26 13:10:25 -0700185import multiprocessing
Tao Bao2dd1c482017-02-03 16:49:39 -0800186import os.path
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700187import shlex
Tao Bao15a146a2018-02-21 16:06:59 -0800188import shutil
Tao Bao85f16982018-03-08 16:28:33 -0800189import struct
Tao Bao481bab82017-12-21 11:23:09 -0800190import sys
Doug Zongkereef39442009-04-02 12:14:19 -0700191import tempfile
Doug Zongkereef39442009-04-02 12:14:19 -0700192import zipfile
193
194import common
Doug Zongkerc494d7c2009-06-18 08:43:44 -0700195import edify_generator
Tianjie Xu67c7cbb2018-08-30 00:32:07 -0700196import verity_utils
Doug Zongkereef39442009-04-02 12:14:19 -0700197
Tao Bao481bab82017-12-21 11:23:09 -0800198if sys.hexversion < 0x02070000:
199 print("Python 2.7 or newer is required.", file=sys.stderr)
200 sys.exit(1)
201
Tao Bao32fcdab2018-10-12 10:30:39 -0700202logger = logging.getLogger(__name__)
Tao Bao481bab82017-12-21 11:23:09 -0800203
Doug Zongkereef39442009-04-02 12:14:19 -0700204OPTIONS = common.OPTIONS
Doug Zongkerafb32ea2011-09-22 10:28:04 -0700205OPTIONS.package_key = None
Doug Zongkereef39442009-04-02 12:14:19 -0700206OPTIONS.incremental_source = None
Michael Runge63f01de2014-10-28 19:24:19 -0700207OPTIONS.verify = False
Doug Zongkereef39442009-04-02 12:14:19 -0700208OPTIONS.patch_threshold = 0.95
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700209OPTIONS.wipe_user_data = False
Tao Bao5d182562016-02-23 11:38:39 -0800210OPTIONS.downgrade = False
Doug Zongker1c390a22009-05-14 19:06:36 -0700211OPTIONS.extra_script = None
Doug Zongkerfc44a512014-08-26 13:10:25 -0700212OPTIONS.worker_threads = multiprocessing.cpu_count() // 2
213if OPTIONS.worker_threads == 0:
214 OPTIONS.worker_threads = 1
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800215OPTIONS.two_step = False
Tao Baof7140c02018-01-30 17:09:24 -0800216OPTIONS.include_secondary = False
Takeshi Kanemotoe153b342013-11-14 17:20:50 +0900217OPTIONS.no_signing = False
Tao Bao457cbf62017-03-06 09:56:01 -0800218OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -0800219OPTIONS.updater_binary = None
Michael Runge6e836112014-04-15 17:40:21 -0700220OPTIONS.oem_source = None
Tao Bao8608cde2016-02-25 19:49:55 -0800221OPTIONS.oem_no_mount = False
Tao Bao43078aa2015-04-21 14:32:35 -0700222OPTIONS.full_radio = False
leozwangaa6c1a12015-08-14 10:57:58 -0700223OPTIONS.full_bootloader = False
Tao Baod47d8e12015-05-21 14:09:49 -0700224# Stash size cannot exceed cache_size * threshold.
225OPTIONS.cache_size = None
226OPTIONS.stash_threshold = 0.8
Tao Baod62c6032015-11-30 09:40:20 -0800227OPTIONS.log_diff = None
Tao Baodea0f8b2016-06-20 17:55:06 -0700228OPTIONS.payload_signer = None
Baligh Uddin2abbbd02016-06-22 12:14:16 -0700229OPTIONS.payload_signer_args = []
xunchang376cc7c2019-04-08 23:04:58 -0700230OPTIONS.payload_signer_key_size = None
Tao Bao5f8ff932017-03-21 22:35:00 -0700231OPTIONS.extracted_input = None
Christian Oderf63e2cd2017-05-01 22:30:15 +0200232OPTIONS.key_passwords = []
Tao Bao15a146a2018-02-21 16:06:59 -0800233OPTIONS.skip_postinstall = False
Yifan Hong50e79542018-11-08 17:44:12 -0800234OPTIONS.retrofit_dynamic_partitions = False
xunchangabfa2652019-02-19 16:27:10 -0800235OPTIONS.skip_compatibility_check = False
xunchang1cfe2512019-02-19 14:14:48 -0800236OPTIONS.output_metadata_path = None
Tao Bao15a146a2018-02-21 16:06:59 -0800237
Tao Bao8dcf7382015-05-21 14:09:49 -0700238
Tao Bao2dd1c482017-02-03 16:49:39 -0800239METADATA_NAME = 'META-INF/com/android/metadata'
Tao Bao15a146a2018-02-21 16:06:59 -0800240POSTINSTALL_CONFIG = 'META/postinstall_config.txt'
Yifan Hong50e79542018-11-08 17:44:12 -0800241DYNAMIC_PARTITION_INFO = 'META/dynamic_partitions_info.txt'
Yifan Hongb433eba2019-03-06 12:42:53 -0800242AB_PARTITIONS = 'META/ab_partitions.txt'
Tao Bao04808502019-07-25 23:11:41 -0700243UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Yifan Hongb433eba2019-03-06 12:42:53 -0800244RETROFIT_DAP_UNZIP_PATTERN = ['OTA/super_*.img', AB_PARTITIONS]
Tao Bao6b0b2f92017-03-05 11:38:11 -0800245
Tao Bao2dd1c482017-02-03 16:49:39 -0800246
Tao Bao481bab82017-12-21 11:23:09 -0800247class BuildInfo(object):
248 """A class that holds the information for a given build.
249
250 This class wraps up the property querying for a given source or target build.
251 It abstracts away the logic of handling OEM-specific properties, and caches
252 the commonly used properties such as fingerprint.
253
254 There are two types of info dicts: a) build-time info dict, which is generated
255 at build time (i.e. included in a target_files zip); b) OEM info dict that is
256 specified at package generation time (via command line argument
257 '--oem_settings'). If a build doesn't use OEM-specific properties (i.e. not
258 having "oem_fingerprint_properties" in build-time info dict), all the queries
259 would be answered based on build-time info dict only. Otherwise if using
260 OEM-specific properties, some of them will be calculated from two info dicts.
261
262 Users can query properties similarly as using a dict() (e.g. info['fstab']),
263 or to query build properties via GetBuildProp() or GetVendorBuildProp().
264
265 Attributes:
266 info_dict: The build-time info dict.
267 is_ab: Whether it's a build that uses A/B OTA.
268 oem_dicts: A list of OEM dicts.
269 oem_props: A list of OEM properties that should be read from OEM dicts; None
270 if the build doesn't use any OEM-specific property.
271 fingerprint: The fingerprint of the build, which would be calculated based
272 on OEM properties if applicable.
273 device: The device name, which could come from OEM dicts if applicable.
274 """
275
Steven Laver9e73e822019-01-29 20:20:08 -0800276 _RO_PRODUCT_RESOLVE_PROPS = ["ro.product.brand", "ro.product.device",
277 "ro.product.manufacturer", "ro.product.model",
278 "ro.product.name"]
Justin Yun6151e3f2019-06-25 15:58:13 +0900279 _RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER = ["product", "odm", "vendor",
280 "system_ext", "system"]
Steven Laver9e73e822019-01-29 20:20:08 -0800281
Tao Bao481bab82017-12-21 11:23:09 -0800282 def __init__(self, info_dict, oem_dicts):
283 """Initializes a BuildInfo instance with the given dicts.
284
Tao Bao667c7532018-07-06 10:13:59 -0700285 Note that it only wraps up the given dicts, without making copies.
286
Tao Bao481bab82017-12-21 11:23:09 -0800287 Arguments:
288 info_dict: The build-time info dict.
289 oem_dicts: A list of OEM dicts (which is parsed from --oem_settings). Note
290 that it always uses the first dict to calculate the fingerprint or the
291 device name. The rest would be used for asserting OEM properties only
Tao Bao667c7532018-07-06 10:13:59 -0700292 (e.g. one package can be installed on one of these devices).
Tao Bao481bab82017-12-21 11:23:09 -0800293 """
294 self.info_dict = info_dict
295 self.oem_dicts = oem_dicts
296
297 self._is_ab = info_dict.get("ab_update") == "true"
298 self._oem_props = info_dict.get("oem_fingerprint_properties")
299
300 if self._oem_props:
301 assert oem_dicts, "OEM source required for this build"
302
303 # These two should be computed only after setting self._oem_props.
304 self._device = self.GetOemProperty("ro.product.device")
305 self._fingerprint = self.CalculateFingerprint()
306
307 @property
308 def is_ab(self):
309 return self._is_ab
310
311 @property
312 def device(self):
313 return self._device
314
315 @property
316 def fingerprint(self):
317 return self._fingerprint
318
319 @property
Tao Baoea6cbd02018-09-05 13:06:37 -0700320 def vendor_fingerprint(self):
Yifan Hong51d37562019-04-23 17:06:46 -0700321 return self._fingerprint_of("vendor")
322
323 @property
324 def product_fingerprint(self):
325 return self._fingerprint_of("product")
326
327 @property
328 def odm_fingerprint(self):
329 return self._fingerprint_of("odm")
330
331 def _fingerprint_of(self, partition):
332 if partition + ".build.prop" not in self.info_dict:
Tao Baoea6cbd02018-09-05 13:06:37 -0700333 return None
Yifan Hong51d37562019-04-23 17:06:46 -0700334 build_prop = self.info_dict[partition + ".build.prop"]
335 if "ro." + partition + ".build.fingerprint" in build_prop:
336 return build_prop["ro." + partition + ".build.fingerprint"]
337 if "ro." + partition + ".build.thumbprint" in build_prop:
338 return build_prop["ro." + partition + ".build.thumbprint"]
Tao Baoea6cbd02018-09-05 13:06:37 -0700339 return None
340
341 @property
Tao Bao481bab82017-12-21 11:23:09 -0800342 def oem_props(self):
343 return self._oem_props
344
345 def __getitem__(self, key):
346 return self.info_dict[key]
347
Tao Bao667c7532018-07-06 10:13:59 -0700348 def __setitem__(self, key, value):
349 self.info_dict[key] = value
350
Tao Bao481bab82017-12-21 11:23:09 -0800351 def get(self, key, default=None):
352 return self.info_dict.get(key, default)
353
Tao Bao667c7532018-07-06 10:13:59 -0700354 def items(self):
355 return self.info_dict.items()
356
Tao Bao481bab82017-12-21 11:23:09 -0800357 def GetBuildProp(self, prop):
358 """Returns the inquired build property."""
Steven Laver9e73e822019-01-29 20:20:08 -0800359 if prop in BuildInfo._RO_PRODUCT_RESOLVE_PROPS:
360 return self._ResolveRoProductBuildProp(prop)
361
Tao Bao481bab82017-12-21 11:23:09 -0800362 try:
363 return self.info_dict.get("build.prop", {})[prop]
364 except KeyError:
365 raise common.ExternalError("couldn't find %s in build.prop" % (prop,))
366
Steven Laver9e73e822019-01-29 20:20:08 -0800367 def _ResolveRoProductBuildProp(self, prop):
368 """Resolves the inquired ro.product.* build property"""
369 prop_val = self.info_dict.get("build.prop", {}).get(prop)
370 if prop_val:
371 return prop_val
372
373 source_order_val = self.info_dict.get("build.prop", {}).get(
Tao Bao59cf0c52019-06-25 10:04:24 -0700374 "ro.product.property_source_order")
Steven Laver9e73e822019-01-29 20:20:08 -0800375 if source_order_val:
376 source_order = source_order_val.split(",")
377 else:
378 source_order = BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
379
380 # Check that all sources in ro.product.property_source_order are valid
381 if any([x not in BuildInfo._RO_PRODUCT_PROPS_DEFAULT_SOURCE_ORDER
382 for x in source_order]):
383 raise common.ExternalError(
Tao Bao59cf0c52019-06-25 10:04:24 -0700384 "Invalid ro.product.property_source_order '{}'".format(source_order))
Steven Laver9e73e822019-01-29 20:20:08 -0800385
386 for source in source_order:
Tao Bao59cf0c52019-06-25 10:04:24 -0700387 source_prop = prop.replace(
388 "ro.product", "ro.product.{}".format(source), 1)
389 prop_val = self.info_dict.get(
390 "{}.build.prop".format(source), {}).get(source_prop)
Steven Laver9e73e822019-01-29 20:20:08 -0800391 if prop_val:
392 return prop_val
393
394 raise common.ExternalError("couldn't resolve {}".format(prop))
395
Tao Bao481bab82017-12-21 11:23:09 -0800396 def GetVendorBuildProp(self, prop):
397 """Returns the inquired vendor build property."""
398 try:
399 return self.info_dict.get("vendor.build.prop", {})[prop]
400 except KeyError:
401 raise common.ExternalError(
402 "couldn't find %s in vendor.build.prop" % (prop,))
403
404 def GetOemProperty(self, key):
405 if self.oem_props is not None and key in self.oem_props:
406 return self.oem_dicts[0][key]
407 return self.GetBuildProp(key)
408
409 def CalculateFingerprint(self):
410 if self.oem_props is None:
Steven Laver9e73e822019-01-29 20:20:08 -0800411 try:
412 return self.GetBuildProp("ro.build.fingerprint")
413 except common.ExternalError:
414 return "{}/{}/{}:{}/{}/{}:{}/{}".format(
Tao Bao59cf0c52019-06-25 10:04:24 -0700415 self.GetBuildProp("ro.product.brand"),
416 self.GetBuildProp("ro.product.name"),
417 self.GetBuildProp("ro.product.device"),
418 self.GetBuildProp("ro.build.version.release"),
419 self.GetBuildProp("ro.build.id"),
420 self.GetBuildProp("ro.build.version.incremental"),
421 self.GetBuildProp("ro.build.type"),
422 self.GetBuildProp("ro.build.tags"))
Tao Bao481bab82017-12-21 11:23:09 -0800423 return "%s/%s/%s:%s" % (
424 self.GetOemProperty("ro.product.brand"),
425 self.GetOemProperty("ro.product.name"),
426 self.GetOemProperty("ro.product.device"),
427 self.GetBuildProp("ro.build.thumbprint"))
428
429 def WriteMountOemScript(self, script):
430 assert self.oem_props is not None
431 recovery_mount_options = self.info_dict.get("recovery_mount_options")
432 script.Mount("/oem", recovery_mount_options)
433
434 def WriteDeviceAssertions(self, script, oem_no_mount):
435 # Read the property directly if not using OEM properties.
436 if not self.oem_props:
437 script.AssertDevice(self.device)
438 return
439
440 # Otherwise assert OEM properties.
441 if not self.oem_dicts:
442 raise common.ExternalError(
443 "No OEM file provided to answer expected assertions")
444
445 for prop in self.oem_props.split():
446 values = []
447 for oem_dict in self.oem_dicts:
448 if prop in oem_dict:
449 values.append(oem_dict[prop])
450 if not values:
451 raise common.ExternalError(
452 "The OEM file is missing the property %s" % (prop,))
453 script.AssertOemProperty(prop, values, oem_no_mount)
454
455
Tao Baofabe0832018-01-17 15:52:28 -0800456class PayloadSigner(object):
457 """A class that wraps the payload signing works.
458
459 When generating a Payload, hashes of the payload and metadata files will be
460 signed with the device key, either by calling an external payload signer or
461 by calling openssl with the package key. This class provides a unified
462 interface, so that callers can just call PayloadSigner.Sign().
463
464 If an external payload signer has been specified (OPTIONS.payload_signer), it
465 calls the signer with the provided args (OPTIONS.payload_signer_args). Note
466 that the signing key should be provided as part of the payload_signer_args.
467 Otherwise without an external signer, it uses the package key
468 (OPTIONS.package_key) and calls openssl for the signing works.
469 """
470
471 def __init__(self):
472 if OPTIONS.payload_signer is None:
473 # Prepare the payload signing key.
474 private_key = OPTIONS.package_key + OPTIONS.private_key_suffix
475 pw = OPTIONS.key_passwords[OPTIONS.package_key]
476
477 cmd = ["openssl", "pkcs8", "-in", private_key, "-inform", "DER"]
478 cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
479 signing_key = common.MakeTempFile(prefix="key-", suffix=".key")
480 cmd.extend(["-out", signing_key])
Tao Baobec89c12018-10-15 11:53:28 -0700481 common.RunAndCheckOutput(cmd, verbose=False)
Tao Baofabe0832018-01-17 15:52:28 -0800482
483 self.signer = "openssl"
484 self.signer_args = ["pkeyutl", "-sign", "-inkey", signing_key,
485 "-pkeyopt", "digest:sha256"]
xunchang376cc7c2019-04-08 23:04:58 -0700486 self.key_size = self._GetKeySizeInBytes(signing_key)
Tao Baofabe0832018-01-17 15:52:28 -0800487 else:
488 self.signer = OPTIONS.payload_signer
489 self.signer_args = OPTIONS.payload_signer_args
xunchang376cc7c2019-04-08 23:04:58 -0700490 if OPTIONS.payload_signer_key_size:
491 self.key_size = int(OPTIONS.payload_signer_key_size)
492 assert self.key_size == 256 or self.key_size == 512, \
493 "Unsupported key size {}".format(OPTIONS.payload_signer_key_size)
494 else:
495 self.key_size = 256
496
497 @staticmethod
498 def _GetKeySizeInBytes(signing_key):
499 modulus_file = common.MakeTempFile(prefix="modulus-")
500 cmd = ["openssl", "rsa", "-inform", "PEM", "-in", signing_key, "-modulus",
501 "-noout", "-out", modulus_file]
502 common.RunAndCheckOutput(cmd, verbose=False)
503
504 with open(modulus_file) as f:
505 modulus_string = f.read()
506 # The modulus string has the format "Modulus=$data", where $data is the
507 # concatenation of hex dump of the modulus.
508 MODULUS_PREFIX = "Modulus="
509 assert modulus_string.startswith(MODULUS_PREFIX)
510 modulus_string = modulus_string[len(MODULUS_PREFIX):]
Tao Bao59cf0c52019-06-25 10:04:24 -0700511 key_size = len(modulus_string) // 2
xunchang376cc7c2019-04-08 23:04:58 -0700512 assert key_size == 256 or key_size == 512, \
513 "Unsupported key size {}".format(key_size)
514 return key_size
Tao Baofabe0832018-01-17 15:52:28 -0800515
516 def Sign(self, in_file):
517 """Signs the given input file. Returns the output filename."""
518 out_file = common.MakeTempFile(prefix="signed-", suffix=".bin")
519 cmd = [self.signer] + self.signer_args + ['-in', in_file, '-out', out_file]
Tao Bao718faed2019-08-02 13:24:19 -0700520 common.RunAndCheckOutput(cmd)
Tao Baofabe0832018-01-17 15:52:28 -0800521 return out_file
522
523
Tao Bao40b18822018-01-30 18:19:04 -0800524class Payload(object):
525 """Manages the creation and the signing of an A/B OTA Payload."""
526
527 PAYLOAD_BIN = 'payload.bin'
528 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
Tao Baof7140c02018-01-30 17:09:24 -0800529 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
530 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
Tao Bao40b18822018-01-30 18:19:04 -0800531
Tao Bao667ff572018-02-10 00:02:40 -0800532 def __init__(self, secondary=False):
533 """Initializes a Payload instance.
534
535 Args:
536 secondary: Whether it's generating a secondary payload (default: False).
537 """
Tao Bao40b18822018-01-30 18:19:04 -0800538 self.payload_file = None
539 self.payload_properties = None
Tao Bao667ff572018-02-10 00:02:40 -0800540 self.secondary = secondary
Tao Bao40b18822018-01-30 18:19:04 -0800541
Tao Bao718faed2019-08-02 13:24:19 -0700542 def _Run(self, cmd):
543 # Don't pipe (buffer) the output if verbose is set. Let
544 # brillo_update_payload write to stdout/stderr directly, so its progress can
545 # be monitored.
546 if OPTIONS.verbose:
547 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
548 else:
549 common.RunAndCheckOutput(cmd)
550
Tao Bao40b18822018-01-30 18:19:04 -0800551 def Generate(self, target_file, source_file=None, additional_args=None):
552 """Generates a payload from the given target-files zip(s).
553
554 Args:
555 target_file: The filename of the target build target-files zip.
556 source_file: The filename of the source build target-files zip; or None if
557 generating a full OTA.
558 additional_args: A list of additional args that should be passed to
559 brillo_update_payload script; or None.
560 """
561 if additional_args is None:
562 additional_args = []
563
564 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
565 cmd = ["brillo_update_payload", "generate",
566 "--payload", payload_file,
567 "--target_image", target_file]
568 if source_file is not None:
569 cmd.extend(["--source_image", source_file])
570 cmd.extend(additional_args)
Tao Bao718faed2019-08-02 13:24:19 -0700571 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800572
573 self.payload_file = payload_file
574 self.payload_properties = None
575
576 def Sign(self, payload_signer):
577 """Generates and signs the hashes of the payload and metadata.
578
579 Args:
580 payload_signer: A PayloadSigner() instance that serves the signing work.
581
582 Raises:
583 AssertionError: On any failure when calling brillo_update_payload script.
584 """
585 assert isinstance(payload_signer, PayloadSigner)
586
587 # 1. Generate hashes of the payload and metadata files.
588 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
589 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
590 cmd = ["brillo_update_payload", "hash",
591 "--unsigned_payload", self.payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700592 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800593 "--metadata_hash_file", metadata_sig_file,
594 "--payload_hash_file", payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700595 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800596
597 # 2. Sign the hashes.
598 signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
599 signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
600
601 # 3. Insert the signatures back into the payload file.
602 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
603 suffix=".bin")
604 cmd = ["brillo_update_payload", "sign",
605 "--unsigned_payload", self.payload_file,
606 "--payload", signed_payload_file,
xunchang376cc7c2019-04-08 23:04:58 -0700607 "--signature_size", str(payload_signer.key_size),
Tao Bao40b18822018-01-30 18:19:04 -0800608 "--metadata_signature_file", signed_metadata_sig_file,
609 "--payload_signature_file", signed_payload_sig_file]
Tao Bao718faed2019-08-02 13:24:19 -0700610 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800611
612 # 4. Dump the signed payload properties.
613 properties_file = common.MakeTempFile(prefix="payload-properties-",
614 suffix=".txt")
615 cmd = ["brillo_update_payload", "properties",
616 "--payload", signed_payload_file,
617 "--properties_file", properties_file]
Tao Bao718faed2019-08-02 13:24:19 -0700618 self._Run(cmd)
Tao Bao40b18822018-01-30 18:19:04 -0800619
Tao Bao667ff572018-02-10 00:02:40 -0800620 if self.secondary:
621 with open(properties_file, "a") as f:
622 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
623
Tao Bao40b18822018-01-30 18:19:04 -0800624 if OPTIONS.wipe_user_data:
625 with open(properties_file, "a") as f:
626 f.write("POWERWASH=1\n")
627
628 self.payload_file = signed_payload_file
629 self.payload_properties = properties_file
630
Tao Bao667ff572018-02-10 00:02:40 -0800631 def WriteToZip(self, output_zip):
Tao Bao40b18822018-01-30 18:19:04 -0800632 """Writes the payload to the given zip.
633
634 Args:
635 output_zip: The output ZipFile instance.
636 """
637 assert self.payload_file is not None
638 assert self.payload_properties is not None
639
Tao Bao667ff572018-02-10 00:02:40 -0800640 if self.secondary:
Tao Baof7140c02018-01-30 17:09:24 -0800641 payload_arcname = Payload.SECONDARY_PAYLOAD_BIN
642 payload_properties_arcname = Payload.SECONDARY_PAYLOAD_PROPERTIES_TXT
643 else:
644 payload_arcname = Payload.PAYLOAD_BIN
645 payload_properties_arcname = Payload.PAYLOAD_PROPERTIES_TXT
646
Tao Bao40b18822018-01-30 18:19:04 -0800647 # Add the signed payload file and properties into the zip. In order to
648 # support streaming, we pack them as ZIP_STORED. So these entries can be
649 # read directly with the offset and length pairs.
Tao Baof7140c02018-01-30 17:09:24 -0800650 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800651 compress_type=zipfile.ZIP_STORED)
652 common.ZipWrite(output_zip, self.payload_properties,
Tao Baof7140c02018-01-30 17:09:24 -0800653 arcname=payload_properties_arcname,
Tao Bao40b18822018-01-30 18:19:04 -0800654 compress_type=zipfile.ZIP_STORED)
655
656
Doug Zongkereef39442009-04-02 12:14:19 -0700657def SignOutput(temp_zip_name, output_zip_name):
Christian Oderf63e2cd2017-05-01 22:30:15 +0200658 pw = OPTIONS.key_passwords[OPTIONS.package_key]
Doug Zongkereef39442009-04-02 12:14:19 -0700659
Doug Zongker951495f2009-08-14 12:44:19 -0700660 common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
661 whole_file=True)
Doug Zongkereef39442009-04-02 12:14:19 -0700662
663
Tao Bao481bab82017-12-21 11:23:09 -0800664def _LoadOemDicts(oem_source):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800665 """Returns the list of loaded OEM properties dict."""
Tao Bao481bab82017-12-21 11:23:09 -0800666 if not oem_source:
667 return None
668
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800669 oem_dicts = []
Tao Bao481bab82017-12-21 11:23:09 -0800670 for oem_file in oem_source:
671 with open(oem_file) as fp:
672 oem_dicts.append(common.LoadDictionaryFromLines(fp.readlines()))
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -0800673 return oem_dicts
Doug Zongkereef39442009-04-02 12:14:19 -0700674
Doug Zongkereef39442009-04-02 12:14:19 -0700675
Tao Baod42e97e2016-11-30 12:11:57 -0800676def _WriteRecoveryImageToBoot(script, output_zip):
677 """Find and write recovery image to /boot in two-step OTA.
678
679 In two-step OTAs, we write recovery image to /boot as the first step so that
680 we can reboot to there and install a new recovery image to /recovery.
681 A special "recovery-two-step.img" will be preferred, which encodes the correct
682 path of "/boot". Otherwise the device may show "device is corrupt" message
683 when booting into /boot.
684
685 Fall back to using the regular recovery.img if the two-step recovery image
686 doesn't exist. Note that rebuilding the special image at this point may be
687 infeasible, because we don't have the desired boot signer and keys when
688 calling ota_from_target_files.py.
689 """
690
691 recovery_two_step_img_name = "recovery-two-step.img"
692 recovery_two_step_img_path = os.path.join(
Tao Bao04808502019-07-25 23:11:41 -0700693 OPTIONS.input_tmp, "OTA", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800694 if os.path.exists(recovery_two_step_img_path):
Tao Bao04808502019-07-25 23:11:41 -0700695 common.ZipWrite(
696 output_zip,
697 recovery_two_step_img_path,
698 arcname=recovery_two_step_img_name)
Tao Bao32fcdab2018-10-12 10:30:39 -0700699 logger.info(
700 "two-step package: using %s in stage 1/3", recovery_two_step_img_name)
Tao Baod42e97e2016-11-30 12:11:57 -0800701 script.WriteRawImage("/boot", recovery_two_step_img_name)
702 else:
Tao Bao32fcdab2018-10-12 10:30:39 -0700703 logger.info("two-step package: using recovery.img in stage 1/3")
Tao Baod42e97e2016-11-30 12:11:57 -0800704 # The "recovery.img" entry has been written into package earlier.
705 script.WriteRawImage("/boot", "recovery.img")
706
707
Doug Zongkerc9253822014-02-04 12:17:58 -0800708def HasRecoveryPatch(target_files_zip):
Tao Baof2cffbd2015-07-22 12:33:18 -0700709 namelist = [name for name in target_files_zip.namelist()]
710 return ("SYSTEM/recovery-from-boot.p" in namelist or
711 "SYSTEM/etc/recovery.img" in namelist)
Doug Zongker73ef8252009-07-23 15:12:53 -0700712
Tao Bao457cbf62017-03-06 09:56:01 -0800713
Yifan Hong51d37562019-04-23 17:06:46 -0700714def HasPartition(target_files_zip, partition):
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700715 try:
Yifan Hong51d37562019-04-23 17:06:46 -0700716 target_files_zip.getinfo(partition.upper() + "/")
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700717 return True
718 except KeyError:
719 return False
720
Tao Bao457cbf62017-03-06 09:56:01 -0800721
Yifan Hong51d37562019-04-23 17:06:46 -0700722def HasVendorPartition(target_files_zip):
723 return HasPartition(target_files_zip, "vendor")
724
725
726def HasProductPartition(target_files_zip):
727 return HasPartition(target_files_zip, "product")
728
729
730def HasOdmPartition(target_files_zip):
731 return HasPartition(target_files_zip, "odm")
732
733
Tao Bao481bab82017-12-21 11:23:09 -0800734def HasTrebleEnabled(target_files_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700735 return (HasVendorPartition(target_files_zip) and
Tao Bao481bab82017-12-21 11:23:09 -0800736 target_info.GetBuildProp("ro.treble.enabled") == "true")
Tao Baobcd1d162017-08-26 13:10:26 -0700737
738
Tao Bao481bab82017-12-21 11:23:09 -0800739def WriteFingerprintAssertion(script, target_info, source_info):
740 source_oem_props = source_info.oem_props
741 target_oem_props = target_info.oem_props
Michael Runge6e836112014-04-15 17:40:21 -0700742
Tao Bao481bab82017-12-21 11:23:09 -0800743 if source_oem_props is None and target_oem_props is None:
744 script.AssertSomeFingerprint(
745 source_info.fingerprint, target_info.fingerprint)
746 elif source_oem_props is not None and target_oem_props is not None:
747 script.AssertSomeThumbprint(
748 target_info.GetBuildProp("ro.build.thumbprint"),
749 source_info.GetBuildProp("ro.build.thumbprint"))
750 elif source_oem_props is None and target_oem_props is not None:
751 script.AssertFingerprintOrThumbprint(
752 source_info.fingerprint,
753 target_info.GetBuildProp("ro.build.thumbprint"))
754 else:
755 script.AssertFingerprintOrThumbprint(
756 target_info.fingerprint,
757 source_info.GetBuildProp("ro.build.thumbprint"))
Doug Zongker73ef8252009-07-23 15:12:53 -0700758
Doug Zongkerfc44a512014-08-26 13:10:25 -0700759
Tao Bao481bab82017-12-21 11:23:09 -0800760def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info,
761 source_info=None):
Tao Baobcd1d162017-08-26 13:10:26 -0700762 """Adds compatibility info into the output zip if it's Treble-enabled target.
Tao Bao21803d32017-04-19 10:16:09 -0700763
764 Metadata used for on-device compatibility verification is retrieved from
765 target_zip then added to compatibility.zip which is added to the output_zip
766 archive.
767
Tao Baobcd1d162017-08-26 13:10:26 -0700768 Compatibility archive should only be included for devices that have enabled
769 Treble support.
Tao Bao21803d32017-04-19 10:16:09 -0700770
771 Args:
772 target_zip: Zip file containing the source files to be included for OTA.
773 output_zip: Zip file that will be sent for OTA.
Tao Bao481bab82017-12-21 11:23:09 -0800774 target_info: The BuildInfo instance that holds the target build info.
775 source_info: The BuildInfo instance that holds the source build info, if
776 generating an incremental OTA; None otherwise.
Tao Bao21803d32017-04-19 10:16:09 -0700777 """
778
Yifan Hong51d37562019-04-23 17:06:46 -0700779 def AddCompatibilityArchive(framework_updated, device_updated):
780 """Adds compatibility info based on update status of both sides of Treble
781 boundary.
Tao Bao21803d32017-04-19 10:16:09 -0700782
Tao Baobcd1d162017-08-26 13:10:26 -0700783 Args:
Yifan Hong51d37562019-04-23 17:06:46 -0700784 framework_updated: If True, the system / product image will be updated
785 and therefore their metadata should be included.
786 device_updated: If True, the vendor / odm image will be updated and
787 therefore their metadata should be included.
Tao Baobcd1d162017-08-26 13:10:26 -0700788 """
789 # Determine what metadata we need. Files are names relative to META/.
790 compatibility_files = []
Yifan Hong51d37562019-04-23 17:06:46 -0700791 device_metadata = ("vendor_manifest.xml", "vendor_matrix.xml")
792 framework_metadata = ("system_manifest.xml", "system_matrix.xml")
793 if device_updated:
794 compatibility_files += device_metadata
795 if framework_updated:
796 compatibility_files += framework_metadata
Tao Bao21803d32017-04-19 10:16:09 -0700797
Tao Baobcd1d162017-08-26 13:10:26 -0700798 # Create new archive.
799 compatibility_archive = tempfile.NamedTemporaryFile()
Tao Bao481bab82017-12-21 11:23:09 -0800800 compatibility_archive_zip = zipfile.ZipFile(
801 compatibility_archive, "w", compression=zipfile.ZIP_DEFLATED)
Tao Bao21803d32017-04-19 10:16:09 -0700802
Tao Baobcd1d162017-08-26 13:10:26 -0700803 # Add metadata.
804 for file_name in compatibility_files:
805 target_file_name = "META/" + file_name
Tao Bao21803d32017-04-19 10:16:09 -0700806
Tao Baobcd1d162017-08-26 13:10:26 -0700807 if target_file_name in target_zip.namelist():
808 data = target_zip.read(target_file_name)
809 common.ZipWriteStr(compatibility_archive_zip, file_name, data)
Tao Bao21803d32017-04-19 10:16:09 -0700810
Tao Baobcd1d162017-08-26 13:10:26 -0700811 # Ensure files are written before we copy into output_zip.
812 compatibility_archive_zip.close()
813
814 # Only add the archive if we have any compatibility info.
815 if compatibility_archive_zip.namelist():
816 common.ZipWrite(output_zip, compatibility_archive.name,
817 arcname="compatibility.zip",
818 compress_type=zipfile.ZIP_STORED)
819
Yifan Hong51d37562019-04-23 17:06:46 -0700820 def FingerprintChanged(source_fp, target_fp):
821 if source_fp is None or target_fp is None:
822 return True
823 return source_fp != target_fp
824
Tao Baobcd1d162017-08-26 13:10:26 -0700825 # Will only proceed if the target has enabled the Treble support (as well as
826 # having a /vendor partition).
Tao Bao481bab82017-12-21 11:23:09 -0800827 if not HasTrebleEnabled(target_zip, target_info):
Tao Baobcd1d162017-08-26 13:10:26 -0700828 return
829
xunchangabfa2652019-02-19 16:27:10 -0800830 # Skip adding the compatibility package as a workaround for b/114240221. The
831 # compatibility will always fail on devices without qualified kernels.
832 if OPTIONS.skip_compatibility_check:
833 return
834
Yifan Hong51d37562019-04-23 17:06:46 -0700835 # Full OTA carries the info for system/vendor/product/odm
Tao Bao481bab82017-12-21 11:23:09 -0800836 if source_info is None:
Tao Baobcd1d162017-08-26 13:10:26 -0700837 AddCompatibilityArchive(True, True)
838 return
839
Tao Bao481bab82017-12-21 11:23:09 -0800840 source_fp = source_info.fingerprint
841 target_fp = target_info.fingerprint
Tao Baobcd1d162017-08-26 13:10:26 -0700842 system_updated = source_fp != target_fp
843
Yifan Hong51d37562019-04-23 17:06:46 -0700844 # other build fingerprints could be possibly blacklisted at build time. For
845 # such a case, we consider those images being changed.
846 vendor_updated = FingerprintChanged(source_info.vendor_fingerprint,
847 target_info.vendor_fingerprint)
848 product_updated = HasProductPartition(target_zip) and \
849 FingerprintChanged(source_info.product_fingerprint,
850 target_info.product_fingerprint)
851 odm_updated = HasOdmPartition(target_zip) and \
852 FingerprintChanged(source_info.odm_fingerprint,
853 target_info.odm_fingerprint)
Tao Baobcd1d162017-08-26 13:10:26 -0700854
Yifan Hong51d37562019-04-23 17:06:46 -0700855 AddCompatibilityArchive(system_updated or product_updated,
856 vendor_updated or odm_updated)
Tao Bao21803d32017-04-19 10:16:09 -0700857
858
Tao Bao491d7e22018-02-21 13:17:22 -0800859def WriteFullOTAPackage(input_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -0800860 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
Doug Zongkereef39442009-04-02 12:14:19 -0700861
Tao Bao481bab82017-12-21 11:23:09 -0800862 # We don't know what version it will be installed on top of. We expect the API
863 # just won't change very often. Similarly for fstab, it might have changed in
864 # the target build.
865 target_api_version = target_info["recovery_api_version"]
866 script = edify_generator.EdifyGenerator(target_api_version, target_info)
Michael Runge6e836112014-04-15 17:40:21 -0700867
Tao Bao481bab82017-12-21 11:23:09 -0800868 if target_info.oem_props and not OPTIONS.oem_no_mount:
869 target_info.WriteMountOemScript(script)
870
Tao Baodf3a48b2018-01-10 16:30:43 -0800871 metadata = GetPackageMetadata(target_info)
Doug Zongker2ea21062010-04-28 16:05:21 -0700872
Tao Bao491d7e22018-02-21 13:17:22 -0800873 if not OPTIONS.no_signing:
874 staging_file = common.MakeTempFile(suffix='.zip')
875 else:
876 staging_file = output_file
877
878 output_zip = zipfile.ZipFile(
879 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
880
Doug Zongker05d3dea2009-06-22 11:32:31 -0700881 device_specific = common.DeviceSpecificParams(
882 input_zip=input_zip,
Tao Bao481bab82017-12-21 11:23:09 -0800883 input_version=target_api_version,
Doug Zongker05d3dea2009-06-22 11:32:31 -0700884 output_zip=output_zip,
885 script=script,
Doug Zongker2ea21062010-04-28 16:05:21 -0700886 input_tmp=OPTIONS.input_tmp,
Doug Zongker96a57e72010-09-26 14:57:41 -0700887 metadata=metadata,
888 info_dict=OPTIONS.info_dict)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700889
Tao Bao457cbf62017-03-06 09:56:01 -0800890 assert HasRecoveryPatch(input_zip)
Doug Zongkerc9253822014-02-04 12:17:58 -0800891
Tao Bao481bab82017-12-21 11:23:09 -0800892 # Assertions (e.g. downgrade check, device properties check).
893 ts = target_info.GetBuildProp("ro.build.date.utc")
894 ts_text = target_info.GetBuildProp("ro.build.date")
Elliott Hughesd8a52f92016-06-20 14:35:47 -0700895 script.AssertOlderBuild(ts, ts_text)
Doug Zongkereef39442009-04-02 12:14:19 -0700896
Tao Bao481bab82017-12-21 11:23:09 -0800897 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Doug Zongker05d3dea2009-06-22 11:32:31 -0700898 device_specific.FullOTA_Assertions()
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800899
900 # Two-step package strategy (in chronological order, which is *not*
901 # the order in which the generated script has things):
902 #
903 # if stage is not "2/3" or "3/3":
904 # write recovery image to boot partition
905 # set stage to "2/3"
906 # reboot to boot partition and restart recovery
907 # else if stage is "2/3":
908 # write recovery image to recovery partition
909 # set stage to "3/3"
910 # reboot to recovery partition and restart recovery
911 # else:
912 # (stage must be "3/3")
913 # set stage to ""
914 # do normal full package installation:
915 # wipe and install system, boot image, etc.
916 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -0700917 # complete script normally
918 # (allow recovery to mark itself finished and reboot)
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800919
920 recovery_img = common.GetBootableImage("recovery.img", "recovery.img",
921 OPTIONS.input_tmp, "RECOVERY")
922 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -0800923 if not target_info.get("multistage_support"):
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800924 assert False, "two-step packages not supported by this build"
Tao Bao481bab82017-12-21 11:23:09 -0800925 fs = target_info["fstab"]["/misc"]
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800926 assert fs.fs_type.upper() == "EMMC", \
927 "two-step packages only supported on devices with EMMC /misc partitions"
928 bcb_dev = {"bcb_dev": fs.device}
929 common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
930 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -0700931if get_stage("%(bcb_dev)s") == "2/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800932""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -0800933
934 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
935 script.Comment("Stage 2/3")
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800936 script.WriteRawImage("/recovery", "recovery.img")
937 script.AppendExtra("""
938set_stage("%(bcb_dev)s", "3/3");
939reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -0700940else if get_stage("%(bcb_dev)s") == "3/3" then
Doug Zongker9b23f2c2013-11-25 14:44:12 -0800941""" % bcb_dev)
942
Tao Baod42e97e2016-11-30 12:11:57 -0800943 # Stage 3/3: Make changes.
944 script.Comment("Stage 3/3")
945
Tao Bao6c55a8a2015-04-08 15:30:27 -0700946 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -0800947 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -0700948
Doug Zongkere5ff5902012-01-17 10:55:37 -0800949 device_specific.FullOTA_InstallBegin()
Doug Zongker171f1cd2009-06-15 22:36:37 -0700950
Doug Zongker01ce19c2014-02-04 13:48:15 -0800951 system_progress = 0.75
Doug Zongkereef39442009-04-02 12:14:19 -0700952
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700953 if OPTIONS.wipe_user_data:
Doug Zongker01ce19c2014-02-04 13:48:15 -0800954 system_progress -= 0.1
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700955 if HasVendorPartition(input_zip):
956 system_progress -= 0.1
Doug Zongkerdbfaae52009-04-21 17:12:54 -0700957
Doug Zongker4b9596f2014-06-09 14:15:45 -0700958 script.ShowProgress(system_progress, 0)
Jesse Zhao75bcea02015-01-06 10:59:53 -0800959
Yifan Hong10c530d2018-12-27 17:34:18 -0800960 def GetBlockDifference(partition):
961 # Full OTA is done as an "incremental" against an empty source image. This
962 # has the effect of writing new data from the package to the entire
963 # partition, but lets us reuse the updater code that writes incrementals to
964 # do it.
Yifan Hong8a66a712019-04-04 15:37:57 -0700965 tgt = common.GetUserImage(partition, OPTIONS.input_tmp, input_zip,
966 info_dict=target_info,
967 reset_file_map=True)
Yifan Hong10c530d2018-12-27 17:34:18 -0800968 diff = common.BlockDifference(partition, tgt, src=None)
969 return diff
Doug Zongkereef39442009-04-02 12:14:19 -0700970
Yifan Hong10c530d2018-12-27 17:34:18 -0800971 device_specific_diffs = device_specific.FullOTA_GetBlockDifferences()
972 if device_specific_diffs:
973 assert all(isinstance(diff, common.BlockDifference)
974 for diff in device_specific_diffs), \
975 "FullOTA_GetBlockDifferences is not returning a list of " \
976 "BlockDifference objects"
Doug Zongkerc9253822014-02-04 12:17:58 -0800977
Yifan Hong10c530d2018-12-27 17:34:18 -0800978 progress_dict = dict()
979 block_diffs = [GetBlockDifference("system")]
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700980 if HasVendorPartition(input_zip):
Yifan Hong10c530d2018-12-27 17:34:18 -0800981 block_diffs.append(GetBlockDifference("vendor"))
982 progress_dict["vendor"] = 0.1
983 if device_specific_diffs:
984 block_diffs += device_specific_diffs
Doug Zongkerc8b4e842014-06-16 15:16:31 -0700985
Yifan Hong10c530d2018-12-27 17:34:18 -0800986 if target_info.get('use_dynamic_partitions') == "true":
987 # Use empty source_info_dict to indicate that all partitions / groups must
988 # be re-added.
989 dynamic_partitions_diff = common.DynamicPartitionsDifference(
990 info_dict=OPTIONS.info_dict,
991 block_diffs=block_diffs,
992 progress_dict=progress_dict)
993 dynamic_partitions_diff.WriteScript(script, output_zip,
994 write_verify_script=OPTIONS.verify)
995 else:
996 for block_diff in block_diffs:
997 block_diff.WriteScript(script, output_zip,
998 progress=progress_dict.get(block_diff.partition),
999 write_verify_script=OPTIONS.verify)
Doug Zongker73ef8252009-07-23 15:12:53 -07001000
Tao Bao481bab82017-12-21 11:23:09 -08001001 AddCompatibilityArchiveIfTrebleEnabled(input_zip, output_zip, target_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001002
Yifan Hong10c530d2018-12-27 17:34:18 -08001003 boot_img = common.GetBootableImage(
1004 "boot.img", "boot.img", OPTIONS.input_tmp, "BOOT")
Tao Bao481bab82017-12-21 11:23:09 -08001005 common.CheckSize(boot_img.data, "boot.img", target_info)
Doug Zongker73ef8252009-07-23 15:12:53 -07001006 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001007
Doug Zongker01ce19c2014-02-04 13:48:15 -08001008 script.ShowProgress(0.05, 5)
Doug Zongker9ce0fb62010-09-20 18:04:41 -07001009 script.WriteRawImage("/boot", "boot.img")
Doug Zongker05d3dea2009-06-22 11:32:31 -07001010
Doug Zongker01ce19c2014-02-04 13:48:15 -08001011 script.ShowProgress(0.2, 10)
Doug Zongker05d3dea2009-06-22 11:32:31 -07001012 device_specific.FullOTA_InstallEnd()
Doug Zongkereef39442009-04-02 12:14:19 -07001013
Doug Zongker1c390a22009-05-14 19:06:36 -07001014 if OPTIONS.extra_script is not None:
Doug Zongkerc494d7c2009-06-18 08:43:44 -07001015 script.AppendExtra(OPTIONS.extra_script)
Doug Zongker1c390a22009-05-14 19:06:36 -07001016
Doug Zongker14833602010-02-02 13:12:04 -08001017 script.UnmountAll()
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001018
Doug Zongker922206e2014-03-04 13:16:24 -08001019 if OPTIONS.wipe_user_data:
1020 script.ShowProgress(0.1, 10)
1021 script.FormatPartition("/data")
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001022
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001023 if OPTIONS.two_step:
1024 script.AppendExtra("""
1025set_stage("%(bcb_dev)s", "");
1026""" % bcb_dev)
1027 script.AppendExtra("else\n")
Tao Baod42e97e2016-11-30 12:11:57 -08001028
1029 # Stage 1/3: Nothing to verify for full OTA. Write recovery image to /boot.
1030 script.Comment("Stage 1/3")
1031 _WriteRecoveryImageToBoot(script, output_zip)
1032
Doug Zongker9b23f2c2013-11-25 14:44:12 -08001033 script.AppendExtra("""
1034set_stage("%(bcb_dev)s", "2/3");
1035reboot_now("%(bcb_dev)s", "");
1036endif;
1037endif;
1038""" % bcb_dev)
Tao Baod8d14be2016-02-04 14:26:02 -08001039
Tao Bao5d182562016-02-23 11:38:39 -08001040 script.SetProgress(1)
1041 script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001042 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001043
1044 # We haven't written the metadata entry, which will be done in
1045 # FinalizeMetadata.
1046 common.ZipClose(output_zip)
1047
1048 needed_property_files = (
1049 NonAbOtaPropertyFiles(),
1050 )
1051 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Doug Zongker2ea21062010-04-28 16:05:21 -07001052
Doug Zongkerfc44a512014-08-26 13:10:25 -07001053
xunchang1cfe2512019-02-19 14:14:48 -08001054def WriteMetadata(metadata, output):
1055 """Writes the metadata to the zip archive or a file.
1056
1057 Args:
1058 metadata: The metadata dict for the package.
1059 output: A ZipFile object or a string of the output file path.
1060 """
1061
Tao Bao59cf0c52019-06-25 10:04:24 -07001062 value = "".join(["%s=%s\n" % kv for kv in sorted(metadata.items())])
xunchang1cfe2512019-02-19 14:14:48 -08001063 if isinstance(output, zipfile.ZipFile):
1064 common.ZipWriteStr(output, METADATA_NAME, value,
1065 compress_type=zipfile.ZIP_STORED)
1066 return
1067
1068 with open(output, 'w') as f:
1069 f.write(value)
Doug Zongkereef39442009-04-02 12:14:19 -07001070
Doug Zongkerfc44a512014-08-26 13:10:25 -07001071
Tao Bao481bab82017-12-21 11:23:09 -08001072def HandleDowngradeMetadata(metadata, target_info, source_info):
Tao Baob31892e2017-02-07 11:21:17 -08001073 # Only incremental OTAs are allowed to reach here.
1074 assert OPTIONS.incremental_source is not None
1075
Tao Bao481bab82017-12-21 11:23:09 -08001076 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
1077 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Bao59cf0c52019-06-25 10:04:24 -07001078 is_downgrade = int(post_timestamp) < int(pre_timestamp)
Tao Baob31892e2017-02-07 11:21:17 -08001079
1080 if OPTIONS.downgrade:
Tao Baob31892e2017-02-07 11:21:17 -08001081 if not is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001082 raise RuntimeError(
1083 "--downgrade or --override_timestamp specified but no downgrade "
1084 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tao Bao3e6161a2017-02-28 11:48:48 -08001085 metadata["ota-downgrade"] = "yes"
Tao Baob31892e2017-02-07 11:21:17 -08001086 else:
1087 if is_downgrade:
Tao Baofaa8e0b2018-04-12 14:31:43 -07001088 raise RuntimeError(
1089 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
1090 "Need to specify --override_timestamp OR --downgrade to allow "
1091 "building the incremental." % (pre_timestamp, post_timestamp))
Tao Baob31892e2017-02-07 11:21:17 -08001092
1093
Tao Baodf3a48b2018-01-10 16:30:43 -08001094def GetPackageMetadata(target_info, source_info=None):
1095 """Generates and returns the metadata dict.
1096
1097 It generates a dict() that contains the info to be written into an OTA
1098 package (META-INF/com/android/metadata). It also handles the detection of
Tao Baofaa8e0b2018-04-12 14:31:43 -07001099 downgrade / data wipe based on the global options.
Tao Baodf3a48b2018-01-10 16:30:43 -08001100
1101 Args:
1102 target_info: The BuildInfo instance that holds the target build info.
1103 source_info: The BuildInfo instance that holds the source build info, or
1104 None if generating full OTA.
1105
1106 Returns:
1107 A dict to be written into package metadata entry.
1108 """
1109 assert isinstance(target_info, BuildInfo)
1110 assert source_info is None or isinstance(source_info, BuildInfo)
1111
1112 metadata = {
1113 'post-build' : target_info.fingerprint,
1114 'post-build-incremental' : target_info.GetBuildProp(
1115 'ro.build.version.incremental'),
Tao Bao35dc2552018-02-01 13:18:00 -08001116 'post-sdk-level' : target_info.GetBuildProp(
1117 'ro.build.version.sdk'),
1118 'post-security-patch-level' : target_info.GetBuildProp(
1119 'ro.build.version.security_patch'),
Tao Baodf3a48b2018-01-10 16:30:43 -08001120 }
1121
1122 if target_info.is_ab:
1123 metadata['ota-type'] = 'AB'
1124 metadata['ota-required-cache'] = '0'
1125 else:
1126 metadata['ota-type'] = 'BLOCK'
1127
1128 if OPTIONS.wipe_user_data:
1129 metadata['ota-wipe'] = 'yes'
1130
Tao Bao393eeb42019-03-06 16:00:38 -08001131 if OPTIONS.retrofit_dynamic_partitions:
1132 metadata['ota-retrofit-dynamic-partitions'] = 'yes'
1133
Tao Baodf3a48b2018-01-10 16:30:43 -08001134 is_incremental = source_info is not None
1135 if is_incremental:
1136 metadata['pre-build'] = source_info.fingerprint
1137 metadata['pre-build-incremental'] = source_info.GetBuildProp(
1138 'ro.build.version.incremental')
1139 metadata['pre-device'] = source_info.device
1140 else:
1141 metadata['pre-device'] = target_info.device
1142
Tao Baofaa8e0b2018-04-12 14:31:43 -07001143 # Use the actual post-timestamp, even for a downgrade case.
1144 metadata['post-timestamp'] = target_info.GetBuildProp('ro.build.date.utc')
1145
1146 # Detect downgrades and set up downgrade flags accordingly.
Tao Baodf3a48b2018-01-10 16:30:43 -08001147 if is_incremental:
1148 HandleDowngradeMetadata(metadata, target_info, source_info)
Tao Baodf3a48b2018-01-10 16:30:43 -08001149
1150 return metadata
1151
1152
Tao Baod3fc38a2018-03-08 16:09:01 -08001153class PropertyFiles(object):
1154 """A class that computes the property-files string for an OTA package.
1155
1156 A property-files string is a comma-separated string that contains the
1157 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
1158 can be fetched directly with the package URL along with the offset/size info.
1159 These strings can be used for streaming A/B OTAs, or allowing an updater to
1160 download package metadata entry directly, without paying the cost of
1161 downloading entire package.
Tao Baofe5b69a2018-03-02 09:47:43 -08001162
Tao Baocc8e2662018-03-01 19:30:00 -08001163 Computing the final property-files string requires two passes. Because doing
1164 the whole package signing (with signapk.jar) will possibly reorder the ZIP
1165 entries, which may in turn invalidate earlier computed ZIP entry offset/size
1166 values.
1167
1168 This class provides functions to be called for each pass. The general flow is
1169 as follows.
1170
Tao Baod3fc38a2018-03-08 16:09:01 -08001171 property_files = PropertyFiles()
Tao Baocc8e2662018-03-01 19:30:00 -08001172 # The first pass, which writes placeholders before doing initial signing.
1173 property_files.Compute()
1174 SignOutput()
1175
1176 # The second pass, by replacing the placeholders with actual data.
1177 property_files.Finalize()
1178 SignOutput()
1179
1180 And the caller can additionally verify the final result.
1181
1182 property_files.Verify()
Tao Baofe5b69a2018-03-02 09:47:43 -08001183 """
1184
Tao Baocc8e2662018-03-01 19:30:00 -08001185 def __init__(self):
Tao Baod3fc38a2018-03-08 16:09:01 -08001186 self.name = None
1187 self.required = ()
1188 self.optional = ()
Tao Baofe5b69a2018-03-02 09:47:43 -08001189
Tao Baocc8e2662018-03-01 19:30:00 -08001190 def Compute(self, input_zip):
1191 """Computes and returns a property-files string with placeholders.
Tao Baofe5b69a2018-03-02 09:47:43 -08001192
Tao Baocc8e2662018-03-01 19:30:00 -08001193 We reserve extra space for the offset and size of the metadata entry itself,
1194 although we don't know the final values until the package gets signed.
Tao Baofe5b69a2018-03-02 09:47:43 -08001195
Tao Baocc8e2662018-03-01 19:30:00 -08001196 Args:
1197 input_zip: The input ZIP file.
Tao Baofe5b69a2018-03-02 09:47:43 -08001198
Tao Baocc8e2662018-03-01 19:30:00 -08001199 Returns:
1200 A string with placeholders for the metadata offset/size info, e.g.
1201 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1202 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001203 return self.GetPropertyFilesString(input_zip, reserve_space=True)
Tao Baofe5b69a2018-03-02 09:47:43 -08001204
Tao Baod2ce2ed2018-03-16 12:59:42 -07001205 class InsufficientSpaceException(Exception):
1206 pass
1207
Tao Baocc8e2662018-03-01 19:30:00 -08001208 def Finalize(self, input_zip, reserved_length):
1209 """Finalizes a property-files string with actual METADATA offset/size info.
1210
1211 The input ZIP file has been signed, with the ZIP entries in the desired
1212 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
1213 the ZIP entry offsets and construct the property-files string with actual
1214 data. Note that during this process, we must pad the property-files string
1215 to the reserved length, so that the METADATA entry size remains the same.
1216 Otherwise the entries' offsets and sizes may change again.
1217
1218 Args:
1219 input_zip: The input ZIP file.
1220 reserved_length: The reserved length of the property-files string during
1221 the call to Compute(). The final string must be no more than this
1222 size.
1223
1224 Returns:
1225 A property-files string including the metadata offset/size info, e.g.
1226 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
1227
1228 Raises:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001229 InsufficientSpaceException: If the reserved length is insufficient to hold
1230 the final string.
Tao Baocc8e2662018-03-01 19:30:00 -08001231 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001232 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001233 if len(result) > reserved_length:
1234 raise self.InsufficientSpaceException(
1235 'Insufficient reserved space: reserved={}, actual={}'.format(
1236 reserved_length, len(result)))
1237
Tao Baocc8e2662018-03-01 19:30:00 -08001238 result += ' ' * (reserved_length - len(result))
1239 return result
1240
1241 def Verify(self, input_zip, expected):
1242 """Verifies the input ZIP file contains the expected property-files string.
1243
1244 Args:
1245 input_zip: The input ZIP file.
1246 expected: The property-files string that's computed from Finalize().
1247
1248 Raises:
1249 AssertionError: On finding a mismatch.
1250 """
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001251 actual = self.GetPropertyFilesString(input_zip)
Tao Baocc8e2662018-03-01 19:30:00 -08001252 assert actual == expected, \
1253 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
1254
Zhomart Mukhamejanov603655f2018-05-04 12:35:09 -07001255 def GetPropertyFilesString(self, zip_file, reserve_space=False):
1256 """
1257 Constructs the property-files string per request.
1258
1259 Args:
1260 zip_file: The input ZIP file.
1261 reserved_length: The reserved length of the property-files string.
1262
1263 Returns:
1264 A property-files string including the metadata offset/size info, e.g.
1265 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
1266 """
Tao Baocc8e2662018-03-01 19:30:00 -08001267
1268 def ComputeEntryOffsetSize(name):
1269 """Computes the zip entry offset and size."""
1270 info = zip_file.getinfo(name)
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001271 offset = info.header_offset
1272 offset += zipfile.sizeFileHeader
1273 offset += len(info.extra) + len(info.filename)
Tao Baocc8e2662018-03-01 19:30:00 -08001274 size = info.file_size
1275 return '%s:%d:%d' % (os.path.basename(name), offset, size)
1276
1277 tokens = []
Tao Bao85f16982018-03-08 16:28:33 -08001278 tokens.extend(self._GetPrecomputed(zip_file))
Tao Baocc8e2662018-03-01 19:30:00 -08001279 for entry in self.required:
1280 tokens.append(ComputeEntryOffsetSize(entry))
1281 for entry in self.optional:
1282 if entry in zip_file.namelist():
1283 tokens.append(ComputeEntryOffsetSize(entry))
1284
1285 # 'META-INF/com/android/metadata' is required. We don't know its actual
1286 # offset and length (as well as the values for other entries). So we reserve
Tao Baod2ce2ed2018-03-16 12:59:42 -07001287 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
1288 # the space for metadata entry. Because 'offset' allows a max of 10-digit
1289 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
1290 # reserved space serves the metadata entry only.
Tao Baocc8e2662018-03-01 19:30:00 -08001291 if reserve_space:
Tao Baod2ce2ed2018-03-16 12:59:42 -07001292 tokens.append('metadata:' + ' ' * 15)
Tao Baocc8e2662018-03-01 19:30:00 -08001293 else:
1294 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
1295
1296 return ','.join(tokens)
Tao Baofe5b69a2018-03-02 09:47:43 -08001297
Tao Bao85f16982018-03-08 16:28:33 -08001298 def _GetPrecomputed(self, input_zip):
1299 """Computes the additional tokens to be included into the property-files.
1300
1301 This applies to tokens without actual ZIP entries, such as
1302 payload_metadadata.bin. We want to expose the offset/size to updaters, so
1303 that they can download the payload metadata directly with the info.
1304
1305 Args:
1306 input_zip: The input zip file.
1307
1308 Returns:
1309 A list of strings (tokens) to be added to the property-files string.
1310 """
1311 # pylint: disable=no-self-use
1312 # pylint: disable=unused-argument
1313 return []
1314
Tao Baofe5b69a2018-03-02 09:47:43 -08001315
Tao Baod3fc38a2018-03-08 16:09:01 -08001316class StreamingPropertyFiles(PropertyFiles):
1317 """A subclass for computing the property-files for streaming A/B OTAs."""
1318
1319 def __init__(self):
1320 super(StreamingPropertyFiles, self).__init__()
1321 self.name = 'ota-streaming-property-files'
1322 self.required = (
1323 # payload.bin and payload_properties.txt must exist.
1324 'payload.bin',
1325 'payload_properties.txt',
1326 )
1327 self.optional = (
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07001328 # care_map is available only if dm-verity is enabled.
1329 'care_map.pb',
Tao Baod3fc38a2018-03-08 16:09:01 -08001330 'care_map.txt',
1331 # compatibility.zip is available only if target supports Treble.
1332 'compatibility.zip',
1333 )
1334
1335
Tao Bao85f16982018-03-08 16:28:33 -08001336class AbOtaPropertyFiles(StreamingPropertyFiles):
1337 """The property-files for A/B OTA that includes payload_metadata.bin info.
1338
1339 Since P, we expose one more token (aka property-file), in addition to the ones
1340 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
1341 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
1342 doesn't exist as a separate ZIP entry, but can be used to verify if the
1343 payload can be applied on the given device.
1344
1345 For backward compatibility, we keep both of the 'ota-streaming-property-files'
1346 and the newly added 'ota-property-files' in P. The new token will only be
1347 available in 'ota-property-files'.
1348 """
1349
1350 def __init__(self):
1351 super(AbOtaPropertyFiles, self).__init__()
1352 self.name = 'ota-property-files'
1353
1354 def _GetPrecomputed(self, input_zip):
1355 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
1356 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
1357
1358 @staticmethod
1359 def _GetPayloadMetadataOffsetAndSize(input_zip):
1360 """Computes the offset and size of the payload metadata for a given package.
1361
1362 (From system/update_engine/update_metadata.proto)
1363 A delta update file contains all the deltas needed to update a system from
1364 one specific version to another specific version. The update format is
1365 represented by this struct pseudocode:
1366
1367 struct delta_update_file {
1368 char magic[4] = "CrAU";
1369 uint64 file_format_version;
1370 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1371
1372 // Only present if format_version > 1:
1373 uint32 metadata_signature_size;
1374
1375 // The Bzip2 compressed DeltaArchiveManifest
1376 char manifest[metadata_signature_size];
1377
1378 // The signature of the metadata (from the beginning of the payload up to
1379 // this location, not including the signature itself). This is a
1380 // serialized Signatures message.
1381 char medatada_signature_message[metadata_signature_size];
1382
1383 // Data blobs for files, no specific format. The specific offset
1384 // and length of each data blob is recorded in the DeltaArchiveManifest.
1385 struct {
1386 char data[];
1387 } blobs[];
1388
1389 // These two are not signed:
1390 uint64 payload_signatures_message_size;
1391 char payload_signatures_message[];
1392 };
1393
1394 'payload-metadata.bin' contains all the bytes from the beginning of the
1395 payload, till the end of 'medatada_signature_message'.
1396 """
1397 payload_info = input_zip.getinfo('payload.bin')
Shashikant Baviskar338856f2018-04-12 12:11:22 +09001398 payload_offset = payload_info.header_offset
1399 payload_offset += zipfile.sizeFileHeader
1400 payload_offset += len(payload_info.extra) + len(payload_info.filename)
Tao Bao85f16982018-03-08 16:28:33 -08001401 payload_size = payload_info.file_size
1402
Tao Bao59cf0c52019-06-25 10:04:24 -07001403 with input_zip.open('payload.bin') as payload_fp:
Tao Bao85f16982018-03-08 16:28:33 -08001404 header_bin = payload_fp.read(24)
1405
1406 # network byte order (big-endian)
1407 header = struct.unpack("!IQQL", header_bin)
1408
1409 # 'CrAU'
1410 magic = header[0]
1411 assert magic == 0x43724155, "Invalid magic: {:x}".format(magic)
1412
1413 manifest_size = header[2]
1414 metadata_signature_size = header[3]
1415 metadata_total = 24 + manifest_size + metadata_signature_size
1416 assert metadata_total < payload_size
1417
1418 return (payload_offset, metadata_total)
1419
1420
Tao Bao491d7e22018-02-21 13:17:22 -08001421class NonAbOtaPropertyFiles(PropertyFiles):
1422 """The property-files for non-A/B OTA.
1423
1424 For non-A/B OTA, the property-files string contains the info for METADATA
1425 entry, with which a system updater can be fetched the package metadata prior
1426 to downloading the entire package.
1427 """
1428
1429 def __init__(self):
1430 super(NonAbOtaPropertyFiles, self).__init__()
1431 self.name = 'ota-property-files'
1432
1433
Tao Baod3fc38a2018-03-08 16:09:01 -08001434def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
Tao Baofe5b69a2018-03-02 09:47:43 -08001435 """Finalizes the metadata and signs an A/B OTA package.
1436
1437 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
1438 that contains the offsets and sizes for the ZIP entries. An example
1439 property-files string is as follows.
1440
1441 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
1442
1443 OTA server can pass down this string, in addition to the package URL, to the
1444 system update client. System update client can then fetch individual ZIP
1445 entries (ZIP_STORED) directly at the given offset of the URL.
1446
1447 Args:
1448 metadata: The metadata dict for the package.
1449 input_file: The input ZIP filename that doesn't contain the package METADATA
1450 entry yet.
1451 output_file: The final output ZIP filename.
Tao Baod3fc38a2018-03-08 16:09:01 -08001452 needed_property_files: The list of PropertyFiles' to be generated.
Tao Baofe5b69a2018-03-02 09:47:43 -08001453 """
Tao Baofe5b69a2018-03-02 09:47:43 -08001454
Tao Baod2ce2ed2018-03-16 12:59:42 -07001455 def ComputeAllPropertyFiles(input_file, needed_property_files):
1456 # Write the current metadata entry with placeholders.
1457 with zipfile.ZipFile(input_file) as input_zip:
1458 for property_files in needed_property_files:
1459 metadata[property_files.name] = property_files.Compute(input_zip)
1460 namelist = input_zip.namelist()
Tao Baofe5b69a2018-03-02 09:47:43 -08001461
Tao Baod2ce2ed2018-03-16 12:59:42 -07001462 if METADATA_NAME in namelist:
1463 common.ZipDelete(input_file, METADATA_NAME)
1464 output_zip = zipfile.ZipFile(input_file, 'a')
1465 WriteMetadata(metadata, output_zip)
1466 common.ZipClose(output_zip)
1467
1468 if OPTIONS.no_signing:
1469 return input_file
1470
Tao Bao491d7e22018-02-21 13:17:22 -08001471 prelim_signing = common.MakeTempFile(suffix='.zip')
1472 SignOutput(input_file, prelim_signing)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001473 return prelim_signing
Tao Baofe5b69a2018-03-02 09:47:43 -08001474
Tao Baod2ce2ed2018-03-16 12:59:42 -07001475 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
1476 with zipfile.ZipFile(prelim_signing) as prelim_signing_zip:
1477 for property_files in needed_property_files:
1478 metadata[property_files.name] = property_files.Finalize(
1479 prelim_signing_zip, len(metadata[property_files.name]))
1480
1481 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
1482 # entries, as well as padding the entry headers. We do a preliminary signing
1483 # (with an incomplete metadata entry) to allow that to happen. Then compute
1484 # the ZIP entry offsets, write back the final metadata and do the final
1485 # signing.
1486 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
1487 try:
1488 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
1489 except PropertyFiles.InsufficientSpaceException:
1490 # Even with the preliminary signing, the entry orders may change
1491 # dramatically, which leads to insufficiently reserved space during the
1492 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
1493 # preliminary signing works, based on the already ordered ZIP entries, to
1494 # address the issue.
1495 prelim_signing = ComputeAllPropertyFiles(
1496 prelim_signing, needed_property_files)
1497 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
Tao Baofe5b69a2018-03-02 09:47:43 -08001498
1499 # Replace the METADATA entry.
1500 common.ZipDelete(prelim_signing, METADATA_NAME)
Tao Baod2ce2ed2018-03-16 12:59:42 -07001501 output_zip = zipfile.ZipFile(prelim_signing, 'a')
Tao Baofe5b69a2018-03-02 09:47:43 -08001502 WriteMetadata(metadata, output_zip)
1503 common.ZipClose(output_zip)
1504
1505 # Re-sign the package after updating the metadata entry.
Tao Bao491d7e22018-02-21 13:17:22 -08001506 if OPTIONS.no_signing:
1507 output_file = prelim_signing
1508 else:
1509 SignOutput(prelim_signing, output_file)
Tao Baofe5b69a2018-03-02 09:47:43 -08001510
1511 # Reopen the final signed zip to double check the streaming metadata.
Tao Baod2ce2ed2018-03-16 12:59:42 -07001512 with zipfile.ZipFile(output_file) as output_zip:
Tao Baod3fc38a2018-03-08 16:09:01 -08001513 for property_files in needed_property_files:
1514 property_files.Verify(output_zip, metadata[property_files.name].strip())
Tao Baofe5b69a2018-03-02 09:47:43 -08001515
xunchang1cfe2512019-02-19 14:14:48 -08001516 # If requested, dump the metadata to a separate file.
1517 output_metadata_path = OPTIONS.output_metadata_path
1518 if output_metadata_path:
1519 WriteMetadata(metadata, output_metadata_path)
1520
Tao Baofe5b69a2018-03-02 09:47:43 -08001521
Tao Bao491d7e22018-02-21 13:17:22 -08001522def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_file):
Tao Bao481bab82017-12-21 11:23:09 -08001523 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
1524 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
Geremy Condra36bd3652014-02-06 19:45:10 -08001525
Tao Bao481bab82017-12-21 11:23:09 -08001526 target_api_version = target_info["recovery_api_version"]
1527 source_api_version = source_info["recovery_api_version"]
1528 if source_api_version == 0:
Tao Bao32fcdab2018-10-12 10:30:39 -07001529 logger.warning(
1530 "Generating edify script for a source that can't install it.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001531
Tao Bao481bab82017-12-21 11:23:09 -08001532 script = edify_generator.EdifyGenerator(
1533 source_api_version, target_info, fstab=source_info["fstab"])
1534
1535 if target_info.oem_props or source_info.oem_props:
1536 if not OPTIONS.oem_no_mount:
1537 source_info.WriteMountOemScript(script)
Tao Bao3806c232015-07-05 21:08:33 -07001538
Tao Baodf3a48b2018-01-10 16:30:43 -08001539 metadata = GetPackageMetadata(target_info, source_info)
Tao Bao5d182562016-02-23 11:38:39 -08001540
Tao Bao491d7e22018-02-21 13:17:22 -08001541 if not OPTIONS.no_signing:
1542 staging_file = common.MakeTempFile(suffix='.zip')
1543 else:
1544 staging_file = output_file
1545
1546 output_zip = zipfile.ZipFile(
1547 staging_file, "w", compression=zipfile.ZIP_DEFLATED)
1548
Geremy Condra36bd3652014-02-06 19:45:10 -08001549 device_specific = common.DeviceSpecificParams(
1550 source_zip=source_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001551 source_version=source_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001552 source_tmp=OPTIONS.source_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001553 target_zip=target_zip,
Tao Bao481bab82017-12-21 11:23:09 -08001554 target_version=target_api_version,
Yifan Hong8a66a712019-04-04 15:37:57 -07001555 target_tmp=OPTIONS.target_tmp,
Geremy Condra36bd3652014-02-06 19:45:10 -08001556 output_zip=output_zip,
1557 script=script,
1558 metadata=metadata,
Tao Bao481bab82017-12-21 11:23:09 -08001559 info_dict=source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001560
Geremy Condra36bd3652014-02-06 19:45:10 -08001561 source_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001562 "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001563 target_boot = common.GetBootableImage(
Tao Bao481bab82017-12-21 11:23:09 -08001564 "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT", target_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001565 updating_boot = (not OPTIONS.two_step and
1566 (source_boot.data != target_boot.data))
1567
Geremy Condra36bd3652014-02-06 19:45:10 -08001568 target_recovery = common.GetBootableImage(
1569 "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
Geremy Condra36bd3652014-02-06 19:45:10 -08001570
Yifan Hong8a66a712019-04-04 15:37:57 -07001571 # See notes in common.GetUserImage()
Tao Baoe709b092018-02-07 12:40:00 -08001572 allow_shared_blocks = (source_info.get('ext4_share_dup_blocks') == "true" or
1573 target_info.get('ext4_share_dup_blocks') == "true")
Yifan Hong8a66a712019-04-04 15:37:57 -07001574 system_src = common.GetUserImage("system", OPTIONS.source_tmp, source_zip,
1575 info_dict=source_info,
1576 allow_shared_blocks=allow_shared_blocks)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001577
1578 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
1579 "system", 4096, target_info)
Yifan Hong8a66a712019-04-04 15:37:57 -07001580 system_tgt = common.GetUserImage("system", OPTIONS.target_tmp, target_zip,
1581 info_dict=target_info,
1582 allow_shared_blocks=allow_shared_blocks,
1583 hashtree_info_generator=
1584 hashtree_info_generator)
Tao Baodd2a5892015-03-12 12:32:37 -07001585
Tao Bao0582cb62017-12-21 11:47:01 -08001586 blockimgdiff_version = max(
Tao Bao481bab82017-12-21 11:23:09 -08001587 int(i) for i in target_info.get("blockimgdiff_versions", "1").split(","))
Tao Bao0582cb62017-12-21 11:47:01 -08001588 assert blockimgdiff_version >= 3
Tao Baodd2a5892015-03-12 12:32:37 -07001589
Tao Baof8acad12016-07-07 09:09:58 -07001590 # Check the first block of the source system partition for remount R/W only
1591 # if the filesystem is ext4.
Tao Bao481bab82017-12-21 11:23:09 -08001592 system_src_partition = source_info["fstab"]["/system"]
Tao Baof8acad12016-07-07 09:09:58 -07001593 check_first_block = system_src_partition.fs_type == "ext4"
Tao Bao293fd132016-06-11 12:19:23 -07001594 # Disable using imgdiff for squashfs. 'imgdiff -z' expects input files to be
1595 # in zip formats. However with squashfs, a) all files are compressed in LZ4;
1596 # b) the blocks listed in block map may not contain all the bytes for a given
1597 # file (because they're rounded to be 4K-aligned).
Tao Bao481bab82017-12-21 11:23:09 -08001598 system_tgt_partition = target_info["fstab"]["/system"]
Tao Baof8acad12016-07-07 09:09:58 -07001599 disable_imgdiff = (system_src_partition.fs_type == "squashfs" or
1600 system_tgt_partition.fs_type == "squashfs")
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001601 system_diff = common.BlockDifference("system", system_tgt, system_src,
Tianjie Xufc3422a2015-12-15 11:53:59 -08001602 check_first_block,
Tao Bao293fd132016-06-11 12:19:23 -07001603 version=blockimgdiff_version,
1604 disable_imgdiff=disable_imgdiff)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001605
Doug Zongkerc8b4e842014-06-16 15:16:31 -07001606 if HasVendorPartition(target_zip):
1607 if not HasVendorPartition(source_zip):
1608 raise RuntimeError("can't generate incremental that adds /vendor")
Yifan Hong8a66a712019-04-04 15:37:57 -07001609 vendor_src = common.GetUserImage("vendor", OPTIONS.source_tmp, source_zip,
1610 info_dict=source_info,
1611 allow_shared_blocks=allow_shared_blocks)
Tianjie Xu67c7cbb2018-08-30 00:32:07 -07001612 hashtree_info_generator = verity_utils.CreateHashtreeInfoGenerator(
1613 "vendor", 4096, target_info)
Yifan Hong8a66a712019-04-04 15:37:57 -07001614 vendor_tgt = common.GetUserImage(
1615 "vendor", OPTIONS.target_tmp, target_zip,
1616 info_dict=target_info,
1617 allow_shared_blocks=allow_shared_blocks,
1618 hashtree_info_generator=hashtree_info_generator)
Tianjie Xufc3422a2015-12-15 11:53:59 -08001619
1620 # Check first block of vendor partition for remount R/W only if
1621 # disk type is ext4
Tao Bao481bab82017-12-21 11:23:09 -08001622 vendor_partition = source_info["fstab"]["/vendor"]
Tao Baod8d14be2016-02-04 14:26:02 -08001623 check_first_block = vendor_partition.fs_type == "ext4"
Tao Bao293fd132016-06-11 12:19:23 -07001624 disable_imgdiff = vendor_partition.fs_type == "squashfs"
Doug Zongkerb34fcce2014-09-11 09:34:56 -07001625 vendor_diff = common.BlockDifference("vendor", vendor_tgt, vendor_src,
Tianjie Xufc3422a2015-12-15 11:53:59 -08001626 check_first_block,
Tao Bao293fd132016-06-11 12:19:23 -07001627 version=blockimgdiff_version,
1628 disable_imgdiff=disable_imgdiff)
Doug Zongkerfc44a512014-08-26 13:10:25 -07001629 else:
1630 vendor_diff = None
Geremy Condra36bd3652014-02-06 19:45:10 -08001631
Tao Baobcd1d162017-08-26 13:10:26 -07001632 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08001633 target_zip, output_zip, target_info, source_info)
Tao Baobcd1d162017-08-26 13:10:26 -07001634
Tao Bao481bab82017-12-21 11:23:09 -08001635 # Assertions (e.g. device properties check).
1636 target_info.WriteDeviceAssertions(script, OPTIONS.oem_no_mount)
Geremy Condra36bd3652014-02-06 19:45:10 -08001637 device_specific.IncrementalOTA_Assertions()
1638
1639 # Two-step incremental package strategy (in chronological order,
1640 # which is *not* the order in which the generated script has
1641 # things):
1642 #
1643 # if stage is not "2/3" or "3/3":
1644 # do verification on current system
1645 # write recovery image to boot partition
1646 # set stage to "2/3"
1647 # reboot to boot partition and restart recovery
1648 # else if stage is "2/3":
1649 # write recovery image to recovery partition
1650 # set stage to "3/3"
1651 # reboot to recovery partition and restart recovery
1652 # else:
1653 # (stage must be "3/3")
1654 # perform update:
1655 # patch system files, etc.
1656 # force full install of new boot image
1657 # set up system to update recovery partition on first boot
Dan Albert8b72aef2015-03-23 19:13:21 -07001658 # complete script normally
1659 # (allow recovery to mark itself finished and reboot)
Geremy Condra36bd3652014-02-06 19:45:10 -08001660
1661 if OPTIONS.two_step:
Tao Bao481bab82017-12-21 11:23:09 -08001662 if not source_info.get("multistage_support"):
Geremy Condra36bd3652014-02-06 19:45:10 -08001663 assert False, "two-step packages not supported by this build"
Tao Bao24604cc2018-02-01 16:25:44 -08001664 fs = source_info["fstab"]["/misc"]
Geremy Condra36bd3652014-02-06 19:45:10 -08001665 assert fs.fs_type.upper() == "EMMC", \
1666 "two-step packages only supported on devices with EMMC /misc partitions"
Tao Bao481bab82017-12-21 11:23:09 -08001667 bcb_dev = {"bcb_dev" : fs.device}
Geremy Condra36bd3652014-02-06 19:45:10 -08001668 common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
1669 script.AppendExtra("""
Michael Rungefb8886d2014-10-23 13:51:04 -07001670if get_stage("%(bcb_dev)s") == "2/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001671""" % bcb_dev)
Tao Baod42e97e2016-11-30 12:11:57 -08001672
1673 # Stage 2/3: Write recovery image to /recovery (currently running /boot).
1674 script.Comment("Stage 2/3")
Dan Albert8b72aef2015-03-23 19:13:21 -07001675 script.AppendExtra("sleep(20);\n")
Geremy Condra36bd3652014-02-06 19:45:10 -08001676 script.WriteRawImage("/recovery", "recovery.img")
1677 script.AppendExtra("""
1678set_stage("%(bcb_dev)s", "3/3");
1679reboot_now("%(bcb_dev)s", "recovery");
Michael Rungefb8886d2014-10-23 13:51:04 -07001680else if get_stage("%(bcb_dev)s") != "3/3" then
Geremy Condra36bd3652014-02-06 19:45:10 -08001681""" % bcb_dev)
1682
Tao Baod42e97e2016-11-30 12:11:57 -08001683 # Stage 1/3: (a) Verify the current system.
1684 script.Comment("Stage 1/3")
1685
Tao Bao6c55a8a2015-04-08 15:30:27 -07001686 # Dump fingerprints
Tao Bao481bab82017-12-21 11:23:09 -08001687 script.Print("Source: {}".format(source_info.fingerprint))
1688 script.Print("Target: {}".format(target_info.fingerprint))
Tao Bao6c55a8a2015-04-08 15:30:27 -07001689
Geremy Condra36bd3652014-02-06 19:45:10 -08001690 script.Print("Verifying current system...")
1691
1692 device_specific.IncrementalOTA_VerifyBegin()
1693
Tao Bao481bab82017-12-21 11:23:09 -08001694 WriteFingerprintAssertion(script, target_info, source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001695
Tao Baod8d14be2016-02-04 14:26:02 -08001696 # Check the required cache size (i.e. stashed blocks).
1697 size = []
1698 if system_diff:
1699 size.append(system_diff.required_cache)
1700 if vendor_diff:
1701 size.append(vendor_diff.required_cache)
1702
Geremy Condra36bd3652014-02-06 19:45:10 -08001703 if updating_boot:
Tao Bao481bab82017-12-21 11:23:09 -08001704 boot_type, boot_device = common.GetTypeAndDevice("/boot", source_info)
Geremy Condra36bd3652014-02-06 19:45:10 -08001705 d = common.Difference(target_boot, source_boot)
1706 _, _, d = d.ComputePatch()
Doug Zongkerf8340082014-08-05 10:39:37 -07001707 if d is None:
1708 include_full_boot = True
1709 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1710 else:
1711 include_full_boot = False
Geremy Condra36bd3652014-02-06 19:45:10 -08001712
Tao Bao32fcdab2018-10-12 10:30:39 -07001713 logger.info(
1714 "boot target: %d source: %d diff: %d", target_boot.size,
1715 source_boot.size, len(d))
Geremy Condra36bd3652014-02-06 19:45:10 -08001716
Tao Bao51216552018-08-26 11:53:15 -07001717 common.ZipWriteStr(output_zip, "boot.img.p", d)
Geremy Condra36bd3652014-02-06 19:45:10 -08001718
Tao Bao51216552018-08-26 11:53:15 -07001719 script.PatchPartitionCheck(
1720 "{}:{}:{}:{}".format(
1721 boot_type, boot_device, target_boot.size, target_boot.sha1),
1722 "{}:{}:{}:{}".format(
1723 boot_type, boot_device, source_boot.size, source_boot.sha1))
1724
Tao Baod8d14be2016-02-04 14:26:02 -08001725 size.append(target_boot.size)
1726
1727 if size:
1728 script.CacheFreeSpaceCheck(max(size))
Geremy Condra36bd3652014-02-06 19:45:10 -08001729
1730 device_specific.IncrementalOTA_VerifyEnd()
1731
1732 if OPTIONS.two_step:
Tao Baod42e97e2016-11-30 12:11:57 -08001733 # Stage 1/3: (b) Write recovery image to /boot.
1734 _WriteRecoveryImageToBoot(script, output_zip)
1735
Geremy Condra36bd3652014-02-06 19:45:10 -08001736 script.AppendExtra("""
1737set_stage("%(bcb_dev)s", "2/3");
1738reboot_now("%(bcb_dev)s", "");
1739else
1740""" % bcb_dev)
1741
Tao Baod42e97e2016-11-30 12:11:57 -08001742 # Stage 3/3: Make changes.
1743 script.Comment("Stage 3/3")
1744
Jesse Zhao75bcea02015-01-06 10:59:53 -08001745 # Verify the existing partitions.
Tao Baod522bdc2016-04-12 15:53:16 -07001746 system_diff.WriteVerifyScript(script, touched_blocks_only=True)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001747 if vendor_diff:
Tao Baod522bdc2016-04-12 15:53:16 -07001748 vendor_diff.WriteVerifyScript(script, touched_blocks_only=True)
Yifan Hong10c530d2018-12-27 17:34:18 -08001749 device_specific_diffs = device_specific.IncrementalOTA_GetBlockDifferences()
1750 if device_specific_diffs:
1751 assert all(isinstance(diff, common.BlockDifference)
1752 for diff in device_specific_diffs), \
1753 "IncrementalOTA_GetBlockDifferences is not returning a list of " \
1754 "BlockDifference objects"
1755 for diff in device_specific_diffs:
1756 diff.WriteVerifyScript(script, touched_blocks_only=True)
Jesse Zhao75bcea02015-01-06 10:59:53 -08001757
Geremy Condra36bd3652014-02-06 19:45:10 -08001758 script.Comment("---- start making changes here ----")
1759
1760 device_specific.IncrementalOTA_InstallBegin()
1761
Yifan Hong10c530d2018-12-27 17:34:18 -08001762 block_diffs = [system_diff]
1763 progress_dict = {"system": 0.8 if vendor_diff else 0.9}
Doug Zongkerfc44a512014-08-26 13:10:25 -07001764 if vendor_diff:
Yifan Hong10c530d2018-12-27 17:34:18 -08001765 block_diffs.append(vendor_diff)
1766 progress_dict["vendor"] = 0.1
1767 if device_specific_diffs:
1768 block_diffs += device_specific_diffs
1769
1770 if OPTIONS.source_info_dict.get("use_dynamic_partitions") == "true":
1771 if OPTIONS.target_info_dict.get("use_dynamic_partitions") != "true":
1772 raise RuntimeError(
1773 "can't generate incremental that disables dynamic partitions")
1774 dynamic_partitions_diff = common.DynamicPartitionsDifference(
1775 info_dict=OPTIONS.target_info_dict,
1776 source_info_dict=OPTIONS.source_info_dict,
1777 block_diffs=block_diffs,
1778 progress_dict=progress_dict)
1779 dynamic_partitions_diff.WriteScript(
1780 script, output_zip, write_verify_script=OPTIONS.verify)
1781 else:
1782 for block_diff in block_diffs:
1783 block_diff.WriteScript(script, output_zip,
1784 progress=progress_dict.get(block_diff.partition),
1785 write_verify_script=OPTIONS.verify)
Geremy Condra36bd3652014-02-06 19:45:10 -08001786
1787 if OPTIONS.two_step:
1788 common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
1789 script.WriteRawImage("/boot", "boot.img")
Tao Bao32fcdab2018-10-12 10:30:39 -07001790 logger.info("writing full boot image (forced by two-step mode)")
Geremy Condra36bd3652014-02-06 19:45:10 -08001791
1792 if not OPTIONS.two_step:
1793 if updating_boot:
Doug Zongkerf8340082014-08-05 10:39:37 -07001794 if include_full_boot:
Tao Bao32fcdab2018-10-12 10:30:39 -07001795 logger.info("boot image changed; including full.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001796 script.Print("Installing boot image...")
1797 script.WriteRawImage("/boot", "boot.img")
1798 else:
1799 # Produce the boot image by applying a patch to the current
1800 # contents of the boot partition, and write it back to the
1801 # partition.
Tao Bao32fcdab2018-10-12 10:30:39 -07001802 logger.info("boot image changed; including patch.")
Doug Zongkerf8340082014-08-05 10:39:37 -07001803 script.Print("Patching boot image...")
1804 script.ShowProgress(0.1, 10)
Tao Bao51216552018-08-26 11:53:15 -07001805 script.PatchPartition(
1806 '{}:{}:{}:{}'.format(
1807 boot_type, boot_device, target_boot.size, target_boot.sha1),
1808 '{}:{}:{}:{}'.format(
1809 boot_type, boot_device, source_boot.size, source_boot.sha1),
1810 'boot.img.p')
Geremy Condra36bd3652014-02-06 19:45:10 -08001811 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07001812 logger.info("boot image unchanged; skipping.")
Geremy Condra36bd3652014-02-06 19:45:10 -08001813
1814 # Do device-specific installation (eg, write radio image).
1815 device_specific.IncrementalOTA_InstallEnd()
1816
1817 if OPTIONS.extra_script is not None:
1818 script.AppendExtra(OPTIONS.extra_script)
1819
Doug Zongker922206e2014-03-04 13:16:24 -08001820 if OPTIONS.wipe_user_data:
1821 script.Print("Erasing user data...")
1822 script.FormatPartition("/data")
1823
Geremy Condra36bd3652014-02-06 19:45:10 -08001824 if OPTIONS.two_step:
1825 script.AppendExtra("""
1826set_stage("%(bcb_dev)s", "");
1827endif;
1828endif;
1829""" % bcb_dev)
1830
1831 script.SetProgress(1)
Tao Bao4996cf02016-03-08 17:53:39 -08001832 # For downgrade OTAs, we prefer to use the update-binary in the source
1833 # build that is actually newer than the one in the target build.
1834 if OPTIONS.downgrade:
1835 script.AddToZip(source_zip, output_zip, input_path=OPTIONS.updater_binary)
1836 else:
1837 script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)
Tao Baod8d14be2016-02-04 14:26:02 -08001838 metadata["ota-required-cache"] = str(script.required_cache)
Tao Bao491d7e22018-02-21 13:17:22 -08001839
1840 # We haven't written the metadata entry yet, which will be handled in
1841 # FinalizeMetadata().
1842 common.ZipClose(output_zip)
1843
1844 # Sign the generated zip package unless no_signing is specified.
1845 needed_property_files = (
1846 NonAbOtaPropertyFiles(),
1847 )
1848 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Geremy Condra36bd3652014-02-06 19:45:10 -08001849
Doug Zongker32b527d2014-03-04 10:03:02 -08001850
Tao Bao15a146a2018-02-21 16:06:59 -08001851def GetTargetFilesZipForSecondaryImages(input_file, skip_postinstall=False):
Tao Baof7140c02018-01-30 17:09:24 -08001852 """Returns a target-files.zip file for generating secondary payload.
1853
1854 Although the original target-files.zip already contains secondary slot
1855 images (i.e. IMAGES/system_other.img), we need to rename the files to the
1856 ones without _other suffix. Note that we cannot instead modify the names in
1857 META/ab_partitions.txt, because there are no matching partitions on device.
1858
1859 For the partitions that don't have secondary images, the ones for primary
1860 slot will be used. This is to ensure that we always have valid boot, vbmeta,
1861 bootloader images in the inactive slot.
1862
1863 Args:
1864 input_file: The input target-files.zip file.
Tao Bao15a146a2018-02-21 16:06:59 -08001865 skip_postinstall: Whether to skip copying the postinstall config file.
Tao Baof7140c02018-01-30 17:09:24 -08001866
1867 Returns:
1868 The filename of the target-files.zip for generating secondary payload.
1869 """
1870 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1871 target_zip = zipfile.ZipFile(target_file, 'w', allowZip64=True)
1872
Tao Baodba59ee2018-01-09 13:21:02 -08001873 with zipfile.ZipFile(input_file, 'r') as input_zip:
1874 infolist = input_zip.infolist()
Tao Bao12489802018-07-12 14:47:38 -07001875
Tao Bao0ff15de2019-03-20 11:26:06 -07001876 input_tmp = common.UnzipTemp(input_file, UNZIP_PATTERN)
Tao Baodba59ee2018-01-09 13:21:02 -08001877 for info in infolist:
Tao Baof7140c02018-01-30 17:09:24 -08001878 unzipped_file = os.path.join(input_tmp, *info.filename.split('/'))
1879 if info.filename == 'IMAGES/system_other.img':
1880 common.ZipWrite(target_zip, unzipped_file, arcname='IMAGES/system.img')
1881
1882 # Primary images and friends need to be skipped explicitly.
1883 elif info.filename in ('IMAGES/system.img',
1884 'IMAGES/system.map'):
1885 pass
1886
Tao Bao15a146a2018-02-21 16:06:59 -08001887 # Skip copying the postinstall config if requested.
1888 elif skip_postinstall and info.filename == POSTINSTALL_CONFIG:
1889 pass
1890
Tao Bao12489802018-07-12 14:47:38 -07001891 elif info.filename.startswith(('META/', 'IMAGES/', 'RADIO/')):
Tao Baof7140c02018-01-30 17:09:24 -08001892 common.ZipWrite(target_zip, unzipped_file, arcname=info.filename)
1893
Tao Baof7140c02018-01-30 17:09:24 -08001894 common.ZipClose(target_zip)
1895
1896 return target_file
1897
1898
Tao Bao15a146a2018-02-21 16:06:59 -08001899def GetTargetFilesZipWithoutPostinstallConfig(input_file):
1900 """Returns a target-files.zip that's not containing postinstall_config.txt.
1901
1902 This allows brillo_update_payload script to skip writing all the postinstall
1903 hooks in the generated payload. The input target-files.zip file will be
1904 duplicated, with 'META/postinstall_config.txt' skipped. If input_file doesn't
1905 contain the postinstall_config.txt entry, the input file will be returned.
1906
1907 Args:
1908 input_file: The input target-files.zip filename.
1909
1910 Returns:
1911 The filename of target-files.zip that doesn't contain postinstall config.
1912 """
1913 # We should only make a copy if postinstall_config entry exists.
1914 with zipfile.ZipFile(input_file, 'r') as input_zip:
1915 if POSTINSTALL_CONFIG not in input_zip.namelist():
1916 return input_file
1917
1918 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1919 shutil.copyfile(input_file, target_file)
1920 common.ZipDelete(target_file, POSTINSTALL_CONFIG)
1921 return target_file
1922
1923
Yifan Hong50e79542018-11-08 17:44:12 -08001924def GetTargetFilesZipForRetrofitDynamicPartitions(input_file,
Yifan Hongb433eba2019-03-06 12:42:53 -08001925 super_block_devices,
1926 dynamic_partition_list):
Yifan Hong50e79542018-11-08 17:44:12 -08001927 """Returns a target-files.zip for retrofitting dynamic partitions.
1928
1929 This allows brillo_update_payload to generate an OTA based on the exact
1930 bits on the block devices. Postinstall is disabled.
1931
1932 Args:
1933 input_file: The input target-files.zip filename.
1934 super_block_devices: The list of super block devices
Yifan Hongb433eba2019-03-06 12:42:53 -08001935 dynamic_partition_list: The list of dynamic partitions
Yifan Hong50e79542018-11-08 17:44:12 -08001936
1937 Returns:
1938 The filename of target-files.zip with *.img replaced with super_*.img for
1939 each block device in super_block_devices.
1940 """
1941 assert super_block_devices, "No super_block_devices are specified."
1942
1943 replace = {'OTA/super_{}.img'.format(dev): 'IMAGES/{}.img'.format(dev)
Tao Bao03fecb62018-11-28 10:59:23 -08001944 for dev in super_block_devices}
Yifan Hong50e79542018-11-08 17:44:12 -08001945
1946 target_file = common.MakeTempFile(prefix="targetfiles-", suffix=".zip")
1947 shutil.copyfile(input_file, target_file)
1948
Tao Baoa3705452019-06-24 15:33:41 -07001949 with zipfile.ZipFile(input_file) as input_zip:
Yifan Hong50e79542018-11-08 17:44:12 -08001950 namelist = input_zip.namelist()
1951
Yifan Hongb433eba2019-03-06 12:42:53 -08001952 input_tmp = common.UnzipTemp(input_file, RETROFIT_DAP_UNZIP_PATTERN)
1953
1954 # Remove partitions from META/ab_partitions.txt that is in
1955 # dynamic_partition_list but not in super_block_devices so that
1956 # brillo_update_payload won't generate update for those logical partitions.
1957 ab_partitions_file = os.path.join(input_tmp, *AB_PARTITIONS.split('/'))
1958 with open(ab_partitions_file) as f:
1959 ab_partitions_lines = f.readlines()
1960 ab_partitions = [line.strip() for line in ab_partitions_lines]
1961 # Assert that all super_block_devices are in ab_partitions
1962 super_device_not_updated = [partition for partition in super_block_devices
1963 if partition not in ab_partitions]
1964 assert not super_device_not_updated, \
1965 "{} is in super_block_devices but not in {}".format(
1966 super_device_not_updated, AB_PARTITIONS)
1967 # ab_partitions -= (dynamic_partition_list - super_block_devices)
1968 new_ab_partitions = common.MakeTempFile(prefix="ab_partitions", suffix=".txt")
1969 with open(new_ab_partitions, 'w') as f:
1970 for partition in ab_partitions:
1971 if (partition in dynamic_partition_list and
1972 partition not in super_block_devices):
Tao Bao59cf0c52019-06-25 10:04:24 -07001973 logger.info("Dropping %s from ab_partitions.txt", partition)
1974 continue
Yifan Hongb433eba2019-03-06 12:42:53 -08001975 f.write(partition + "\n")
1976 to_delete = [AB_PARTITIONS]
1977
Yifan Hong50e79542018-11-08 17:44:12 -08001978 # Always skip postinstall for a retrofit update.
Yifan Hongb433eba2019-03-06 12:42:53 -08001979 to_delete += [POSTINSTALL_CONFIG]
Yifan Hong50e79542018-11-08 17:44:12 -08001980
1981 # Delete dynamic_partitions_info.txt so that brillo_update_payload thinks this
1982 # is a regular update on devices without dynamic partitions support.
1983 to_delete += [DYNAMIC_PARTITION_INFO]
1984
Tao Bao03fecb62018-11-28 10:59:23 -08001985 # Remove the existing partition images as well as the map files.
Tao Bao59cf0c52019-06-25 10:04:24 -07001986 to_delete += list(replace.values())
Tao Bao03fecb62018-11-28 10:59:23 -08001987 to_delete += ['IMAGES/{}.map'.format(dev) for dev in super_block_devices]
Yifan Hong50e79542018-11-08 17:44:12 -08001988
1989 common.ZipDelete(target_file, to_delete)
1990
Yifan Hong50e79542018-11-08 17:44:12 -08001991 target_zip = zipfile.ZipFile(target_file, 'a', allowZip64=True)
1992
1993 # Write super_{foo}.img as {foo}.img.
1994 for src, dst in replace.items():
1995 assert src in namelist, \
Tao Bao59cf0c52019-06-25 10:04:24 -07001996 'Missing {} in {}; {} cannot be written'.format(src, input_file, dst)
Yifan Hong50e79542018-11-08 17:44:12 -08001997 unzipped_file = os.path.join(input_tmp, *src.split('/'))
1998 common.ZipWrite(target_zip, unzipped_file, arcname=dst)
1999
Yifan Hongb433eba2019-03-06 12:42:53 -08002000 # Write new ab_partitions.txt file
2001 common.ZipWrite(target_zip, new_ab_partitions, arcname=AB_PARTITIONS)
2002
Yifan Hong50e79542018-11-08 17:44:12 -08002003 common.ZipClose(target_zip)
2004
2005 return target_file
2006
2007
Tao Baoc098e9e2016-01-07 13:03:56 -08002008def WriteABOTAPackageWithBrilloScript(target_file, output_file,
2009 source_file=None):
Tao Baofe5b69a2018-03-02 09:47:43 -08002010 """Generates an Android OTA package that has A/B update payload."""
Tao Baodea0f8b2016-06-20 17:55:06 -07002011 # Stage the output zip package for package signing.
Tao Bao491d7e22018-02-21 13:17:22 -08002012 if not OPTIONS.no_signing:
2013 staging_file = common.MakeTempFile(suffix='.zip')
2014 else:
2015 staging_file = output_file
Tao Baoa652c002018-03-01 19:31:38 -08002016 output_zip = zipfile.ZipFile(staging_file, "w",
Tao Baoc098e9e2016-01-07 13:03:56 -08002017 compression=zipfile.ZIP_DEFLATED)
2018
Tao Bao481bab82017-12-21 11:23:09 -08002019 if source_file is not None:
2020 target_info = BuildInfo(OPTIONS.target_info_dict, OPTIONS.oem_dicts)
2021 source_info = BuildInfo(OPTIONS.source_info_dict, OPTIONS.oem_dicts)
2022 else:
2023 target_info = BuildInfo(OPTIONS.info_dict, OPTIONS.oem_dicts)
2024 source_info = None
Tao Baoc098e9e2016-01-07 13:03:56 -08002025
Tao Bao481bab82017-12-21 11:23:09 -08002026 # Metadata to comply with Android OTA package format.
Tao Baodf3a48b2018-01-10 16:30:43 -08002027 metadata = GetPackageMetadata(target_info, source_info)
Tao Baob31892e2017-02-07 11:21:17 -08002028
Yifan Hong50e79542018-11-08 17:44:12 -08002029 if OPTIONS.retrofit_dynamic_partitions:
2030 target_file = GetTargetFilesZipForRetrofitDynamicPartitions(
Yifan Hongb433eba2019-03-06 12:42:53 -08002031 target_file, target_info.get("super_block_devices").strip().split(),
2032 target_info.get("dynamic_partition_list").strip().split())
Yifan Hong50e79542018-11-08 17:44:12 -08002033 elif OPTIONS.skip_postinstall:
Tao Bao15a146a2018-02-21 16:06:59 -08002034 target_file = GetTargetFilesZipWithoutPostinstallConfig(target_file)
2035
Tao Bao40b18822018-01-30 18:19:04 -08002036 # Generate payload.
2037 payload = Payload()
2038
2039 # Enforce a max timestamp this payload can be applied on top of.
Tao Baoff1b86e2017-10-03 14:17:57 -07002040 if OPTIONS.downgrade:
Tao Bao2a12ed72018-01-22 11:35:00 -08002041 max_timestamp = source_info.GetBuildProp("ro.build.date.utc")
Tao Baoff1b86e2017-10-03 14:17:57 -07002042 else:
2043 max_timestamp = metadata["post-timestamp"]
Tao Bao40b18822018-01-30 18:19:04 -08002044 additional_args = ["--max_timestamp", max_timestamp]
Tao Baoc098e9e2016-01-07 13:03:56 -08002045
Tao Bao40b18822018-01-30 18:19:04 -08002046 payload.Generate(target_file, source_file, additional_args)
Tao Baoc098e9e2016-01-07 13:03:56 -08002047
Tao Bao40b18822018-01-30 18:19:04 -08002048 # Sign the payload.
Tao Baof7140c02018-01-30 17:09:24 -08002049 payload_signer = PayloadSigner()
2050 payload.Sign(payload_signer)
Tao Baoc098e9e2016-01-07 13:03:56 -08002051
Tao Bao40b18822018-01-30 18:19:04 -08002052 # Write the payload into output zip.
2053 payload.WriteToZip(output_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002054
Tao Baof7140c02018-01-30 17:09:24 -08002055 # Generate and include the secondary payload that installs secondary images
2056 # (e.g. system_other.img).
2057 if OPTIONS.include_secondary:
2058 # We always include a full payload for the secondary slot, even when
2059 # building an incremental OTA. See the comments for "--include_secondary".
Tao Bao15a146a2018-02-21 16:06:59 -08002060 secondary_target_file = GetTargetFilesZipForSecondaryImages(
2061 target_file, OPTIONS.skip_postinstall)
Tao Bao667ff572018-02-10 00:02:40 -08002062 secondary_payload = Payload(secondary=True)
Tao Baodb1fe412018-02-09 23:15:05 -08002063 secondary_payload.Generate(secondary_target_file,
2064 additional_args=additional_args)
Tao Baof7140c02018-01-30 17:09:24 -08002065 secondary_payload.Sign(payload_signer)
Tao Bao667ff572018-02-10 00:02:40 -08002066 secondary_payload.WriteToZip(output_zip)
Tao Baof7140c02018-01-30 17:09:24 -08002067
Tianjie Xucfa86222016-03-07 16:31:19 -08002068 # If dm-verity is supported for the device, copy contents of care_map
2069 # into A/B OTA package.
Tao Bao21803d32017-04-19 10:16:09 -07002070 target_zip = zipfile.ZipFile(target_file, "r")
Tao Bao481bab82017-12-21 11:23:09 -08002071 if (target_info.get("verity") == "true" or
2072 target_info.get("avb_enable") == "true"):
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002073 care_map_list = [x for x in ["care_map.pb", "care_map.txt"] if
2074 "META/" + x in target_zip.namelist()]
2075
2076 # Adds care_map if either the protobuf format or the plain text one exists.
2077 if care_map_list:
2078 care_map_name = care_map_list[0]
2079 care_map_data = target_zip.read("META/" + care_map_name)
2080 # In order to support streaming, care_map needs to be packed as
Tao Bao40b18822018-01-30 18:19:04 -08002081 # ZIP_STORED.
Tianjie Xu4c05f4a2018-09-14 16:24:41 -07002082 common.ZipWriteStr(output_zip, care_map_name, care_map_data,
Tao Bao481bab82017-12-21 11:23:09 -08002083 compress_type=zipfile.ZIP_STORED)
Tianjie Xucfa86222016-03-07 16:31:19 -08002084 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002085 logger.warning("Cannot find care map file in target_file package")
Tao Bao21803d32017-04-19 10:16:09 -07002086
Tao Baobcd1d162017-08-26 13:10:26 -07002087 AddCompatibilityArchiveIfTrebleEnabled(
Tao Bao481bab82017-12-21 11:23:09 -08002088 target_zip, output_zip, target_info, source_info)
Tao Bao21803d32017-04-19 10:16:09 -07002089
Tao Bao21803d32017-04-19 10:16:09 -07002090 common.ZipClose(target_zip)
Tianjie Xucfa86222016-03-07 16:31:19 -08002091
Tao Baofe5b69a2018-03-02 09:47:43 -08002092 # We haven't written the metadata entry yet, which will be handled in
2093 # FinalizeMetadata().
Tao Baoc96316c2017-01-24 22:10:49 -08002094 common.ZipClose(output_zip)
2095
Tao Bao85f16982018-03-08 16:28:33 -08002096 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
2097 # all the info of the latter. However, system updaters and OTA servers need to
2098 # take time to switch to the new flag. We keep both of the flags for
2099 # P-timeframe, and will remove StreamingPropertyFiles in later release.
Tao Baod3fc38a2018-03-08 16:09:01 -08002100 needed_property_files = (
Tao Bao85f16982018-03-08 16:28:33 -08002101 AbOtaPropertyFiles(),
Tao Baod3fc38a2018-03-08 16:09:01 -08002102 StreamingPropertyFiles(),
2103 )
2104 FinalizeMetadata(metadata, staging_file, output_file, needed_property_files)
Tao Baoc96316c2017-01-24 22:10:49 -08002105
Tao Baoc098e9e2016-01-07 13:03:56 -08002106
Doug Zongkereef39442009-04-02 12:14:19 -07002107def main(argv):
2108
2109 def option_handler(o, a):
Tao Bao4b76a0e2017-10-31 12:13:33 -07002110 if o in ("-k", "--package_key"):
Doug Zongkereef39442009-04-02 12:14:19 -07002111 OPTIONS.package_key = a
Doug Zongkereef39442009-04-02 12:14:19 -07002112 elif o in ("-i", "--incremental_from"):
2113 OPTIONS.incremental_source = a
Tao Bao43078aa2015-04-21 14:32:35 -07002114 elif o == "--full_radio":
2115 OPTIONS.full_radio = True
leozwangaa6c1a12015-08-14 10:57:58 -07002116 elif o == "--full_bootloader":
2117 OPTIONS.full_bootloader = True
Tao Bao337633f2017-12-06 15:20:19 -08002118 elif o == "--wipe_user_data":
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002119 OPTIONS.wipe_user_data = True
Tao Bao5d182562016-02-23 11:38:39 -08002120 elif o == "--downgrade":
2121 OPTIONS.downgrade = True
2122 OPTIONS.wipe_user_data = True
Tao Bao3e6161a2017-02-28 11:48:48 -08002123 elif o == "--override_timestamp":
Tao Baofaa8e0b2018-04-12 14:31:43 -07002124 OPTIONS.downgrade = True
Michael Runge6e836112014-04-15 17:40:21 -07002125 elif o in ("-o", "--oem_settings"):
Alain Vongsouvanh7f804ba2017-02-16 13:06:55 -08002126 OPTIONS.oem_source = a.split(',')
Tao Bao8608cde2016-02-25 19:49:55 -08002127 elif o == "--oem_no_mount":
2128 OPTIONS.oem_no_mount = True
Doug Zongker1c390a22009-05-14 19:06:36 -07002129 elif o in ("-e", "--extra_script"):
2130 OPTIONS.extra_script = a
Martin Blumenstingl374e1142014-05-31 20:42:55 +02002131 elif o in ("-t", "--worker_threads"):
2132 if a.isdigit():
2133 OPTIONS.worker_threads = int(a)
2134 else:
2135 raise ValueError("Cannot parse value %r for option %r - only "
2136 "integers are allowed." % (a, o))
Doug Zongker9b23f2c2013-11-25 14:44:12 -08002137 elif o in ("-2", "--two_step"):
2138 OPTIONS.two_step = True
Tao Baof7140c02018-01-30 17:09:24 -08002139 elif o == "--include_secondary":
2140 OPTIONS.include_secondary = True
Doug Zongker26e66192014-02-20 13:22:07 -08002141 elif o == "--no_signing":
Takeshi Kanemotoe153b342013-11-14 17:20:50 +09002142 OPTIONS.no_signing = True
Dan Albert8b72aef2015-03-23 19:13:21 -07002143 elif o == "--verify":
Michael Runge63f01de2014-10-28 19:24:19 -07002144 OPTIONS.verify = True
Doug Zongker26e66192014-02-20 13:22:07 -08002145 elif o == "--block":
2146 OPTIONS.block_based = True
Doug Zongker25568482014-03-03 10:21:27 -08002147 elif o in ("-b", "--binary"):
2148 OPTIONS.updater_binary = a
Tao Bao8dcf7382015-05-21 14:09:49 -07002149 elif o == "--stash_threshold":
2150 try:
2151 OPTIONS.stash_threshold = float(a)
2152 except ValueError:
2153 raise ValueError("Cannot parse value %r for option %r - expecting "
2154 "a float" % (a, o))
Tao Baod62c6032015-11-30 09:40:20 -08002155 elif o == "--log_diff":
2156 OPTIONS.log_diff = a
Tao Baodea0f8b2016-06-20 17:55:06 -07002157 elif o == "--payload_signer":
2158 OPTIONS.payload_signer = a
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002159 elif o == "--payload_signer_args":
2160 OPTIONS.payload_signer_args = shlex.split(a)
xunchang376cc7c2019-04-08 23:04:58 -07002161 elif o == "--payload_signer_key_size":
2162 OPTIONS.payload_signer_key_size = a
Dan Willemsencea5cd22017-03-21 14:44:27 -07002163 elif o == "--extracted_input_target_files":
2164 OPTIONS.extracted_input = a
Tao Bao15a146a2018-02-21 16:06:59 -08002165 elif o == "--skip_postinstall":
2166 OPTIONS.skip_postinstall = True
Yifan Hong50e79542018-11-08 17:44:12 -08002167 elif o == "--retrofit_dynamic_partitions":
2168 OPTIONS.retrofit_dynamic_partitions = True
xunchangabfa2652019-02-19 16:27:10 -08002169 elif o == "--skip_compatibility_check":
2170 OPTIONS.skip_compatibility_check = True
xunchang1cfe2512019-02-19 14:14:48 -08002171 elif o == "--output_metadata_path":
2172 OPTIONS.output_metadata_path = a
Doug Zongkereef39442009-04-02 12:14:19 -07002173 else:
2174 return False
Doug Zongkerdbfaae52009-04-21 17:12:54 -07002175 return True
Doug Zongkereef39442009-04-02 12:14:19 -07002176
2177 args = common.ParseOptions(argv, __doc__,
Tao Bao337633f2017-12-06 15:20:19 -08002178 extra_opts="b:k:i:d:e:t:2o:",
Dan Albert8b72aef2015-03-23 19:13:21 -07002179 extra_long_opts=[
Dan Albert8b72aef2015-03-23 19:13:21 -07002180 "package_key=",
2181 "incremental_from=",
Tao Bao43078aa2015-04-21 14:32:35 -07002182 "full_radio",
leozwangaa6c1a12015-08-14 10:57:58 -07002183 "full_bootloader",
Dan Albert8b72aef2015-03-23 19:13:21 -07002184 "wipe_user_data",
Tao Bao5d182562016-02-23 11:38:39 -08002185 "downgrade",
Tao Bao3e6161a2017-02-28 11:48:48 -08002186 "override_timestamp",
Dan Albert8b72aef2015-03-23 19:13:21 -07002187 "extra_script=",
2188 "worker_threads=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002189 "two_step",
Tao Baof7140c02018-01-30 17:09:24 -08002190 "include_secondary",
Dan Albert8b72aef2015-03-23 19:13:21 -07002191 "no_signing",
2192 "block",
2193 "binary=",
2194 "oem_settings=",
Tao Bao8608cde2016-02-25 19:49:55 -08002195 "oem_no_mount",
Dan Albert8b72aef2015-03-23 19:13:21 -07002196 "verify",
Tao Bao8dcf7382015-05-21 14:09:49 -07002197 "stash_threshold=",
Tao Baod62c6032015-11-30 09:40:20 -08002198 "log_diff=",
Tao Baodea0f8b2016-06-20 17:55:06 -07002199 "payload_signer=",
Baligh Uddin2abbbd02016-06-22 12:14:16 -07002200 "payload_signer_args=",
xunchang376cc7c2019-04-08 23:04:58 -07002201 "payload_signer_key_size=",
Dan Willemsencea5cd22017-03-21 14:44:27 -07002202 "extracted_input_target_files=",
Tao Bao15a146a2018-02-21 16:06:59 -08002203 "skip_postinstall",
Yifan Hong50e79542018-11-08 17:44:12 -08002204 "retrofit_dynamic_partitions",
xunchangabfa2652019-02-19 16:27:10 -08002205 "skip_compatibility_check",
xunchang1cfe2512019-02-19 14:14:48 -08002206 "output_metadata_path=",
Dan Albert8b72aef2015-03-23 19:13:21 -07002207 ], extra_option_handler=option_handler)
Doug Zongkereef39442009-04-02 12:14:19 -07002208
2209 if len(args) != 2:
2210 common.Usage(__doc__)
2211 sys.exit(1)
2212
Tao Bao32fcdab2018-10-12 10:30:39 -07002213 common.InitLogging()
2214
Tao Bao5d182562016-02-23 11:38:39 -08002215 if OPTIONS.downgrade:
Tao Bao5d182562016-02-23 11:38:39 -08002216 # We should only allow downgrading incrementals (as opposed to full).
2217 # Otherwise the device may go back from arbitrary build with this full
2218 # OTA package.
2219 if OPTIONS.incremental_source is None:
Elliott Hughesd8a52f92016-06-20 14:35:47 -07002220 raise ValueError("Cannot generate downgradable full OTAs")
Tao Bao5d182562016-02-23 11:38:39 -08002221
Tao Bao2db13852018-01-08 22:28:57 -08002222 # Load the build info dicts from the zip directly or the extracted input
2223 # directory. We don't need to unzip the entire target-files zips, because they
2224 # won't be needed for A/B OTAs (brillo_update_payload does that on its own).
2225 # When loading the info dicts, we don't need to provide the second parameter
2226 # to common.LoadInfoDict(). Specifying the second parameter allows replacing
2227 # some properties with their actual paths, such as 'selinux_fc',
2228 # 'ramdisk_dir', which won't be used during OTA generation.
Dan Willemsencea5cd22017-03-21 14:44:27 -07002229 if OPTIONS.extracted_input is not None:
Tao Bao2db13852018-01-08 22:28:57 -08002230 OPTIONS.info_dict = common.LoadInfoDict(OPTIONS.extracted_input)
Dan Willemsencea5cd22017-03-21 14:44:27 -07002231 else:
Tao Bao2db13852018-01-08 22:28:57 -08002232 with zipfile.ZipFile(args[0], 'r') as input_zip:
2233 OPTIONS.info_dict = common.LoadInfoDict(input_zip)
Tao Baoc098e9e2016-01-07 13:03:56 -08002234
Tao Bao32fcdab2018-10-12 10:30:39 -07002235 logger.info("--- target info ---")
2236 common.DumpInfoDict(OPTIONS.info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002237
2238 # Load the source build dict if applicable.
2239 if OPTIONS.incremental_source is not None:
2240 OPTIONS.target_info_dict = OPTIONS.info_dict
2241 with zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
2242 OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
2243
Tao Bao32fcdab2018-10-12 10:30:39 -07002244 logger.info("--- source info ---")
2245 common.DumpInfoDict(OPTIONS.source_info_dict)
Tao Bao2db13852018-01-08 22:28:57 -08002246
2247 # Load OEM dicts if provided.
Tao Bao481bab82017-12-21 11:23:09 -08002248 OPTIONS.oem_dicts = _LoadOemDicts(OPTIONS.oem_source)
2249
Yifan Hong50e79542018-11-08 17:44:12 -08002250 # Assume retrofitting dynamic partitions when base build does not set
Yifan Hong50611032018-11-20 14:27:38 -08002251 # use_dynamic_partitions but target build does.
Yifan Hong50e79542018-11-08 17:44:12 -08002252 if (OPTIONS.source_info_dict and
Yifan Hong50611032018-11-20 14:27:38 -08002253 OPTIONS.source_info_dict.get("use_dynamic_partitions") != "true" and
2254 OPTIONS.target_info_dict.get("use_dynamic_partitions") == "true"):
Yifan Hong50e79542018-11-08 17:44:12 -08002255 if OPTIONS.target_info_dict.get("dynamic_partition_retrofit") != "true":
2256 raise common.ExternalError(
2257 "Expect to generate incremental OTA for retrofitting dynamic "
2258 "partitions, but dynamic_partition_retrofit is not set in target "
2259 "build.")
2260 logger.info("Implicitly generating retrofit incremental OTA.")
2261 OPTIONS.retrofit_dynamic_partitions = True
2262
2263 # Skip postinstall for retrofitting dynamic partitions.
2264 if OPTIONS.retrofit_dynamic_partitions:
2265 OPTIONS.skip_postinstall = True
2266
Tao Baoc098e9e2016-01-07 13:03:56 -08002267 ab_update = OPTIONS.info_dict.get("ab_update") == "true"
2268
Christian Oderf63e2cd2017-05-01 22:30:15 +02002269 # Use the default key to sign the package if not specified with package_key.
2270 # package_keys are needed on ab_updates, so always define them if an
2271 # ab_update is getting created.
2272 if not OPTIONS.no_signing or ab_update:
2273 if OPTIONS.package_key is None:
2274 OPTIONS.package_key = OPTIONS.info_dict.get(
2275 "default_system_dev_certificate",
Dan Willemsen0ab1be62019-04-09 21:35:37 -07002276 "build/make/target/product/security/testkey")
Christian Oderf63e2cd2017-05-01 22:30:15 +02002277 # Get signing keys
2278 OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
2279
Tao Baoc098e9e2016-01-07 13:03:56 -08002280 if ab_update:
Tao Baoc098e9e2016-01-07 13:03:56 -08002281 WriteABOTAPackageWithBrilloScript(
2282 target_file=args[0],
2283 output_file=args[1],
2284 source_file=OPTIONS.incremental_source)
2285
Tao Bao32fcdab2018-10-12 10:30:39 -07002286 logger.info("done.")
Tao Baoc098e9e2016-01-07 13:03:56 -08002287 return
2288
Tao Bao2db13852018-01-08 22:28:57 -08002289 # Sanity check the loaded info dicts first.
2290 if OPTIONS.info_dict.get("no_recovery") == "true":
2291 raise common.ExternalError(
2292 "--- target build has specified no recovery ---")
2293
2294 # Non-A/B OTAs rely on /cache partition to store temporary files.
2295 cache_size = OPTIONS.info_dict.get("cache_size")
2296 if cache_size is None:
Tao Bao32fcdab2018-10-12 10:30:39 -07002297 logger.warning("--- can't determine the cache partition size ---")
Tao Bao2db13852018-01-08 22:28:57 -08002298 OPTIONS.cache_size = cache_size
2299
Doug Zongker1c390a22009-05-14 19:06:36 -07002300 if OPTIONS.extra_script is not None:
Tao Bao59cf0c52019-06-25 10:04:24 -07002301 with open(OPTIONS.extra_script) as fp:
2302 OPTIONS.extra_script = fp.read()
Doug Zongker1c390a22009-05-14 19:06:36 -07002303
Dan Willemsencea5cd22017-03-21 14:44:27 -07002304 if OPTIONS.extracted_input is not None:
2305 OPTIONS.input_tmp = OPTIONS.extracted_input
Dan Willemsencea5cd22017-03-21 14:44:27 -07002306 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002307 logger.info("unzipping target target-files...")
Tao Baodba59ee2018-01-09 13:21:02 -08002308 OPTIONS.input_tmp = common.UnzipTemp(args[0], UNZIP_PATTERN)
Tao Bao2db13852018-01-08 22:28:57 -08002309 OPTIONS.target_tmp = OPTIONS.input_tmp
Doug Zongkerfdd8e692009-08-03 17:27:48 -07002310
Tao Bao2db13852018-01-08 22:28:57 -08002311 # If the caller explicitly specified the device-specific extensions path via
2312 # -s / --device_specific, use that. Otherwise, use META/releasetools.py if it
2313 # is present in the target target_files. Otherwise, take the path of the file
2314 # from 'tool_extensions' in the info dict and look for that in the local
2315 # filesystem, relative to the current directory.
Doug Zongker37974732010-09-16 17:44:38 -07002316 if OPTIONS.device_specific is None:
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002317 from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
2318 if os.path.exists(from_input):
Tao Bao32fcdab2018-10-12 10:30:39 -07002319 logger.info("(using device-specific extensions from target_files)")
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002320 OPTIONS.device_specific = from_input
2321 else:
Tao Bao2db13852018-01-08 22:28:57 -08002322 OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions")
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002323
Doug Zongker37974732010-09-16 17:44:38 -07002324 if OPTIONS.device_specific is not None:
Doug Zongkereb0a78a2014-01-27 10:01:06 -08002325 OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)
Doug Zongker37974732010-09-16 17:44:38 -07002326
Tao Bao767e3ac2015-11-10 12:19:19 -08002327 # Generate a full OTA.
Tao Bao0c6a4142017-12-15 10:11:19 -08002328 if OPTIONS.incremental_source is None:
Tao Baodba59ee2018-01-09 13:21:02 -08002329 with zipfile.ZipFile(args[0], 'r') as input_zip:
Tao Bao491d7e22018-02-21 13:17:22 -08002330 WriteFullOTAPackage(
2331 input_zip,
2332 output_file=args[1])
Tao Bao767e3ac2015-11-10 12:19:19 -08002333
Tao Bao32b80dc2018-01-08 22:50:47 -08002334 # Generate an incremental OTA.
Tao Bao767e3ac2015-11-10 12:19:19 -08002335 else:
Tao Bao32fcdab2018-10-12 10:30:39 -07002336 logger.info("unzipping source target-files...")
Tao Baodba59ee2018-01-09 13:21:02 -08002337 OPTIONS.source_tmp = common.UnzipTemp(
2338 OPTIONS.incremental_source, UNZIP_PATTERN)
2339 with zipfile.ZipFile(args[0], 'r') as input_zip, \
2340 zipfile.ZipFile(OPTIONS.incremental_source, 'r') as source_zip:
Tao Bao491d7e22018-02-21 13:17:22 -08002341 WriteBlockIncrementalOTAPackage(
2342 input_zip,
2343 source_zip,
2344 output_file=args[1])
Tao Bao32b80dc2018-01-08 22:50:47 -08002345
2346 if OPTIONS.log_diff:
2347 with open(OPTIONS.log_diff, 'w') as out_file:
Tao Baod62c6032015-11-30 09:40:20 -08002348 import target_files_diff
Tao Bao32b80dc2018-01-08 22:50:47 -08002349 target_files_diff.recursiveDiff(
2350 '', OPTIONS.source_tmp, OPTIONS.input_tmp, out_file)
Doug Zongker62d4f182014-08-04 16:06:43 -07002351
Tao Bao32fcdab2018-10-12 10:30:39 -07002352 logger.info("done.")
Doug Zongkereef39442009-04-02 12:14:19 -07002353
2354
2355if __name__ == '__main__':
2356 try:
Ying Wang7e6d4e42010-12-13 16:25:36 -08002357 common.CloseInheritedPipes()
Doug Zongkereef39442009-04-02 12:14:19 -07002358 main(sys.argv[1:])
Tao Bao32fcdab2018-10-12 10:30:39 -07002359 except common.ExternalError:
2360 logger.exception("\n ERROR:\n")
Doug Zongkereef39442009-04-02 12:14:19 -07002361 sys.exit(1)
Doug Zongkerfc44a512014-08-26 13:10:25 -07002362 finally:
2363 common.Cleanup()