blob: 80a6c7a39b11f90e4eb0a37b6a72fbc9e813f533 [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"
51
Kelvin Zhangcff4d762020-07-29 16:37:51 -040052
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000053def FinalizeMetadata(metadata, input_file, output_file, needed_property_files=None, package_key=None, pw=None):
Kelvin Zhangcff4d762020-07-29 16:37:51 -040054 """Finalizes the metadata and signs an A/B OTA package.
55
56 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
57 that contains the offsets and sizes for the ZIP entries. An example
58 property-files string is as follows.
59
60 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
61
62 OTA server can pass down this string, in addition to the package URL, to the
63 system update client. System update client can then fetch individual ZIP
64 entries (ZIP_STORED) directly at the given offset of the URL.
65
66 Args:
67 metadata: The metadata dict for the package.
68 input_file: The input ZIP filename that doesn't contain the package METADATA
69 entry yet.
70 output_file: The final output ZIP filename.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000071 needed_property_files: The list of PropertyFiles' to be generated. Default is [AbOtaPropertyFiles(), StreamingPropertyFiles()]
72 package_key: The key used to sign this OTA package
73 pw: Password for the package_key
Kelvin Zhangcff4d762020-07-29 16:37:51 -040074 """
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000075 no_signing = package_key is None
76
77 if needed_property_files is None:
78 # AbOtaPropertyFiles intends to replace StreamingPropertyFiles, as it covers
79 # all the info of the latter. However, system updaters and OTA servers need to
80 # take time to switch to the new flag. We keep both of the flags for
81 # P-timeframe, and will remove StreamingPropertyFiles in later release.
82 needed_property_files = (
83 AbOtaPropertyFiles(),
84 StreamingPropertyFiles(),
85 )
Kelvin Zhangcff4d762020-07-29 16:37:51 -040086
87 def ComputeAllPropertyFiles(input_file, needed_property_files):
88 # Write the current metadata entry with placeholders.
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -070089 with zipfile.ZipFile(input_file, 'r', allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040090 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070091 metadata.property_files[property_files.name] = property_files.Compute(
92 input_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040093
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -070094 ZipDelete(input_file, [METADATA_NAME, METADATA_PROTO_NAME], True)
95 with zipfile.ZipFile(input_file, 'a', allowZip64=True) as output_zip:
96 WriteMetadata(metadata, output_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040097
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +000098 if no_signing:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040099 return input_file
100
101 prelim_signing = MakeTempFile(suffix='.zip')
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000102 SignOutput(input_file, prelim_signing, package_key, pw)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400103 return prelim_signing
104
105 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -0700106 with zipfile.ZipFile(prelim_signing, 'r', allowZip64=True) as prelim_signing_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400107 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700108 metadata.property_files[property_files.name] = property_files.Finalize(
109 prelim_signing_zip,
110 len(metadata.property_files[property_files.name]))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400111
112 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
113 # entries, as well as padding the entry headers. We do a preliminary signing
114 # (with an incomplete metadata entry) to allow that to happen. Then compute
115 # the ZIP entry offsets, write back the final metadata and do the final
116 # signing.
117 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
118 try:
119 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
120 except PropertyFiles.InsufficientSpaceException:
121 # Even with the preliminary signing, the entry orders may change
122 # dramatically, which leads to insufficiently reserved space during the
123 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
124 # preliminary signing works, based on the already ordered ZIP entries, to
125 # address the issue.
126 prelim_signing = ComputeAllPropertyFiles(
127 prelim_signing, needed_property_files)
128 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
129
130 # Replace the METADATA entry.
Tianjiea2076132020-08-19 17:25:32 -0700131 ZipDelete(prelim_signing, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -0700132 with zipfile.ZipFile(prelim_signing, 'a', allowZip64=True) as output_zip:
133 WriteMetadata(metadata, output_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400134
135 # Re-sign the package after updating the metadata entry.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000136 if no_signing:
Kelvin Zhangf80e8862023-01-20 10:18:11 -0800137 logger.info(f"Signing disabled for output file {output_file}")
Kelvin Zhangb9fdf2d2022-08-12 14:07:31 -0700138 shutil.copy(prelim_signing, output_file)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400139 else:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700140 logger.info(
141 f"Signing the output file {output_file} with key {package_key}")
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000142 SignOutput(prelim_signing, output_file, package_key, pw)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400143
144 # Reopen the final signed zip to double check the streaming metadata.
Kelvin Zhang928c2342020-09-22 16:15:57 -0400145 with zipfile.ZipFile(output_file, allowZip64=True) as output_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400146 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700147 property_files.Verify(
148 output_zip, metadata.property_files[property_files.name].strip())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400149
150 # If requested, dump the metadata to a separate file.
151 output_metadata_path = OPTIONS.output_metadata_path
152 if output_metadata_path:
153 WriteMetadata(metadata, output_metadata_path)
154
155
Tianjiea2076132020-08-19 17:25:32 -0700156def WriteMetadata(metadata_proto, output):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400157 """Writes the metadata to the zip archive or a file.
158
159 Args:
Tianjiea2076132020-08-19 17:25:32 -0700160 metadata_proto: The metadata protobuf for the package.
161 output: A ZipFile object or a string of the output file path. If a string
162 path is given, the metadata in the protobuf format will be written to
163 {output}.pb, e.g. ota_metadata.pb
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400164 """
165
Tianjiea2076132020-08-19 17:25:32 -0700166 metadata_dict = BuildLegacyOtaMetadata(metadata_proto)
167 legacy_metadata = "".join(["%s=%s\n" % kv for kv in
168 sorted(metadata_dict.items())])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400169 if isinstance(output, zipfile.ZipFile):
Tianjiea2076132020-08-19 17:25:32 -0700170 ZipWriteStr(output, METADATA_PROTO_NAME, metadata_proto.SerializeToString(),
171 compress_type=zipfile.ZIP_STORED)
172 ZipWriteStr(output, METADATA_NAME, legacy_metadata,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400173 compress_type=zipfile.ZIP_STORED)
174 return
175
Cole Faustb820bcd2021-10-28 13:59:48 -0700176 with open('{}.pb'.format(output), 'wb') as f:
Tianjiea2076132020-08-19 17:25:32 -0700177 f.write(metadata_proto.SerializeToString())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400178 with open(output, 'w') as f:
Tianjiea2076132020-08-19 17:25:32 -0700179 f.write(legacy_metadata)
180
181
182def UpdateDeviceState(device_state, build_info, boot_variable_values,
183 is_post_build):
184 """Update the fields of the DeviceState proto with build info."""
185
Tianjie2bb14862020-08-28 16:24:34 -0700186 def UpdatePartitionStates(partition_states):
187 """Update the per-partition state according to its build.prop"""
Kelvin Zhang39aea442020-08-17 11:04:25 -0400188 if not build_info.is_ab:
189 return
Tianjie2bb14862020-08-28 16:24:34 -0700190 build_info_set = ComputeRuntimeBuildInfos(build_info,
191 boot_variable_values)
Kelvin Zhang39aea442020-08-17 11:04:25 -0400192 assert "ab_partitions" in build_info.info_dict,\
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500193 "ab_partitions property required for ab update."
Kelvin Zhang39aea442020-08-17 11:04:25 -0400194 ab_partitions = set(build_info.info_dict.get("ab_partitions"))
195
196 # delta_generator will error out on unused timestamps,
197 # so only generate timestamps for dynamic partitions
198 # used in OTA update.
Yifan Hong5057b952021-01-07 14:09:57 -0800199 for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
Tianjie2bb14862020-08-28 16:24:34 -0700200 partition_prop = build_info.info_dict.get(
201 '{}.build.prop'.format(partition))
202 # Skip if the partition is missing, or it doesn't have a build.prop
203 if not partition_prop or not partition_prop.build_props:
204 continue
205
206 partition_state = partition_states.add()
207 partition_state.partition_name = partition
208 # Update the partition's runtime device names and fingerprints
209 partition_devices = set()
210 partition_fingerprints = set()
211 for runtime_build_info in build_info_set:
212 partition_devices.add(
213 runtime_build_info.GetPartitionBuildProp('ro.product.device',
214 partition))
215 partition_fingerprints.add(
216 runtime_build_info.GetPartitionFingerprint(partition))
217
218 partition_state.device.extend(sorted(partition_devices))
219 partition_state.build.extend(sorted(partition_fingerprints))
220
221 # TODO(xunchang) set the boot image's version with kmi. Note the boot
222 # image doesn't have a file map.
223 partition_state.version = build_info.GetPartitionBuildProp(
224 'ro.build.date.utc', partition)
225
226 # TODO(xunchang), we can save a call to ComputeRuntimeBuildInfos.
Tianjiea2076132020-08-19 17:25:32 -0700227 build_devices, build_fingerprints = \
228 CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values)
229 device_state.device.extend(sorted(build_devices))
230 device_state.build.extend(sorted(build_fingerprints))
231 device_state.build_incremental = build_info.GetBuildProp(
232 'ro.build.version.incremental')
233
Tianjie2bb14862020-08-28 16:24:34 -0700234 UpdatePartitionStates(device_state.partition_state)
Tianjiea2076132020-08-19 17:25:32 -0700235
236 if is_post_build:
237 device_state.sdk_level = build_info.GetBuildProp(
238 'ro.build.version.sdk')
239 device_state.security_patch_level = build_info.GetBuildProp(
240 'ro.build.version.security_patch')
241 # Use the actual post-timestamp, even for a downgrade case.
242 device_state.timestamp = int(build_info.GetBuildProp('ro.build.date.utc'))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400243
244
245def GetPackageMetadata(target_info, source_info=None):
Tianjiea2076132020-08-19 17:25:32 -0700246 """Generates and returns the metadata proto.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400247
Tianjiea2076132020-08-19 17:25:32 -0700248 It generates a ota_metadata protobuf that contains the info to be written
249 into an OTA package (META-INF/com/android/metadata.pb). It also handles the
250 detection of downgrade / data wipe based on the global options.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400251
252 Args:
253 target_info: The BuildInfo instance that holds the target build info.
254 source_info: The BuildInfo instance that holds the source build info, or
255 None if generating full OTA.
256
257 Returns:
Tianjiea2076132020-08-19 17:25:32 -0700258 A protobuf to be written into package metadata entry.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400259 """
260 assert isinstance(target_info, BuildInfo)
261 assert source_info is None or isinstance(source_info, BuildInfo)
262
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400263 boot_variable_values = {}
264 if OPTIONS.boot_variable_file:
265 d = LoadDictionaryFromFile(OPTIONS.boot_variable_file)
266 for key, values in d.items():
267 boot_variable_values[key] = [val.strip() for val in values.split(',')]
268
Tianjiea2076132020-08-19 17:25:32 -0700269 metadata_proto = ota_metadata_pb2.OtaMetadata()
270 # TODO(xunchang) some fields, e.g. post-device isn't necessary. We can
271 # consider skipping them if they aren't used by clients.
272 UpdateDeviceState(metadata_proto.postcondition, target_info,
273 boot_variable_values, True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400274
275 if target_info.is_ab and not OPTIONS.force_non_ab:
Tianjiea2076132020-08-19 17:25:32 -0700276 metadata_proto.type = ota_metadata_pb2.OtaMetadata.AB
277 metadata_proto.required_cache = 0
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400278 else:
Tianjiea2076132020-08-19 17:25:32 -0700279 metadata_proto.type = ota_metadata_pb2.OtaMetadata.BLOCK
280 # cache requirement will be updated by the non-A/B codes.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400281
282 if OPTIONS.wipe_user_data:
Tianjiea2076132020-08-19 17:25:32 -0700283 metadata_proto.wipe = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400284
285 if OPTIONS.retrofit_dynamic_partitions:
Tianjiea2076132020-08-19 17:25:32 -0700286 metadata_proto.retrofit_dynamic_partitions = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400287
288 is_incremental = source_info is not None
289 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700290 UpdateDeviceState(metadata_proto.precondition, source_info,
291 boot_variable_values, False)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400292 else:
Tianjiea2076132020-08-19 17:25:32 -0700293 metadata_proto.precondition.device.extend(
294 metadata_proto.postcondition.device)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400295
296 # Detect downgrades and set up downgrade flags accordingly.
297 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700298 HandleDowngradeMetadata(metadata_proto, target_info, source_info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400299
Tianjiea2076132020-08-19 17:25:32 -0700300 return metadata_proto
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400301
302
Tianjiea2076132020-08-19 17:25:32 -0700303def BuildLegacyOtaMetadata(metadata_proto):
304 """Converts the metadata proto to a legacy metadata dict.
305
306 This metadata dict is used to build the legacy metadata text file for
307 backward compatibility. We won't add new keys to the legacy metadata format.
308 If new information is needed, we should add it as a new field in OtaMetadata
309 proto definition.
310 """
311
312 separator = '|'
313
314 metadata_dict = {}
315 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB:
316 metadata_dict['ota-type'] = 'AB'
317 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK:
318 metadata_dict['ota-type'] = 'BLOCK'
319 if metadata_proto.wipe:
320 metadata_dict['ota-wipe'] = 'yes'
321 if metadata_proto.retrofit_dynamic_partitions:
322 metadata_dict['ota-retrofit-dynamic-partitions'] = 'yes'
323 if metadata_proto.downgrade:
324 metadata_dict['ota-downgrade'] = 'yes'
325
326 metadata_dict['ota-required-cache'] = str(metadata_proto.required_cache)
327
328 post_build = metadata_proto.postcondition
329 metadata_dict['post-build'] = separator.join(post_build.build)
330 metadata_dict['post-build-incremental'] = post_build.build_incremental
331 metadata_dict['post-sdk-level'] = post_build.sdk_level
332 metadata_dict['post-security-patch-level'] = post_build.security_patch_level
333 metadata_dict['post-timestamp'] = str(post_build.timestamp)
334
335 pre_build = metadata_proto.precondition
336 metadata_dict['pre-device'] = separator.join(pre_build.device)
337 # incremental updates
338 if len(pre_build.build) != 0:
339 metadata_dict['pre-build'] = separator.join(pre_build.build)
340 metadata_dict['pre-build-incremental'] = pre_build.build_incremental
341
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500342 if metadata_proto.spl_downgrade:
343 metadata_dict['spl-downgrade'] = 'yes'
Tianjiea2076132020-08-19 17:25:32 -0700344 metadata_dict.update(metadata_proto.property_files)
345
346 return metadata_dict
347
348
349def HandleDowngradeMetadata(metadata_proto, target_info, source_info):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400350 # Only incremental OTAs are allowed to reach here.
351 assert OPTIONS.incremental_source is not None
352
353 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
354 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
355 is_downgrade = int(post_timestamp) < int(pre_timestamp)
356
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500357 if OPTIONS.spl_downgrade:
358 metadata_proto.spl_downgrade = True
359
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400360 if OPTIONS.downgrade:
361 if not is_downgrade:
362 raise RuntimeError(
363 "--downgrade or --override_timestamp specified but no downgrade "
364 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tianjiea2076132020-08-19 17:25:32 -0700365 metadata_proto.downgrade = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400366 else:
367 if is_downgrade:
368 raise RuntimeError(
369 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
370 "Need to specify --override_timestamp OR --downgrade to allow "
371 "building the incremental." % (pre_timestamp, post_timestamp))
372
373
Tianjie2bb14862020-08-28 16:24:34 -0700374def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
375 """Returns a set of build info objects that may exist during runtime."""
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400376
Tianjie2bb14862020-08-28 16:24:34 -0700377 build_info_set = {default_build_info}
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400378 if not boot_variable_values:
Tianjie2bb14862020-08-28 16:24:34 -0700379 return build_info_set
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400380
381 # Calculate all possible combinations of the values for the boot variables.
382 keys = boot_variable_values.keys()
383 value_list = boot_variable_values.values()
384 combinations = [dict(zip(keys, values))
385 for values in itertools.product(*value_list)]
386 for placeholder_values in combinations:
387 # Reload the info_dict as some build properties may change their values
388 # based on the value of ro.boot* properties.
Tianjie2bb14862020-08-28 16:24:34 -0700389 info_dict = copy.deepcopy(default_build_info.info_dict)
Yifan Hong5057b952021-01-07 14:09:57 -0800390 for partition in PARTITIONS_WITH_BUILD_PROP:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400391 partition_prop_key = "{}.build.prop".format(partition)
392 input_file = info_dict[partition_prop_key].input_file
TJ Rhoades6f488e92022-05-01 22:16:22 -0700393 ramdisk = GetRamdiskFormat(info_dict)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400394 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400395 with zipfile.ZipFile(input_file.filename, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400396 info_dict[partition_prop_key] = \
397 PartitionBuildProps.FromInputFile(input_zip, partition,
TJ Rhoades6f488e92022-05-01 22:16:22 -0700398 placeholder_values,
399 ramdisk)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400400 else:
401 info_dict[partition_prop_key] = \
402 PartitionBuildProps.FromInputFile(input_file, partition,
TJ Rhoades6f488e92022-05-01 22:16:22 -0700403 placeholder_values,
404 ramdisk)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400405 info_dict["build.prop"] = info_dict["system.build.prop"]
Tianjie2bb14862020-08-28 16:24:34 -0700406 build_info_set.add(BuildInfo(info_dict, default_build_info.oem_dicts))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400407
Tianjie2bb14862020-08-28 16:24:34 -0700408 return build_info_set
409
410
411def CalculateRuntimeDevicesAndFingerprints(default_build_info,
412 boot_variable_values):
413 """Returns a tuple of sets for runtime devices and fingerprints"""
414
415 device_names = set()
416 fingerprints = set()
417 build_info_set = ComputeRuntimeBuildInfos(default_build_info,
418 boot_variable_values)
419 for runtime_build_info in build_info_set:
420 device_names.add(runtime_build_info.device)
421 fingerprints.add(runtime_build_info.fingerprint)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400422 return device_names, fingerprints
423
424
Kelvin Zhang25ab9982021-06-22 09:51:34 -0400425def GetZipEntryOffset(zfp, entry_info):
426 """Get offset to a beginning of a particular zip entry
427 Args:
428 fp: zipfile.ZipFile
429 entry_info: zipfile.ZipInfo
430
431 Returns:
432 (offset, size) tuple
433 """
434 # Don't use len(entry_info.extra). Because that returns size of extra
435 # fields in central directory. We need to look at local file directory,
436 # as these two might have different sizes.
437
438 # We cannot work with zipfile.ZipFile instances, we need a |fp| for the underlying file.
439 zfp = zfp.fp
440 zfp.seek(entry_info.header_offset)
441 data = zfp.read(zipfile.sizeFileHeader)
442 fheader = struct.unpack(zipfile.structFileHeader, data)
443 # Last two fields of local file header are filename length and
444 # extra length
445 filename_len = fheader[-2]
446 extra_len = fheader[-1]
447 offset = entry_info.header_offset
448 offset += zipfile.sizeFileHeader
449 offset += filename_len + extra_len
450 size = entry_info.file_size
451 return (offset, size)
452
453
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400454class PropertyFiles(object):
455 """A class that computes the property-files string for an OTA package.
456
457 A property-files string is a comma-separated string that contains the
458 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
459 can be fetched directly with the package URL along with the offset/size info.
460 These strings can be used for streaming A/B OTAs, or allowing an updater to
461 download package metadata entry directly, without paying the cost of
462 downloading entire package.
463
464 Computing the final property-files string requires two passes. Because doing
465 the whole package signing (with signapk.jar) will possibly reorder the ZIP
466 entries, which may in turn invalidate earlier computed ZIP entry offset/size
467 values.
468
469 This class provides functions to be called for each pass. The general flow is
470 as follows.
471
472 property_files = PropertyFiles()
473 # The first pass, which writes placeholders before doing initial signing.
474 property_files.Compute()
475 SignOutput()
476
477 # The second pass, by replacing the placeholders with actual data.
478 property_files.Finalize()
479 SignOutput()
480
481 And the caller can additionally verify the final result.
482
483 property_files.Verify()
484 """
485
486 def __init__(self):
487 self.name = None
488 self.required = ()
489 self.optional = ()
490
491 def Compute(self, input_zip):
492 """Computes and returns a property-files string with placeholders.
493
494 We reserve extra space for the offset and size of the metadata entry itself,
495 although we don't know the final values until the package gets signed.
496
497 Args:
498 input_zip: The input ZIP file.
499
500 Returns:
501 A string with placeholders for the metadata offset/size info, e.g.
502 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
503 """
504 return self.GetPropertyFilesString(input_zip, reserve_space=True)
505
506 class InsufficientSpaceException(Exception):
507 pass
508
509 def Finalize(self, input_zip, reserved_length):
510 """Finalizes a property-files string with actual METADATA offset/size info.
511
512 The input ZIP file has been signed, with the ZIP entries in the desired
513 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
514 the ZIP entry offsets and construct the property-files string with actual
515 data. Note that during this process, we must pad the property-files string
516 to the reserved length, so that the METADATA entry size remains the same.
517 Otherwise the entries' offsets and sizes may change again.
518
519 Args:
520 input_zip: The input ZIP file.
521 reserved_length: The reserved length of the property-files string during
522 the call to Compute(). The final string must be no more than this
523 size.
524
525 Returns:
526 A property-files string including the metadata offset/size info, e.g.
527 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
528
529 Raises:
530 InsufficientSpaceException: If the reserved length is insufficient to hold
531 the final string.
532 """
533 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
534 if len(result) > reserved_length:
535 raise self.InsufficientSpaceException(
536 'Insufficient reserved space: reserved={}, actual={}'.format(
537 reserved_length, len(result)))
538
539 result += ' ' * (reserved_length - len(result))
540 return result
541
542 def Verify(self, input_zip, expected):
543 """Verifies the input ZIP file contains the expected property-files string.
544
545 Args:
546 input_zip: The input ZIP file.
547 expected: The property-files string that's computed from Finalize().
548
549 Raises:
550 AssertionError: On finding a mismatch.
551 """
552 actual = self.GetPropertyFilesString(input_zip)
553 assert actual == expected, \
554 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
555
556 def GetPropertyFilesString(self, zip_file, reserve_space=False):
557 """
558 Constructs the property-files string per request.
559
560 Args:
561 zip_file: The input ZIP file.
562 reserved_length: The reserved length of the property-files string.
563
564 Returns:
565 A property-files string including the metadata offset/size info, e.g.
566 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
567 """
568
569 def ComputeEntryOffsetSize(name):
570 """Computes the zip entry offset and size."""
571 info = zip_file.getinfo(name)
Kelvin Zhang25ab9982021-06-22 09:51:34 -0400572 (offset, size) = GetZipEntryOffset(zip_file, info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400573 return '%s:%d:%d' % (os.path.basename(name), offset, size)
574
575 tokens = []
576 tokens.extend(self._GetPrecomputed(zip_file))
577 for entry in self.required:
578 tokens.append(ComputeEntryOffsetSize(entry))
579 for entry in self.optional:
580 if entry in zip_file.namelist():
581 tokens.append(ComputeEntryOffsetSize(entry))
582
583 # 'META-INF/com/android/metadata' is required. We don't know its actual
584 # offset and length (as well as the values for other entries). So we reserve
585 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
586 # the space for metadata entry. Because 'offset' allows a max of 10-digit
587 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
588 # reserved space serves the metadata entry only.
589 if reserve_space:
590 tokens.append('metadata:' + ' ' * 15)
Tianjiea2076132020-08-19 17:25:32 -0700591 tokens.append('metadata.pb:' + ' ' * 15)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400592 else:
593 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
Luca Stefanib6075c52021-11-03 17:10:54 +0100594 if METADATA_PROTO_NAME in zip_file.namelist():
Kelvin Zhang2e1ff6e2022-10-10 10:58:57 -0700595 tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400596
597 return ','.join(tokens)
598
599 def _GetPrecomputed(self, input_zip):
600 """Computes the additional tokens to be included into the property-files.
601
602 This applies to tokens without actual ZIP entries, such as
603 payload_metadata.bin. We want to expose the offset/size to updaters, so
604 that they can download the payload metadata directly with the info.
605
606 Args:
607 input_zip: The input zip file.
608
609 Returns:
610 A list of strings (tokens) to be added to the property-files string.
611 """
612 # pylint: disable=no-self-use
613 # pylint: disable=unused-argument
614 return []
615
616
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000617def SignOutput(temp_zip_name, output_zip_name, package_key=None, pw=None):
618 if package_key is None:
619 package_key = OPTIONS.package_key
620 if pw is None and OPTIONS.key_passwords:
621 pw = OPTIONS.key_passwords[package_key]
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400622
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000623 SignFile(temp_zip_name, output_zip_name, package_key, pw,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400624 whole_file=True)
Tianjiea5fca032021-06-01 22:06:28 -0700625
626
627def ConstructOtaApexInfo(target_zip, source_file=None):
628 """If applicable, add the source version to the apex info."""
629
630 def _ReadApexInfo(input_zip):
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700631 if not DoesInputFileContain(input_zip, "META/apex_info.pb"):
Tianjiea5fca032021-06-01 22:06:28 -0700632 logger.warning("target_file doesn't contain apex_info.pb %s", input_zip)
633 return None
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700634 return ReadBytesFromInputFile(input_zip, "META/apex_info.pb")
Tianjiea5fca032021-06-01 22:06:28 -0700635
636 target_apex_string = _ReadApexInfo(target_zip)
637 # Return early if the target apex info doesn't exist or is empty.
638 if not target_apex_string:
639 return target_apex_string
640
641 # If the source apex info isn't available, just return the target info
642 if not source_file:
643 return target_apex_string
644
645 with zipfile.ZipFile(source_file, "r", allowZip64=True) as source_zip:
646 source_apex_string = _ReadApexInfo(source_zip)
647 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 Zhangfcd731e2023-04-04 10:28:11 -0700731 return extracted_dir
732
733
734def LocatePartitionPath(target_files_dir: str, partition: str, allow_empty):
735 path = os.path.join(target_files_dir, "RADIO", partition + ".img")
736 if os.path.exists(path):
737 return path
738 path = os.path.join(target_files_dir, "IMAGES", partition + ".img")
739 if os.path.exists(path):
740 return path
741 if allow_empty:
742 return ""
743 raise common.ExternalError(
744 "Partition {} not found in target files {}".format(partition, target_files_dir))
745
746
747def GetPartitionImages(target_files_dir: str, ab_partitions, allow_empty=True):
748 assert os.path.isdir(target_files_dir)
749 return ":".join([LocatePartitionPath(target_files_dir, partition, allow_empty) for partition in ab_partitions])
750
751
752def LocatePartitionMap(target_files_dir: str, partition: str):
753 path = os.path.join(target_files_dir, "RADIO", partition + ".map")
754 if os.path.exists(path):
755 return path
756 return ""
757
758
759def GetPartitionMaps(target_files_dir: str, ab_partitions):
760 assert os.path.isdir(target_files_dir)
761 return ":".join([LocatePartitionMap(target_files_dir, partition) for partition in ab_partitions])
762
763
Kelvin Zhangfa928692022-08-16 17:01:53 +0000764class PayloadGenerator(object):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000765 """Manages the creation and the signing of an A/B OTA Payload."""
766
767 PAYLOAD_BIN = 'payload.bin'
768 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
769 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
770 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
771
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700772 def __init__(self, secondary=False, wipe_user_data=False, minor_version=None, is_partial_update=False):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000773 """Initializes a Payload instance.
774
775 Args:
776 secondary: Whether it's generating a secondary payload (default: False).
777 """
778 self.payload_file = None
779 self.payload_properties = None
780 self.secondary = secondary
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000781 self.wipe_user_data = wipe_user_data
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700782 self.minor_version = minor_version
783 self.is_partial_update = is_partial_update
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000784
785 def _Run(self, cmd): # pylint: disable=no-self-use
786 # Don't pipe (buffer) the output if verbose is set. Let
787 # brillo_update_payload write to stdout/stderr directly, so its progress can
788 # be monitored.
789 if OPTIONS.verbose:
790 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
791 else:
792 common.RunAndCheckOutput(cmd)
793
794 def Generate(self, target_file, source_file=None, additional_args=None):
795 """Generates a payload from the given target-files zip(s).
796
797 Args:
798 target_file: The filename of the target build target-files zip.
799 source_file: The filename of the source build target-files zip; or None if
800 generating a full OTA.
801 additional_args: A list of additional args that should be passed to
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700802 delta_generator binary; or None.
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000803 """
804 if additional_args is None:
805 additional_args = []
806
807 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700808 target_dir = ExtractTargetFiles(target_file)
809 cmd = ["delta_generator",
810 "--out_file", payload_file]
811 with open(os.path.join(target_dir, "META", "ab_partitions.txt")) as fp:
812 ab_partitions = fp.read().strip().split("\n")
813 cmd.extend(["--partition_names", ":".join(ab_partitions)])
814 cmd.extend(
815 ["--new_partitions", GetPartitionImages(target_dir, ab_partitions, False)])
816 cmd.extend(
817 ["--new_mapfiles", GetPartitionMaps(target_dir, ab_partitions)])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000818 if source_file is not None:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700819 source_dir = ExtractTargetFiles(source_file)
820 cmd.extend(
821 ["--old_partitions", GetPartitionImages(source_dir, ab_partitions, True)])
822 cmd.extend(
823 ["--old_mapfiles", GetPartitionMaps(source_dir, ab_partitions)])
824
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000825 if OPTIONS.disable_fec_computation:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700826 cmd.extend(["--disable_fec_computation=true"])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000827 if OPTIONS.disable_verity_computation:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700828 cmd.extend(["--disable_verity_computation=true"])
829 postinstall_config = os.path.join(
830 target_dir, "META", "postinstall_config.txt")
831
832 if os.path.exists(postinstall_config):
833 cmd.extend(["--new_postinstall_config_file", postinstall_config])
834 dynamic_partition_info = os.path.join(
835 target_dir, "META", "dynamic_partitions_info.txt")
836
837 if os.path.exists(dynamic_partition_info):
838 cmd.extend(["--dynamic_partition_info_file", dynamic_partition_info])
839
840 major_version, minor_version = ParseUpdateEngineConfig(
841 os.path.join(target_dir, "META", "update_engine_config.txt"))
Kelvin Zhang629bc8d2023-04-11 21:08:27 -0700842 if source_file:
843 major_version, minor_version = ParseUpdateEngineConfig(
844 os.path.join(source_dir, "META", "update_engine_config.txt"))
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700845 if self.minor_version:
846 minor_version = self.minor_version
847 cmd.extend(["--major_version", str(major_version)])
848 if source_file is not None or self.is_partial_update:
849 cmd.extend(["--minor_version", str(minor_version)])
850 if self.is_partial_update:
851 cmd.extend(["--is_partial_update=true"])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000852 cmd.extend(additional_args)
853 self._Run(cmd)
854
855 self.payload_file = payload_file
856 self.payload_properties = None
857
858 def Sign(self, payload_signer):
859 """Generates and signs the hashes of the payload and metadata.
860
861 Args:
862 payload_signer: A PayloadSigner() instance that serves the signing work.
863
864 Raises:
865 AssertionError: On any failure when calling brillo_update_payload script.
866 """
867 assert isinstance(payload_signer, PayloadSigner)
868
869 # 1. Generate hashes of the payload and metadata files.
870 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
871 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
872 cmd = ["brillo_update_payload", "hash",
873 "--unsigned_payload", self.payload_file,
874 "--signature_size", str(payload_signer.maximum_signature_size),
875 "--metadata_hash_file", metadata_sig_file,
876 "--payload_hash_file", payload_sig_file]
877 self._Run(cmd)
878
879 # 2. Sign the hashes.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000880 signed_payload_sig_file = payload_signer.SignHashFile(payload_sig_file)
881 signed_metadata_sig_file = payload_signer.SignHashFile(metadata_sig_file)
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000882
883 # 3. Insert the signatures back into the payload file.
884 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
885 suffix=".bin")
886 cmd = ["brillo_update_payload", "sign",
887 "--unsigned_payload", self.payload_file,
888 "--payload", signed_payload_file,
889 "--signature_size", str(payload_signer.maximum_signature_size),
890 "--metadata_signature_file", signed_metadata_sig_file,
891 "--payload_signature_file", signed_payload_sig_file]
892 self._Run(cmd)
893
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000894 self.payload_file = signed_payload_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000895
896 def WriteToZip(self, output_zip):
897 """Writes the payload to the given zip.
898
899 Args:
900 output_zip: The output ZipFile instance.
901 """
902 assert self.payload_file is not None
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000903 # 4. Dump the signed payload properties.
904 properties_file = common.MakeTempFile(prefix="payload-properties-",
905 suffix=".txt")
906 cmd = ["brillo_update_payload", "properties",
907 "--payload", self.payload_file,
908 "--properties_file", properties_file]
909 self._Run(cmd)
910
911 if self.secondary:
912 with open(properties_file, "a") as f:
913 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
914
915 if self.wipe_user_data:
916 with open(properties_file, "a") as f:
917 f.write("POWERWASH=1\n")
918
919 self.payload_properties = properties_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000920
921 if self.secondary:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000922 payload_arcname = PayloadGenerator.SECONDARY_PAYLOAD_BIN
923 payload_properties_arcname = PayloadGenerator.SECONDARY_PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000924 else:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000925 payload_arcname = PayloadGenerator.PAYLOAD_BIN
926 payload_properties_arcname = PayloadGenerator.PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000927
928 # Add the signed payload file and properties into the zip. In order to
929 # support streaming, we pack them as ZIP_STORED. So these entries can be
930 # read directly with the offset and length pairs.
931 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
932 compress_type=zipfile.ZIP_STORED)
933 common.ZipWrite(output_zip, self.payload_properties,
934 arcname=payload_properties_arcname,
935 compress_type=zipfile.ZIP_STORED)
936
937
938class StreamingPropertyFiles(PropertyFiles):
939 """A subclass for computing the property-files for streaming A/B OTAs."""
940
941 def __init__(self):
942 super(StreamingPropertyFiles, self).__init__()
943 self.name = 'ota-streaming-property-files'
944 self.required = (
945 # payload.bin and payload_properties.txt must exist.
946 'payload.bin',
947 'payload_properties.txt',
948 )
949 self.optional = (
950 # apex_info.pb isn't directly used in the update flow
951 'apex_info.pb',
952 # care_map is available only if dm-verity is enabled.
953 'care_map.pb',
954 'care_map.txt',
955 # compatibility.zip is available only if target supports Treble.
956 'compatibility.zip',
957 )
958
959
960class AbOtaPropertyFiles(StreamingPropertyFiles):
961 """The property-files for A/B OTA that includes payload_metadata.bin info.
962
963 Since P, we expose one more token (aka property-file), in addition to the ones
964 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
965 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
966 doesn't exist as a separate ZIP entry, but can be used to verify if the
967 payload can be applied on the given device.
968
969 For backward compatibility, we keep both of the 'ota-streaming-property-files'
970 and the newly added 'ota-property-files' in P. The new token will only be
971 available in 'ota-property-files'.
972 """
973
974 def __init__(self):
975 super(AbOtaPropertyFiles, self).__init__()
976 self.name = 'ota-property-files'
977
978 def _GetPrecomputed(self, input_zip):
979 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
980 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
981
982 @staticmethod
983 def _GetPayloadMetadataOffsetAndSize(input_zip):
984 """Computes the offset and size of the payload metadata for a given package.
985
986 (From system/update_engine/update_metadata.proto)
987 A delta update file contains all the deltas needed to update a system from
988 one specific version to another specific version. The update format is
989 represented by this struct pseudocode:
990
991 struct delta_update_file {
992 char magic[4] = "CrAU";
993 uint64 file_format_version;
994 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
995
996 // Only present if format_version > 1:
997 uint32 metadata_signature_size;
998
999 // The Bzip2 compressed DeltaArchiveManifest
1000 char manifest[metadata_signature_size];
1001
1002 // The signature of the metadata (from the beginning of the payload up to
1003 // this location, not including the signature itself). This is a
1004 // serialized Signatures message.
1005 char medatada_signature_message[metadata_signature_size];
1006
1007 // Data blobs for files, no specific format. The specific offset
1008 // and length of each data blob is recorded in the DeltaArchiveManifest.
1009 struct {
1010 char data[];
1011 } blobs[];
1012
1013 // These two are not signed:
1014 uint64 payload_signatures_message_size;
1015 char payload_signatures_message[];
1016 };
1017
1018 'payload-metadata.bin' contains all the bytes from the beginning of the
1019 payload, till the end of 'medatada_signature_message'.
1020 """
1021 payload_info = input_zip.getinfo('payload.bin')
1022 (payload_offset, payload_size) = GetZipEntryOffset(input_zip, payload_info)
1023
1024 # Read the underlying raw zipfile at specified offset
1025 payload_fp = input_zip.fp
1026 payload_fp.seek(payload_offset)
1027 header_bin = payload_fp.read(24)
1028
1029 # network byte order (big-endian)
1030 header = struct.unpack("!IQQL", header_bin)
1031
1032 # 'CrAU'
1033 magic = header[0]
1034 assert magic == 0x43724155, "Invalid magic: {:x}, computed offset {}" \
1035 .format(magic, payload_offset)
1036
1037 manifest_size = header[2]
1038 metadata_signature_size = header[3]
1039 metadata_total = 24 + manifest_size + metadata_signature_size
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +00001040 assert metadata_total <= payload_size
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +00001041
1042 return (payload_offset, metadata_total)
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001043
1044
1045def Fnmatch(filename, pattersn):
1046 return any([fnmatch.fnmatch(filename, pat) for pat in pattersn])
1047
1048
1049def CopyTargetFilesDir(input_dir):
1050 output_dir = common.MakeTempDir("target_files")
1051 shutil.copytree(os.path.join(input_dir, "IMAGES"), os.path.join(
1052 output_dir, "IMAGES"), dirs_exist_ok=True)
1053 shutil.copytree(os.path.join(input_dir, "META"), os.path.join(
1054 output_dir, "META"), dirs_exist_ok=True)
1055 for (dirpath, _, filenames) in os.walk(input_dir):
1056 for filename in filenames:
1057 path = os.path.join(dirpath, filename)
1058 relative_path = path.removeprefix(input_dir).removeprefix("/")
1059 if not Fnmatch(relative_path, UNZIP_PATTERN):
1060 continue
1061 if filename.endswith(".prop") or filename == "prop.default" or "/etc/vintf/" in relative_path:
1062 target_path = os.path.join(
1063 output_dir, relative_path)
1064 os.makedirs(os.path.dirname(target_path), exist_ok=True)
1065 shutil.copy(path, target_path)
1066 return output_dir