blob: 68c6887f87ffa91ae261612a314a7ef9bd981a20 [file] [log] [blame]
Kelvin Zhangcff4d762020-07-29 16:37:51 -04001# Copyright (C) 2020 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import copy
16import itertools
Yifan Hong125d0b62020-09-24 17:07:03 -070017import logging
Kelvin Zhangcff4d762020-07-29 16:37:51 -040018import os
Kelvin Zhangb9fdf2d2022-08-12 14:07:31 -070019import shutil
Kelvin Zhang25ab9982021-06-22 09:51:34 -040020import struct
Kelvin Zhangcff4d762020-07-29 16:37:51 -040021import zipfile
22
Tianjiea2076132020-08-19 17:25:32 -070023import ota_metadata_pb2
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +000024import common
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -070025import fnmatch
26from common import (ZipDelete, DoesInputFileContain, ReadBytesFromInputFile, OPTIONS, MakeTempFile,
Kelvin Zhangcff4d762020-07-29 16:37:51 -040027 ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
TJ Rhoades6f488e92022-05-01 22:16:22 -070028 SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps,
Kelvin Zhangfcd731e2023-04-04 10:28:11 -070029 GetRamdiskFormat, ParseUpdateEngineConfig)
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +000030from payload_signer import PayloadSigner
31
Kelvin Zhangcff4d762020-07-29 16:37:51 -040032
Yifan Hong125d0b62020-09-24 17:07:03 -070033logger = logging.getLogger(__name__)
Kelvin Zhang2e417382020-08-20 11:33:11 -040034
35OPTIONS.no_signing = False
36OPTIONS.force_non_ab = False
37OPTIONS.wipe_user_data = False
38OPTIONS.downgrade = False
39OPTIONS.key_passwords = {}
40OPTIONS.package_key = None
41OPTIONS.incremental_source = None
42OPTIONS.retrofit_dynamic_partitions = False
43OPTIONS.output_metadata_path = None
44OPTIONS.boot_variable_file = None
45
Kelvin Zhangcff4d762020-07-29 16:37:51 -040046METADATA_NAME = 'META-INF/com/android/metadata'
Tianjiea2076132020-08-19 17:25:32 -070047METADATA_PROTO_NAME = 'META-INF/com/android/metadata.pb'
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -070048UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*',
49 'RADIO/*', '*/build.prop', '*/default.prop', '*/build.default', "*/etc/vintf/*"]
Kelvin Zhang05ff7052021-02-10 09:13:26 -050050SECURITY_PATCH_LEVEL_PROP_NAME = "ro.build.version.security_patch"
Kelvin Zhang22680912023-05-19 13:12:59 -070051TARGET_FILES_IMAGES_SUBDIR = ["IMAGES", "PREBUILT_IMAGES", "RADIO"]
Kelvin Zhang05ff7052021-02-10 09:13:26 -050052
Kelvin Zhangcff4d762020-07-29 16:37:51 -040053
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000054def FinalizeMetadata(metadata, input_file, output_file, needed_property_files=None, package_key=None, pw=None):
Kelvin Zhangcff4d762020-07-29 16:37:51 -040055 """Finalizes the metadata and signs an A/B OTA package.
56
57 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
58 that contains the offsets and sizes for the ZIP entries. An example
59 property-files string is as follows.
60
61 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
62
63 OTA server can pass down this string, in addition to the package URL, to the
64 system update client. System update client can then fetch individual ZIP
65 entries (ZIP_STORED) directly at the given offset of the URL.
66
67 Args:
68 metadata: The metadata dict for the package.
69 input_file: The input ZIP filename that doesn't contain the package METADATA
70 entry yet.
71 output_file: The final output ZIP filename.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000072 needed_property_files: The list of PropertyFiles' to be generated. Default is [AbOtaPropertyFiles(), StreamingPropertyFiles()]
73 package_key: The key used to sign this OTA package
74 pw: Password for the package_key
Kelvin Zhangcff4d762020-07-29 16:37:51 -040075 """
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000076 no_signing = package_key is None
77
78 if needed_property_files is None:
79 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
80 # all the info of the latter. However, system updaters and OTA servers need to
81 # take time to switch to the new flag. We keep both of the flags for
82 # P-timeframe, and will remove StreamingPropertyFiles in later release.
83 needed_property_files = (
84 AbOtaPropertyFiles(),
85 StreamingPropertyFiles(),
86 )
Kelvin Zhangcff4d762020-07-29 16:37:51 -040087
88 def ComputeAllPropertyFiles(input_file, needed_property_files):
89 # Write the current metadata entry with placeholders.
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -070090 with zipfile.ZipFile(input_file, 'r', allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040091 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070092 metadata.property_files[property_files.name] = property_files.Compute(
93 input_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040094
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -070095 ZipDelete(input_file, [METADATA_NAME, METADATA_PROTO_NAME], True)
96 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as output_zip:
97 WriteMetadata(metadata, output_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040098
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000099 if no_signing:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400100 return input_file
101
102 prelim_signing = MakeTempFile(suffix='.zip')
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000103 SignOutput(input_file, prelim_signing, package_key, pw)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400104 return prelim_signing
105
106 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -0700107 with zipfile.ZipFile(prelim_signing, 'r', allowZip64=True) as prelim_signing_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400108 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700109 metadata.property_files[property_files.name] = property_files.Finalize(
110 prelim_signing_zip,
111 len(metadata.property_files[property_files.name]))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400112
113 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
114 # entries, as well as padding the entry headers. We do a preliminary signing
115 # (with an incomplete metadata entry) to allow that to happen. Then compute
116 # the ZIP entry offsets, write back the final metadata and do the final
117 # signing.
118 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
119 try:
120 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
121 except PropertyFiles.InsufficientSpaceException:
122 # Even with the preliminary signing, the entry orders may change
123 # dramatically, which leads to insufficiently reserved space during the
124 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
125 # preliminary signing works, based on the already ordered ZIP entries, to
126 # address the issue.
127 prelim_signing = ComputeAllPropertyFiles(
128 prelim_signing, needed_property_files)
129 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
130
131 # Replace the METADATA entry.
Tianjiea2076132020-08-19 17:25:32 -0700132 ZipDelete(prelim_signing, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -0700133 with zipfile.ZipFile(prelim_signing, 'a', allowZip64=True) as output_zip:
134 WriteMetadata(metadata, output_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400135
136 # Re-sign the package after updating the metadata entry.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000137 if no_signing:
Kelvin Zhangf80e8862023-01-20 10:18:11 -0800138 logger.info(f"Signing disabled for output file {output_file}")
Kelvin Zhangb9fdf2d2022-08-12 14:07:31 -0700139 shutil.copy(prelim_signing, output_file)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400140 else:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700141 logger.info(
142 f"Signing the output file {output_file} with key {package_key}")
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000143 SignOutput(prelim_signing, output_file, package_key, pw)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400144
145 # Reopen the final signed zip to double check the streaming metadata.
Kelvin Zhang928c2342020-09-22 16:15:57 -0400146 with zipfile.ZipFile(output_file, allowZip64=True) as output_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400147 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700148 property_files.Verify(
149 output_zip, metadata.property_files[property_files.name].strip())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400150
151 # If requested, dump the metadata to a separate file.
152 output_metadata_path = OPTIONS.output_metadata_path
153 if output_metadata_path:
154 WriteMetadata(metadata, output_metadata_path)
155
156
Tianjiea2076132020-08-19 17:25:32 -0700157def WriteMetadata(metadata_proto, output):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400158 """Writes the metadata to the zip archive or a file.
159
160 Args:
Tianjiea2076132020-08-19 17:25:32 -0700161 metadata_proto: The metadata protobuf for the package.
162 output: A ZipFile object or a string of the output file path. If a string
163 path is given, the metadata in the protobuf format will be written to
164 {output}.pb, e.g. ota_metadata.pb
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400165 """
166
Tianjiea2076132020-08-19 17:25:32 -0700167 metadata_dict = BuildLegacyOtaMetadata(metadata_proto)
168 legacy_metadata = "".join(["%s=%s\n" % kv for kv in
169 sorted(metadata_dict.items())])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400170 if isinstance(output, zipfile.ZipFile):
Tianjiea2076132020-08-19 17:25:32 -0700171 ZipWriteStr(output, METADATA_PROTO_NAME, metadata_proto.SerializeToString(),
172 compress_type=zipfile.ZIP_STORED)
173 ZipWriteStr(output, METADATA_NAME, legacy_metadata,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400174 compress_type=zipfile.ZIP_STORED)
175 return
176
Cole Faustb820bcd2021-10-28 13:59:48 -0700177 with open('{}.pb'.format(output), 'wb') as f:
Tianjiea2076132020-08-19 17:25:32 -0700178 f.write(metadata_proto.SerializeToString())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400179 with open(output, 'w') as f:
Tianjiea2076132020-08-19 17:25:32 -0700180 f.write(legacy_metadata)
181
182
183def UpdateDeviceState(device_state, build_info, boot_variable_values,
184 is_post_build):
185 """Update the fields of the DeviceState proto with build info."""
186
Tianjie2bb14862020-08-28 16:24:34 -0700187 def UpdatePartitionStates(partition_states):
188 """Update the per-partition state according to its build.prop"""
Kelvin Zhang39aea442020-08-17 11:04:25 -0400189 if not build_info.is_ab:
190 return
Tianjie2bb14862020-08-28 16:24:34 -0700191 build_info_set = ComputeRuntimeBuildInfos(build_info,
192 boot_variable_values)
Kelvin Zhang39aea442020-08-17 11:04:25 -0400193 assert "ab_partitions" in build_info.info_dict,\
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500194 "ab_partitions property required for ab update."
Kelvin Zhang39aea442020-08-17 11:04:25 -0400195 ab_partitions = set(build_info.info_dict.get("ab_partitions"))
196
197 # delta_generator will error out on unused timestamps,
198 # so only generate timestamps for dynamic partitions
199 # used in OTA update.
Yifan Hong5057b952021-01-07 14:09:57 -0800200 for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
Tianjie2bb14862020-08-28 16:24:34 -0700201 partition_prop = build_info.info_dict.get(
202 '{}.build.prop'.format(partition))
203 # Skip if the partition is missing, or it doesn't have a build.prop
204 if not partition_prop or not partition_prop.build_props:
205 continue
206
207 partition_state = partition_states.add()
208 partition_state.partition_name = partition
209 # Update the partition's runtime device names and fingerprints
210 partition_devices = set()
211 partition_fingerprints = set()
212 for runtime_build_info in build_info_set:
213 partition_devices.add(
214 runtime_build_info.GetPartitionBuildProp('ro.product.device',
215 partition))
216 partition_fingerprints.add(
217 runtime_build_info.GetPartitionFingerprint(partition))
218
219 partition_state.device.extend(sorted(partition_devices))
220 partition_state.build.extend(sorted(partition_fingerprints))
221
222 # TODO(xunchang) set the boot image's version with kmi. Note the boot
223 # image doesn't have a file map.
224 partition_state.version = build_info.GetPartitionBuildProp(
225 'ro.build.date.utc', partition)
226
227 # TODO(xunchang), we can save a call to ComputeRuntimeBuildInfos.
Tianjiea2076132020-08-19 17:25:32 -0700228 build_devices, build_fingerprints = \
229 CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values)
230 device_state.device.extend(sorted(build_devices))
231 device_state.build.extend(sorted(build_fingerprints))
232 device_state.build_incremental = build_info.GetBuildProp(
233 'ro.build.version.incremental')
234
Tianjie2bb14862020-08-28 16:24:34 -0700235 UpdatePartitionStates(device_state.partition_state)
Tianjiea2076132020-08-19 17:25:32 -0700236
237 if is_post_build:
238 device_state.sdk_level = build_info.GetBuildProp(
239 'ro.build.version.sdk')
240 device_state.security_patch_level = build_info.GetBuildProp(
241 'ro.build.version.security_patch')
242 # Use the actual post-timestamp, even for a downgrade case.
243 device_state.timestamp = int(build_info.GetBuildProp('ro.build.date.utc'))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400244
245
246def GetPackageMetadata(target_info, source_info=None):
Tianjiea2076132020-08-19 17:25:32 -0700247 """Generates and returns the metadata proto.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400248
Tianjiea2076132020-08-19 17:25:32 -0700249 It generates a ota_metadata protobuf that contains the info to be written
250 into an OTA package (META-INF/com/android/metadata.pb). It also handles the
251 detection of downgrade / data wipe based on the global options.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400252
253 Args:
254 target_info: The BuildInfo instance that holds the target build info.
255 source_info: The BuildInfo instance that holds the source build info, or
256 None if generating full OTA.
257
258 Returns:
Tianjiea2076132020-08-19 17:25:32 -0700259 A protobuf to be written into package metadata entry.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400260 """
261 assert isinstance(target_info, BuildInfo)
262 assert source_info is None or isinstance(source_info, BuildInfo)
263
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400264 boot_variable_values = {}
265 if OPTIONS.boot_variable_file:
266 d = LoadDictionaryFromFile(OPTIONS.boot_variable_file)
267 for key, values in d.items():
268 boot_variable_values[key] = [val.strip() for val in values.split(',')]
269
Tianjiea2076132020-08-19 17:25:32 -0700270 metadata_proto = ota_metadata_pb2.OtaMetadata()
271 # TODO(xunchang) some fields, e.g. post-device isn't necessary. We can
272 # consider skipping them if they aren't used by clients.
273 UpdateDeviceState(metadata_proto.postcondition, target_info,
274 boot_variable_values, True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400275
276 if target_info.is_ab and not OPTIONS.force_non_ab:
Tianjiea2076132020-08-19 17:25:32 -0700277 metadata_proto.type = ota_metadata_pb2.OtaMetadata.AB
278 metadata_proto.required_cache = 0
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400279 else:
Tianjiea2076132020-08-19 17:25:32 -0700280 metadata_proto.type = ota_metadata_pb2.OtaMetadata.BLOCK
281 # cache requirement will be updated by the non-A/B codes.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400282
283 if OPTIONS.wipe_user_data:
Tianjiea2076132020-08-19 17:25:32 -0700284 metadata_proto.wipe = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400285
286 if OPTIONS.retrofit_dynamic_partitions:
Tianjiea2076132020-08-19 17:25:32 -0700287 metadata_proto.retrofit_dynamic_partitions = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400288
289 is_incremental = source_info is not None
290 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700291 UpdateDeviceState(metadata_proto.precondition, source_info,
292 boot_variable_values, False)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400293 else:
Tianjiea2076132020-08-19 17:25:32 -0700294 metadata_proto.precondition.device.extend(
295 metadata_proto.postcondition.device)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400296
297 # Detect downgrades and set up downgrade flags accordingly.
298 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700299 HandleDowngradeMetadata(metadata_proto, target_info, source_info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400300
Tianjiea2076132020-08-19 17:25:32 -0700301 return metadata_proto
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400302
303
Tianjiea2076132020-08-19 17:25:32 -0700304def BuildLegacyOtaMetadata(metadata_proto):
305 """Converts the metadata proto to a legacy metadata dict.
306
307 This metadata dict is used to build the legacy metadata text file for
308 backward compatibility. We won't add new keys to the legacy metadata format.
309 If new information is needed, we should add it as a new field in OtaMetadata
310 proto definition.
311 """
312
313 separator = '|'
314
315 metadata_dict = {}
316 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB:
317 metadata_dict['ota-type'] = 'AB'
318 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK:
319 metadata_dict['ota-type'] = 'BLOCK'
320 if metadata_proto.wipe:
321 metadata_dict['ota-wipe'] = 'yes'
322 if metadata_proto.retrofit_dynamic_partitions:
323 metadata_dict['ota-retrofit-dynamic-partitions'] = 'yes'
324 if metadata_proto.downgrade:
325 metadata_dict['ota-downgrade'] = 'yes'
326
327 metadata_dict['ota-required-cache'] = str(metadata_proto.required_cache)
328
329 post_build = metadata_proto.postcondition
330 metadata_dict['post-build'] = separator.join(post_build.build)
331 metadata_dict['post-build-incremental'] = post_build.build_incremental
332 metadata_dict['post-sdk-level'] = post_build.sdk_level
333 metadata_dict['post-security-patch-level'] = post_build.security_patch_level
334 metadata_dict['post-timestamp'] = str(post_build.timestamp)
335
336 pre_build = metadata_proto.precondition
337 metadata_dict['pre-device'] = separator.join(pre_build.device)
338 # incremental updates
339 if len(pre_build.build) != 0:
340 metadata_dict['pre-build'] = separator.join(pre_build.build)
341 metadata_dict['pre-build-incremental'] = pre_build.build_incremental
342
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500343 if metadata_proto.spl_downgrade:
344 metadata_dict['spl-downgrade'] = 'yes'
Tianjiea2076132020-08-19 17:25:32 -0700345 metadata_dict.update(metadata_proto.property_files)
346
347 return metadata_dict
348
349
350def HandleDowngradeMetadata(metadata_proto, target_info, source_info):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400351 # Only incremental OTAs are allowed to reach here.
352 assert OPTIONS.incremental_source is not None
353
354 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
355 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
356 is_downgrade = int(post_timestamp) < int(pre_timestamp)
357
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500358 if OPTIONS.spl_downgrade:
359 metadata_proto.spl_downgrade = True
360
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400361 if OPTIONS.downgrade:
362 if not is_downgrade:
363 raise RuntimeError(
364 "--downgrade or --override_timestamp specified but no downgrade "
365 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tianjiea2076132020-08-19 17:25:32 -0700366 metadata_proto.downgrade = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400367 else:
368 if is_downgrade:
369 raise RuntimeError(
370 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
371 "Need to specify --override_timestamp OR --downgrade to allow "
372 "building the incremental." % (pre_timestamp, post_timestamp))
373
374
Tianjie2bb14862020-08-28 16:24:34 -0700375def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
376 """Returns a set of build info objects that may exist during runtime."""
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400377
Tianjie2bb14862020-08-28 16:24:34 -0700378 build_info_set = {default_build_info}
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400379 if not boot_variable_values:
Tianjie2bb14862020-08-28 16:24:34 -0700380 return build_info_set
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400381
382 # Calculate all possible combinations of the values for the boot variables.
383 keys = boot_variable_values.keys()
384 value_list = boot_variable_values.values()
385 combinations = [dict(zip(keys, values))
386 for values in itertools.product(*value_list)]
387 for placeholder_values in combinations:
388 # Reload the info_dict as some build properties may change their values
389 # based on the value of ro.boot* properties.
Tianjie2bb14862020-08-28 16:24:34 -0700390 info_dict = copy.deepcopy(default_build_info.info_dict)
Yifan Hong5057b952021-01-07 14:09:57 -0800391 for partition in PARTITIONS_WITH_BUILD_PROP:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400392 partition_prop_key = "{}.build.prop".format(partition)
393 input_file = info_dict[partition_prop_key].input_file
TJ Rhoades6f488e92022-05-01 22:16:22 -0700394 ramdisk = GetRamdiskFormat(info_dict)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400395 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400396 with zipfile.ZipFile(input_file.filename, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400397 info_dict[partition_prop_key] = \
398 PartitionBuildProps.FromInputFile(input_zip, partition,
TJ Rhoades6f488e92022-05-01 22:16:22 -0700399 placeholder_values,
400 ramdisk)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400401 else:
402 info_dict[partition_prop_key] = \
403 PartitionBuildProps.FromInputFile(input_file, partition,
TJ Rhoades6f488e92022-05-01 22:16:22 -0700404 placeholder_values,
405 ramdisk)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400406 info_dict["build.prop"] = info_dict["system.build.prop"]
Tianjie2bb14862020-08-28 16:24:34 -0700407 build_info_set.add(BuildInfo(info_dict, default_build_info.oem_dicts))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400408
Tianjie2bb14862020-08-28 16:24:34 -0700409 return build_info_set
410
411
412def CalculateRuntimeDevicesAndFingerprints(default_build_info,
413 boot_variable_values):
414 """Returns a tuple of sets for runtime devices and fingerprints"""
415
416 device_names = set()
417 fingerprints = set()
418 build_info_set = ComputeRuntimeBuildInfos(default_build_info,
419 boot_variable_values)
420 for runtime_build_info in build_info_set:
421 device_names.add(runtime_build_info.device)
422 fingerprints.add(runtime_build_info.fingerprint)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400423 return device_names, fingerprints
424
425
Kelvin Zhang25ab9982021-06-22 09:51:34 -0400426def GetZipEntryOffset(zfp, entry_info):
427 """Get offset to a beginning of a particular zip entry
428 Args:
429 fp: zipfile.ZipFile
430 entry_info: zipfile.ZipInfo
431
432 Returns:
433 (offset, size) tuple
434 """
435 # Don't use len(entry_info.extra). Because that returns size of extra
436 # fields in central directory. We need to look at local file directory,
437 # as these two might have different sizes.
438
439 # We cannot work with zipfile.ZipFile instances, we need a |fp| for the underlying file.
440 zfp = zfp.fp
441 zfp.seek(entry_info.header_offset)
442 data = zfp.read(zipfile.sizeFileHeader)
443 fheader = struct.unpack(zipfile.structFileHeader, data)
444 # Last two fields of local file header are filename length and
445 # extra length
446 filename_len = fheader[-2]
447 extra_len = fheader[-1]
448 offset = entry_info.header_offset
449 offset += zipfile.sizeFileHeader
450 offset += filename_len + extra_len
451 size = entry_info.file_size
452 return (offset, size)
453
454
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400455class PropertyFiles(object):
456 """A class that computes the property-files string for an OTA package.
457
458 A property-files string is a comma-separated string that contains the
459 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
460 can be fetched directly with the package URL along with the offset/size info.
461 These strings can be used for streaming A/B OTAs, or allowing an updater to
462 download package metadata entry directly, without paying the cost of
463 downloading entire package.
464
465 Computing the final property-files string requires two passes. Because doing
466 the whole package signing (with signapk.jar) will possibly reorder the ZIP
467 entries, which may in turn invalidate earlier computed ZIP entry offset/size
468 values.
469
470 This class provides functions to be called for each pass. The general flow is
471 as follows.
472
473 property_files = PropertyFiles()
474 # The first pass, which writes placeholders before doing initial signing.
475 property_files.Compute()
476 SignOutput()
477
478 # The second pass, by replacing the placeholders with actual data.
479 property_files.Finalize()
480 SignOutput()
481
482 And the caller can additionally verify the final result.
483
484 property_files.Verify()
485 """
486
487 def __init__(self):
488 self.name = None
489 self.required = ()
490 self.optional = ()
491
492 def Compute(self, input_zip):
493 """Computes and returns a property-files string with placeholders.
494
495 We reserve extra space for the offset and size of the metadata entry itself,
496 although we don't know the final values until the package gets signed.
497
498 Args:
499 input_zip: The input ZIP file.
500
501 Returns:
502 A string with placeholders for the metadata offset/size info, e.g.
503 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
504 """
505 return self.GetPropertyFilesString(input_zip, reserve_space=True)
506
507 class InsufficientSpaceException(Exception):
508 pass
509
510 def Finalize(self, input_zip, reserved_length):
511 """Finalizes a property-files string with actual METADATA offset/size info.
512
513 The input ZIP file has been signed, with the ZIP entries in the desired
514 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
515 the ZIP entry offsets and construct the property-files string with actual
516 data. Note that during this process, we must pad the property-files string
517 to the reserved length, so that the METADATA entry size remains the same.
518 Otherwise the entries' offsets and sizes may change again.
519
520 Args:
521 input_zip: The input ZIP file.
522 reserved_length: The reserved length of the property-files string during
523 the call to Compute(). The final string must be no more than this
524 size.
525
526 Returns:
527 A property-files string including the metadata offset/size info, e.g.
528 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
529
530 Raises:
531 InsufficientSpaceException: If the reserved length is insufficient to hold
532 the final string.
533 """
534 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
535 if len(result) > reserved_length:
536 raise self.InsufficientSpaceException(
537 'Insufficient reserved space: reserved={}, actual={}'.format(
538 reserved_length, len(result)))
539
540 result += ' ' * (reserved_length - len(result))
541 return result
542
543 def Verify(self, input_zip, expected):
544 """Verifies the input ZIP file contains the expected property-files string.
545
546 Args:
547 input_zip: The input ZIP file.
548 expected: The property-files string that's computed from Finalize().
549
550 Raises:
551 AssertionError: On finding a mismatch.
552 """
553 actual = self.GetPropertyFilesString(input_zip)
554 assert actual == expected, \
555 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
556
557 def GetPropertyFilesString(self, zip_file, reserve_space=False):
558 """
559 Constructs the property-files string per request.
560
561 Args:
562 zip_file: The input ZIP file.
563 reserved_length: The reserved length of the property-files string.
564
565 Returns:
566 A property-files string including the metadata offset/size info, e.g.
567 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
568 """
569
570 def ComputeEntryOffsetSize(name):
571 """Computes the zip entry offset and size."""
572 info = zip_file.getinfo(name)
Kelvin Zhang25ab9982021-06-22 09:51:34 -0400573 (offset, size) = GetZipEntryOffset(zip_file, info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400574 return '%s:%d:%d' % (os.path.basename(name), offset, size)
575
576 tokens = []
577 tokens.extend(self._GetPrecomputed(zip_file))
578 for entry in self.required:
579 tokens.append(ComputeEntryOffsetSize(entry))
580 for entry in self.optional:
581 if entry in zip_file.namelist():
582 tokens.append(ComputeEntryOffsetSize(entry))
583
584 # 'META-INF/com/android/metadata' is required. We don't know its actual
585 # offset and length (as well as the values for other entries). So we reserve
586 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
587 # the space for metadata entry. Because 'offset' allows a max of 10-digit
588 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
589 # reserved space serves the metadata entry only.
590 if reserve_space:
591 tokens.append('metadata:' + ' ' * 15)
Tianjiea2076132020-08-19 17:25:32 -0700592 tokens.append('metadata.pb:' + ' ' * 15)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400593 else:
594 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
Luca Stefanib6075c52021-11-03 17:10:54 +0100595 if METADATA_PROTO_NAME in zip_file.namelist():
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -0700596 tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400597
598 return ','.join(tokens)
599
600 def _GetPrecomputed(self, input_zip):
601 """Computes the additional tokens to be included into the property-files.
602
603 This applies to tokens without actual ZIP entries, such as
604 payload_metadata.bin. We want to expose the offset/size to updaters, so
605 that they can download the payload metadata directly with the info.
606
607 Args:
608 input_zip: The input zip file.
609
610 Returns:
611 A list of strings (tokens) to be added to the property-files string.
612 """
613 # pylint: disable=no-self-use
614 # pylint: disable=unused-argument
615 return []
616
617
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000618def SignOutput(temp_zip_name, output_zip_name, package_key=None, pw=None):
619 if package_key is None:
620 package_key = OPTIONS.package_key
621 if pw is None and OPTIONS.key_passwords:
622 pw = OPTIONS.key_passwords[package_key]
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400623
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000624 SignFile(temp_zip_name, output_zip_name, package_key, pw,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400625 whole_file=True)
Tianjiea5fca032021-06-01 22:06:28 -0700626
627
628def ConstructOtaApexInfo(target_zip, source_file=None):
629 """If applicable, add the source version to the apex info."""
630
631 def _ReadApexInfo(input_zip):
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700632 if not DoesInputFileContain(input_zip, "META/apex_info.pb"):
Tianjiea5fca032021-06-01 22:06:28 -0700633 logger.warning("target_file doesn't contain apex_info.pb %s", input_zip)
634 return None
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700635 return ReadBytesFromInputFile(input_zip, "META/apex_info.pb")
Tianjiea5fca032021-06-01 22:06:28 -0700636
637 target_apex_string = _ReadApexInfo(target_zip)
638 # Return early if the target apex info doesn't exist or is empty.
639 if not target_apex_string:
640 return target_apex_string
641
642 # If the source apex info isn't available, just return the target info
643 if not source_file:
644 return target_apex_string
645
Kelvin Zhangca45d7a2023-04-21 09:46:47 -0700646 source_apex_string = _ReadApexInfo(source_file)
Tianjiea5fca032021-06-01 22:06:28 -0700647 if not source_apex_string:
648 return target_apex_string
649
650 source_apex_proto = ota_metadata_pb2.ApexMetadata()
651 source_apex_proto.ParseFromString(source_apex_string)
652 source_apex_versions = {apex.package_name: apex.version for apex in
653 source_apex_proto.apex_info}
654
655 # If the apex package is available in the source build, initialize the source
656 # apex version.
657 target_apex_proto = ota_metadata_pb2.ApexMetadata()
658 target_apex_proto.ParseFromString(target_apex_string)
659 for target_apex in target_apex_proto.apex_info:
660 name = target_apex.package_name
661 if name in source_apex_versions:
662 target_apex.source_version = source_apex_versions[name]
663
664 return target_apex_proto.SerializeToString()
Kelvin Zhang410bb382022-01-06 09:15:54 -0800665
666
Kelvin Zhangf2728d62022-01-10 11:42:36 -0800667def IsLz4diffCompatible(source_file: str, target_file: str):
668 """Check whether lz4diff versions in two builds are compatible
669
670 Args:
671 source_file: Path to source build's target_file.zip
672 target_file: Path to target build's target_file.zip
673
674 Returns:
675 bool true if and only if lz4diff versions are compatible
676 """
677 if source_file is None or target_file is None:
678 return False
679 # Right now we enable lz4diff as long as source build has liblz4.so.
680 # In the future we might introduce version system to lz4diff as well.
681 if zipfile.is_zipfile(source_file):
682 with zipfile.ZipFile(source_file, "r") as zfp:
683 return "META/liblz4.so" in zfp.namelist()
684 else:
685 assert os.path.isdir(source_file)
686 return os.path.exists(os.path.join(source_file, "META", "liblz4.so"))
687
688
Kelvin Zhang410bb382022-01-06 09:15:54 -0800689def IsZucchiniCompatible(source_file: str, target_file: str):
690 """Check whether zucchini versions in two builds are compatible
691
692 Args:
693 source_file: Path to source build's target_file.zip
694 target_file: Path to target build's target_file.zip
695
696 Returns:
697 bool true if and only if zucchini versions are compatible
698 """
699 if source_file is None or target_file is None:
700 return False
701 assert os.path.exists(source_file)
702 assert os.path.exists(target_file)
703
704 assert zipfile.is_zipfile(source_file) or os.path.isdir(source_file)
705 assert zipfile.is_zipfile(target_file) or os.path.isdir(target_file)
706 _ZUCCHINI_CONFIG_ENTRY_NAME = "META/zucchini_config.txt"
707
708 def ReadEntry(path, entry):
709 # Read an entry inside a .zip file or extracted dir of .zip file
710 if zipfile.is_zipfile(path):
711 with zipfile.ZipFile(path, "r", allowZip64=True) as zfp:
712 if entry in zfp.namelist():
713 return zfp.read(entry).decode()
714 else:
Zhou Xuezand0d49f52022-09-14 16:26:55 +0800715 entry_path = os.path.join(path, entry)
Kelvin Zhang410bb382022-01-06 09:15:54 -0800716 if os.path.exists(entry_path):
717 with open(entry_path, "r") as fp:
718 return fp.read()
HÃ¥kan Kvist3db1ef62022-05-03 10:19:41 +0200719 return False
720 sourceEntry = ReadEntry(source_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
721 targetEntry = ReadEntry(target_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
722 return sourceEntry and targetEntry and sourceEntry == targetEntry
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000723
724
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700725def ExtractTargetFiles(path: str):
726 if os.path.isdir(path):
727 logger.info("target files %s is already extracted", path)
728 return path
729 extracted_dir = common.MakeTempDir("target_files")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700730 common.UnzipToDir(path, extracted_dir, UNZIP_PATTERN + [""])
Kelvin Zhang22680912023-05-19 13:12:59 -0700731 for subdir in TARGET_FILES_IMAGES_SUBDIR:
732 image_dir = os.path.join(extracted_dir, subdir)
733 if not os.path.exists(image_dir):
734 continue
735 for filename in os.listdir(image_dir):
736 if not filename.endswith(".img"):
737 continue
738 common.UnsparseImage(os.path.join(image_dir, filename))
739
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700740 return extracted_dir
741
742
743def LocatePartitionPath(target_files_dir: str, partition: str, allow_empty):
744 path = os.path.join(target_files_dir, "RADIO", partition + ".img")
745 if os.path.exists(path):
746 return path
747 path = os.path.join(target_files_dir, "IMAGES", partition + ".img")
748 if os.path.exists(path):
749 return path
750 if allow_empty:
751 return ""
752 raise common.ExternalError(
753 "Partition {} not found in target files {}".format(partition, target_files_dir))
754
755
756def GetPartitionImages(target_files_dir: str, ab_partitions, allow_empty=True):
757 assert os.path.isdir(target_files_dir)
758 return ":".join([LocatePartitionPath(target_files_dir, partition, allow_empty) for partition in ab_partitions])
759
760
761def LocatePartitionMap(target_files_dir: str, partition: str):
762 path = os.path.join(target_files_dir, "RADIO", partition + ".map")
763 if os.path.exists(path):
764 return path
Kelvin Zhangb789e842023-06-13 10:34:32 -0700765 path = os.path.join(target_files_dir, "IMAGES", partition + ".map")
766 if os.path.exists(path):
767 return path
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700768 return ""
769
770
771def GetPartitionMaps(target_files_dir: str, ab_partitions):
772 assert os.path.isdir(target_files_dir)
773 return ":".join([LocatePartitionMap(target_files_dir, partition) for partition in ab_partitions])
774
775
Kelvin Zhangfa928692022-08-16 17:01:53 +0000776class PayloadGenerator(object):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000777 """Manages the creation and the signing of an A/B OTA Payload."""
778
779 PAYLOAD_BIN = 'payload.bin'
780 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
781 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
782 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
783
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700784 def __init__(self, secondary=False, wipe_user_data=False, minor_version=None, is_partial_update=False):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000785 """Initializes a Payload instance.
786
787 Args:
788 secondary: Whether it's generating a secondary payload (default: False).
789 """
790 self.payload_file = None
791 self.payload_properties = None
792 self.secondary = secondary
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000793 self.wipe_user_data = wipe_user_data
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700794 self.minor_version = minor_version
795 self.is_partial_update = is_partial_update
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000796
797 def _Run(self, cmd): # pylint: disable=no-self-use
798 # Don't pipe (buffer) the output if verbose is set. Let
799 # brillo_update_payload write to stdout/stderr directly, so its progress can
800 # be monitored.
801 if OPTIONS.verbose:
802 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
803 else:
804 common.RunAndCheckOutput(cmd)
805
806 def Generate(self, target_file, source_file=None, additional_args=None):
807 """Generates a payload from the given target-files zip(s).
808
809 Args:
810 target_file: The filename of the target build target-files zip.
811 source_file: The filename of the source build target-files zip; or None if
812 generating a full OTA.
813 additional_args: A list of additional args that should be passed to
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700814 delta_generator binary; or None.
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000815 """
816 if additional_args is None:
817 additional_args = []
818
819 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700820 target_dir = ExtractTargetFiles(target_file)
821 cmd = ["delta_generator",
822 "--out_file", payload_file]
Kelvin Zhang89b87f62023-06-01 10:23:05 -0700823 with open(os.path.join(target_dir, "META", "ab_partitions.txt"), "r") as fp:
824 ab_partitions = fp.read().strip().splitlines()
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700825 cmd.extend(["--partition_names", ":".join(ab_partitions)])
826 cmd.extend(
827 ["--new_partitions", GetPartitionImages(target_dir, ab_partitions, False)])
828 cmd.extend(
829 ["--new_mapfiles", GetPartitionMaps(target_dir, ab_partitions)])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000830 if source_file is not None:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700831 source_dir = ExtractTargetFiles(source_file)
832 cmd.extend(
833 ["--old_partitions", GetPartitionImages(source_dir, ab_partitions, True)])
834 cmd.extend(
835 ["--old_mapfiles", GetPartitionMaps(source_dir, ab_partitions)])
836
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000837 if OPTIONS.disable_fec_computation:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700838 cmd.extend(["--disable_fec_computation=true"])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000839 if OPTIONS.disable_verity_computation:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700840 cmd.extend(["--disable_verity_computation=true"])
841 postinstall_config = os.path.join(
842 target_dir, "META", "postinstall_config.txt")
843
844 if os.path.exists(postinstall_config):
845 cmd.extend(["--new_postinstall_config_file", postinstall_config])
846 dynamic_partition_info = os.path.join(
847 target_dir, "META", "dynamic_partitions_info.txt")
848
849 if os.path.exists(dynamic_partition_info):
850 cmd.extend(["--dynamic_partition_info_file", dynamic_partition_info])
851
HÃ¥kan Kvistddb968d2023-06-09 11:59:22 +0200852 apex_info = os.path.join(
853 target_dir, "META", "apex_info.pb")
854 if os.path.exists(apex_info):
855 cmd.extend(["--apex_info_file", apex_info])
856
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700857 major_version, minor_version = ParseUpdateEngineConfig(
858 os.path.join(target_dir, "META", "update_engine_config.txt"))
Kelvin Zhang629bc8d2023-04-11 21:08:27 -0700859 if source_file:
860 major_version, minor_version = ParseUpdateEngineConfig(
861 os.path.join(source_dir, "META", "update_engine_config.txt"))
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700862 if self.minor_version:
863 minor_version = self.minor_version
864 cmd.extend(["--major_version", str(major_version)])
865 if source_file is not None or self.is_partial_update:
866 cmd.extend(["--minor_version", str(minor_version)])
867 if self.is_partial_update:
868 cmd.extend(["--is_partial_update=true"])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000869 cmd.extend(additional_args)
870 self._Run(cmd)
871
872 self.payload_file = payload_file
873 self.payload_properties = None
874
875 def Sign(self, payload_signer):
876 """Generates and signs the hashes of the payload and metadata.
877
878 Args:
879 payload_signer: A PayloadSigner() instance that serves the signing work.
880
881 Raises:
882 AssertionError: On any failure when calling brillo_update_payload script.
883 """
884 assert isinstance(payload_signer, PayloadSigner)
885
886 # 1. Generate hashes of the payload and metadata files.
887 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
888 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
889 cmd = ["brillo_update_payload", "hash",
890 "--unsigned_payload", self.payload_file,
891 "--signature_size", str(payload_signer.maximum_signature_size),
892 "--metadata_hash_file", metadata_sig_file,
893 "--payload_hash_file", payload_sig_file]
894 self._Run(cmd)
895
896 # 2. Sign the hashes.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000897 signed_payload_sig_file = payload_signer.SignHashFile(payload_sig_file)
898 signed_metadata_sig_file = payload_signer.SignHashFile(metadata_sig_file)
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000899
900 # 3. Insert the signatures back into the payload file.
901 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
902 suffix=".bin")
903 cmd = ["brillo_update_payload", "sign",
904 "--unsigned_payload", self.payload_file,
905 "--payload", signed_payload_file,
906 "--signature_size", str(payload_signer.maximum_signature_size),
907 "--metadata_signature_file", signed_metadata_sig_file,
908 "--payload_signature_file", signed_payload_sig_file]
909 self._Run(cmd)
910
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000911 self.payload_file = signed_payload_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000912
913 def WriteToZip(self, output_zip):
914 """Writes the payload to the given zip.
915
916 Args:
917 output_zip: The output ZipFile instance.
918 """
919 assert self.payload_file is not None
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000920 # 4. Dump the signed payload properties.
921 properties_file = common.MakeTempFile(prefix="payload-properties-",
922 suffix=".txt")
923 cmd = ["brillo_update_payload", "properties",
924 "--payload", self.payload_file,
925 "--properties_file", properties_file]
926 self._Run(cmd)
927
928 if self.secondary:
929 with open(properties_file, "a") as f:
930 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
931
932 if self.wipe_user_data:
933 with open(properties_file, "a") as f:
934 f.write("POWERWASH=1\n")
935
936 self.payload_properties = properties_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000937
938 if self.secondary:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000939 payload_arcname = PayloadGenerator.SECONDARY_PAYLOAD_BIN
940 payload_properties_arcname = PayloadGenerator.SECONDARY_PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000941 else:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000942 payload_arcname = PayloadGenerator.PAYLOAD_BIN
943 payload_properties_arcname = PayloadGenerator.PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000944
945 # Add the signed payload file and properties into the zip. In order to
946 # support streaming, we pack them as ZIP_STORED. So these entries can be
947 # read directly with the offset and length pairs.
948 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
949 compress_type=zipfile.ZIP_STORED)
950 common.ZipWrite(output_zip, self.payload_properties,
951 arcname=payload_properties_arcname,
952 compress_type=zipfile.ZIP_STORED)
953
954
955class StreamingPropertyFiles(PropertyFiles):
956 """A subclass for computing the property-files for streaming A/B OTAs."""
957
958 def __init__(self):
959 super(StreamingPropertyFiles, self).__init__()
960 self.name = 'ota-streaming-property-files'
961 self.required = (
962 # payload.bin and payload_properties.txt must exist.
963 'payload.bin',
964 'payload_properties.txt',
965 )
966 self.optional = (
967 # apex_info.pb isn't directly used in the update flow
968 'apex_info.pb',
969 # care_map is available only if dm-verity is enabled.
970 'care_map.pb',
971 'care_map.txt',
972 # compatibility.zip is available only if target supports Treble.
973 'compatibility.zip',
974 )
975
976
977class AbOtaPropertyFiles(StreamingPropertyFiles):
978 """The property-files for A/B OTA that includes payload_metadata.bin info.
979
980 Since P, we expose one more token (aka property-file), in addition to the ones
981 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
982 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
983 doesn't exist as a separate ZIP entry, but can be used to verify if the
984 payload can be applied on the given device.
985
986 For backward compatibility, we keep both of the 'ota-streaming-property-files'
987 and the newly added 'ota-property-files' in P. The new token will only be
988 available in 'ota-property-files'.
989 """
990
991 def __init__(self):
992 super(AbOtaPropertyFiles, self).__init__()
993 self.name = 'ota-property-files'
994
995 def _GetPrecomputed(self, input_zip):
996 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
997 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
998
999 @staticmethod
1000 def _GetPayloadMetadataOffsetAndSize(input_zip):
1001 """Computes the offset and size of the payload metadata for a given package.
1002
1003 (From system/update_engine/update_metadata.proto)
1004 A delta update file contains all the deltas needed to update a system from
1005 one specific version to another specific version. The update format is
1006 represented by this struct pseudocode:
1007
1008 struct delta_update_file {
1009 char magic[4] = "CrAU";
1010 uint64 file_format_version;
1011 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
1012
1013 // Only present if format_version > 1:
1014 uint32 metadata_signature_size;
1015
1016 // The Bzip2 compressed DeltaArchiveManifest
1017 char manifest[metadata_signature_size];
1018
1019 // The signature of the metadata (from the beginning of the payload up to
1020 // this location, not including the signature itself). This is a
1021 // serialized Signatures message.
1022 char medatada_signature_message[metadata_signature_size];
1023
1024 // Data blobs for files, no specific format. The specific offset
1025 // and length of each data blob is recorded in the DeltaArchiveManifest.
1026 struct {
1027 char data[];
1028 } blobs[];
1029
1030 // These two are not signed:
1031 uint64 payload_signatures_message_size;
1032 char payload_signatures_message[];
1033 };
1034
1035 'payload-metadata.bin' contains all the bytes from the beginning of the
1036 payload, till the end of 'medatada_signature_message'.
1037 """
1038 payload_info = input_zip.getinfo('payload.bin')
1039 (payload_offset, payload_size) = GetZipEntryOffset(input_zip, payload_info)
1040
1041 # Read the underlying raw zipfile at specified offset
1042 payload_fp = input_zip.fp
1043 payload_fp.seek(payload_offset)
1044 header_bin = payload_fp.read(24)
1045
1046 # network byte order (big-endian)
1047 header = struct.unpack("!IQQL", header_bin)
1048
1049 # 'CrAU'
1050 magic = header[0]
1051 assert magic == 0x43724155, "Invalid magic: {:x}, computed offset {}" \
1052 .format(magic, payload_offset)
1053
1054 manifest_size = header[2]
1055 metadata_signature_size = header[3]
1056 metadata_total = 24 + manifest_size + metadata_signature_size
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +00001057 assert metadata_total <= payload_size
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +00001058
1059 return (payload_offset, metadata_total)
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001060
1061
1062def Fnmatch(filename, pattersn):
1063 return any([fnmatch.fnmatch(filename, pat) for pat in pattersn])
1064
1065
1066def CopyTargetFilesDir(input_dir):
1067 output_dir = common.MakeTempDir("target_files")
Kelvin Zhang22680912023-05-19 13:12:59 -07001068
1069 def SymlinkIfNotSparse(src, dst):
1070 if common.IsSparseImage(src):
1071 return common.UnsparseImage(src, dst)
1072 else:
1073 return os.link(src, dst)
1074
1075 for subdir in TARGET_FILES_IMAGES_SUBDIR:
Kelvin Zhang6b10e152023-05-02 15:48:16 -07001076 if not os.path.exists(os.path.join(input_dir, subdir)):
1077 continue
1078 shutil.copytree(os.path.join(input_dir, subdir), os.path.join(
Kelvin Zhang22680912023-05-19 13:12:59 -07001079 output_dir, subdir), dirs_exist_ok=True, copy_function=SymlinkIfNotSparse)
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001080 shutil.copytree(os.path.join(input_dir, "META"), os.path.join(
1081 output_dir, "META"), dirs_exist_ok=True)
Kelvin Zhang6b10e152023-05-02 15:48:16 -07001082
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001083 for (dirpath, _, filenames) in os.walk(input_dir):
1084 for filename in filenames:
1085 path = os.path.join(dirpath, filename)
1086 relative_path = path.removeprefix(input_dir).removeprefix("/")
1087 if not Fnmatch(relative_path, UNZIP_PATTERN):
1088 continue
1089 if filename.endswith(".prop") or filename == "prop.default" or "/etc/vintf/" in relative_path:
1090 target_path = os.path.join(
1091 output_dir, relative_path)
1092 os.makedirs(os.path.dirname(target_path), exist_ok=True)
1093 shutil.copy(path, target_path)
1094 return output_dir