blob: 9067e78315fb93715e739fab9e6627783347b9c6 [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
Kelvin Zhangca45d7a2023-04-21 09:46:47 -0700645 source_apex_string = _ReadApexInfo(source_file)
Tianjiea5fca032021-06-01 22:06:28 -0700646 if not source_apex_string:
647 return target_apex_string
648
649 source_apex_proto = ota_metadata_pb2.ApexMetadata()
650 source_apex_proto.ParseFromString(source_apex_string)
651 source_apex_versions = {apex.package_name: apex.version for apex in
652 source_apex_proto.apex_info}
653
654 # If the apex package is available in the source build, initialize the source
655 # apex version.
656 target_apex_proto = ota_metadata_pb2.ApexMetadata()
657 target_apex_proto.ParseFromString(target_apex_string)
658 for target_apex in target_apex_proto.apex_info:
659 name = target_apex.package_name
660 if name in source_apex_versions:
661 target_apex.source_version = source_apex_versions[name]
662
663 return target_apex_proto.SerializeToString()
Kelvin Zhang410bb382022-01-06 09:15:54 -0800664
665
Kelvin Zhangf2728d62022-01-10 11:42:36 -0800666def IsLz4diffCompatible(source_file: str, target_file: str):
667 """Check whether lz4diff versions in two builds are compatible
668
669 Args:
670 source_file: Path to source build's target_file.zip
671 target_file: Path to target build's target_file.zip
672
673 Returns:
674 bool true if and only if lz4diff versions are compatible
675 """
676 if source_file is None or target_file is None:
677 return False
678 # Right now we enable lz4diff as long as source build has liblz4.so.
679 # In the future we might introduce version system to lz4diff as well.
680 if zipfile.is_zipfile(source_file):
681 with zipfile.ZipFile(source_file, "r") as zfp:
682 return "META/liblz4.so" in zfp.namelist()
683 else:
684 assert os.path.isdir(source_file)
685 return os.path.exists(os.path.join(source_file, "META", "liblz4.so"))
686
687
Kelvin Zhang410bb382022-01-06 09:15:54 -0800688def IsZucchiniCompatible(source_file: str, target_file: str):
689 """Check whether zucchini versions in two builds are compatible
690
691 Args:
692 source_file: Path to source build's target_file.zip
693 target_file: Path to target build's target_file.zip
694
695 Returns:
696 bool true if and only if zucchini versions are compatible
697 """
698 if source_file is None or target_file is None:
699 return False
700 assert os.path.exists(source_file)
701 assert os.path.exists(target_file)
702
703 assert zipfile.is_zipfile(source_file) or os.path.isdir(source_file)
704 assert zipfile.is_zipfile(target_file) or os.path.isdir(target_file)
705 _ZUCCHINI_CONFIG_ENTRY_NAME = "META/zucchini_config.txt"
706
707 def ReadEntry(path, entry):
708 # Read an entry inside a .zip file or extracted dir of .zip file
709 if zipfile.is_zipfile(path):
710 with zipfile.ZipFile(path, "r", allowZip64=True) as zfp:
711 if entry in zfp.namelist():
712 return zfp.read(entry).decode()
713 else:
Zhou Xuezand0d49f52022-09-14 16:26:55 +0800714 entry_path = os.path.join(path, entry)
Kelvin Zhang410bb382022-01-06 09:15:54 -0800715 if os.path.exists(entry_path):
716 with open(entry_path, "r") as fp:
717 return fp.read()
HÃ¥kan Kvist3db1ef62022-05-03 10:19:41 +0200718 return False
719 sourceEntry = ReadEntry(source_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
720 targetEntry = ReadEntry(target_file, _ZUCCHINI_CONFIG_ENTRY_NAME)
721 return sourceEntry and targetEntry and sourceEntry == targetEntry
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000722
723
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700724def ExtractTargetFiles(path: str):
725 if os.path.isdir(path):
726 logger.info("target files %s is already extracted", path)
727 return path
728 extracted_dir = common.MakeTempDir("target_files")
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -0700729 common.UnzipToDir(path, extracted_dir, UNZIP_PATTERN + [""])
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700730 return extracted_dir
731
732
733def LocatePartitionPath(target_files_dir: str, partition: str, allow_empty):
734 path = os.path.join(target_files_dir, "RADIO", partition + ".img")
735 if os.path.exists(path):
736 return path
737 path = os.path.join(target_files_dir, "IMAGES", partition + ".img")
738 if os.path.exists(path):
739 return path
740 if allow_empty:
741 return ""
742 raise common.ExternalError(
743 "Partition {} not found in target files {}".format(partition, target_files_dir))
744
745
746def GetPartitionImages(target_files_dir: str, ab_partitions, allow_empty=True):
747 assert os.path.isdir(target_files_dir)
748 return ":".join([LocatePartitionPath(target_files_dir, partition, allow_empty) for partition in ab_partitions])
749
750
751def LocatePartitionMap(target_files_dir: str, partition: str):
752 path = os.path.join(target_files_dir, "RADIO", partition + ".map")
753 if os.path.exists(path):
754 return path
755 return ""
756
757
758def GetPartitionMaps(target_files_dir: str, ab_partitions):
759 assert os.path.isdir(target_files_dir)
760 return ":".join([LocatePartitionMap(target_files_dir, partition) for partition in ab_partitions])
761
762
Kelvin Zhangfa928692022-08-16 17:01:53 +0000763class PayloadGenerator(object):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000764 """Manages the creation and the signing of an A/B OTA Payload."""
765
766 PAYLOAD_BIN = 'payload.bin'
767 PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
768 SECONDARY_PAYLOAD_BIN = 'secondary/payload.bin'
769 SECONDARY_PAYLOAD_PROPERTIES_TXT = 'secondary/payload_properties.txt'
770
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700771 def __init__(self, secondary=False, wipe_user_data=False, minor_version=None, is_partial_update=False):
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000772 """Initializes a Payload instance.
773
774 Args:
775 secondary: Whether it's generating a secondary payload (default: False).
776 """
777 self.payload_file = None
778 self.payload_properties = None
779 self.secondary = secondary
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000780 self.wipe_user_data = wipe_user_data
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700781 self.minor_version = minor_version
782 self.is_partial_update = is_partial_update
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000783
784 def _Run(self, cmd): # pylint: disable=no-self-use
785 # Don't pipe (buffer) the output if verbose is set. Let
786 # brillo_update_payload write to stdout/stderr directly, so its progress can
787 # be monitored.
788 if OPTIONS.verbose:
789 common.RunAndCheckOutput(cmd, stdout=None, stderr=None)
790 else:
791 common.RunAndCheckOutput(cmd)
792
793 def Generate(self, target_file, source_file=None, additional_args=None):
794 """Generates a payload from the given target-files zip(s).
795
796 Args:
797 target_file: The filename of the target build target-files zip.
798 source_file: The filename of the source build target-files zip; or None if
799 generating a full OTA.
800 additional_args: A list of additional args that should be passed to
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700801 delta_generator binary; or None.
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000802 """
803 if additional_args is None:
804 additional_args = []
805
806 payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700807 target_dir = ExtractTargetFiles(target_file)
808 cmd = ["delta_generator",
809 "--out_file", payload_file]
810 with open(os.path.join(target_dir, "META", "ab_partitions.txt")) as fp:
811 ab_partitions = fp.read().strip().split("\n")
812 cmd.extend(["--partition_names", ":".join(ab_partitions)])
813 cmd.extend(
814 ["--new_partitions", GetPartitionImages(target_dir, ab_partitions, False)])
815 cmd.extend(
816 ["--new_mapfiles", GetPartitionMaps(target_dir, ab_partitions)])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000817 if source_file is not None:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700818 source_dir = ExtractTargetFiles(source_file)
819 cmd.extend(
820 ["--old_partitions", GetPartitionImages(source_dir, ab_partitions, True)])
821 cmd.extend(
822 ["--old_mapfiles", GetPartitionMaps(source_dir, ab_partitions)])
823
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000824 if OPTIONS.disable_fec_computation:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700825 cmd.extend(["--disable_fec_computation=true"])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000826 if OPTIONS.disable_verity_computation:
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700827 cmd.extend(["--disable_verity_computation=true"])
828 postinstall_config = os.path.join(
829 target_dir, "META", "postinstall_config.txt")
830
831 if os.path.exists(postinstall_config):
832 cmd.extend(["--new_postinstall_config_file", postinstall_config])
833 dynamic_partition_info = os.path.join(
834 target_dir, "META", "dynamic_partitions_info.txt")
835
836 if os.path.exists(dynamic_partition_info):
837 cmd.extend(["--dynamic_partition_info_file", dynamic_partition_info])
838
839 major_version, minor_version = ParseUpdateEngineConfig(
840 os.path.join(target_dir, "META", "update_engine_config.txt"))
Kelvin Zhang629bc8d2023-04-11 21:08:27 -0700841 if source_file:
842 major_version, minor_version = ParseUpdateEngineConfig(
843 os.path.join(source_dir, "META", "update_engine_config.txt"))
Kelvin Zhangfcd731e2023-04-04 10:28:11 -0700844 if self.minor_version:
845 minor_version = self.minor_version
846 cmd.extend(["--major_version", str(major_version)])
847 if source_file is not None or self.is_partial_update:
848 cmd.extend(["--minor_version", str(minor_version)])
849 if self.is_partial_update:
850 cmd.extend(["--is_partial_update=true"])
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000851 cmd.extend(additional_args)
852 self._Run(cmd)
853
854 self.payload_file = payload_file
855 self.payload_properties = None
856
857 def Sign(self, payload_signer):
858 """Generates and signs the hashes of the payload and metadata.
859
860 Args:
861 payload_signer: A PayloadSigner() instance that serves the signing work.
862
863 Raises:
864 AssertionError: On any failure when calling brillo_update_payload script.
865 """
866 assert isinstance(payload_signer, PayloadSigner)
867
868 # 1. Generate hashes of the payload and metadata files.
869 payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
870 metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
871 cmd = ["brillo_update_payload", "hash",
872 "--unsigned_payload", self.payload_file,
873 "--signature_size", str(payload_signer.maximum_signature_size),
874 "--metadata_hash_file", metadata_sig_file,
875 "--payload_hash_file", payload_sig_file]
876 self._Run(cmd)
877
878 # 2. Sign the hashes.
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000879 signed_payload_sig_file = payload_signer.SignHashFile(payload_sig_file)
880 signed_metadata_sig_file = payload_signer.SignHashFile(metadata_sig_file)
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000881
882 # 3. Insert the signatures back into the payload file.
883 signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
884 suffix=".bin")
885 cmd = ["brillo_update_payload", "sign",
886 "--unsigned_payload", self.payload_file,
887 "--payload", signed_payload_file,
888 "--signature_size", str(payload_signer.maximum_signature_size),
889 "--metadata_signature_file", signed_metadata_sig_file,
890 "--payload_signature_file", signed_payload_sig_file]
891 self._Run(cmd)
892
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000893 self.payload_file = signed_payload_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000894
895 def WriteToZip(self, output_zip):
896 """Writes the payload to the given zip.
897
898 Args:
899 output_zip: The output ZipFile instance.
900 """
901 assert self.payload_file is not None
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +0000902 # 4. Dump the signed payload properties.
903 properties_file = common.MakeTempFile(prefix="payload-properties-",
904 suffix=".txt")
905 cmd = ["brillo_update_payload", "properties",
906 "--payload", self.payload_file,
907 "--properties_file", properties_file]
908 self._Run(cmd)
909
910 if self.secondary:
911 with open(properties_file, "a") as f:
912 f.write("SWITCH_SLOT_ON_REBOOT=0\n")
913
914 if self.wipe_user_data:
915 with open(properties_file, "a") as f:
916 f.write("POWERWASH=1\n")
917
918 self.payload_properties = properties_file
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000919
920 if self.secondary:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000921 payload_arcname = PayloadGenerator.SECONDARY_PAYLOAD_BIN
922 payload_properties_arcname = PayloadGenerator.SECONDARY_PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000923 else:
Kelvin Zhangfa928692022-08-16 17:01:53 +0000924 payload_arcname = PayloadGenerator.PAYLOAD_BIN
925 payload_properties_arcname = PayloadGenerator.PAYLOAD_PROPERTIES_TXT
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +0000926
927 # Add the signed payload file and properties into the zip. In order to
928 # support streaming, we pack them as ZIP_STORED. So these entries can be
929 # read directly with the offset and length pairs.
930 common.ZipWrite(output_zip, self.payload_file, arcname=payload_arcname,
931 compress_type=zipfile.ZIP_STORED)
932 common.ZipWrite(output_zip, self.payload_properties,
933 arcname=payload_properties_arcname,
934 compress_type=zipfile.ZIP_STORED)
935
936
937class StreamingPropertyFiles(PropertyFiles):
938 """A subclass for computing the property-files for streaming A/B OTAs."""
939
940 def __init__(self):
941 super(StreamingPropertyFiles, self).__init__()
942 self.name = 'ota-streaming-property-files'
943 self.required = (
944 # payload.bin and payload_properties.txt must exist.
945 'payload.bin',
946 'payload_properties.txt',
947 )
948 self.optional = (
949 # apex_info.pb isn't directly used in the update flow
950 'apex_info.pb',
951 # care_map is available only if dm-verity is enabled.
952 'care_map.pb',
953 'care_map.txt',
954 # compatibility.zip is available only if target supports Treble.
955 'compatibility.zip',
956 )
957
958
959class AbOtaPropertyFiles(StreamingPropertyFiles):
960 """The property-files for A/B OTA that includes payload_metadata.bin info.
961
962 Since P, we expose one more token (aka property-file), in addition to the ones
963 for streaming A/B OTA, for a virtual entry of 'payload_metadata.bin'.
964 'payload_metadata.bin' is the header part of a payload ('payload.bin'), which
965 doesn't exist as a separate ZIP entry, but can be used to verify if the
966 payload can be applied on the given device.
967
968 For backward compatibility, we keep both of the 'ota-streaming-property-files'
969 and the newly added 'ota-property-files' in P. The new token will only be
970 available in 'ota-property-files'.
971 """
972
973 def __init__(self):
974 super(AbOtaPropertyFiles, self).__init__()
975 self.name = 'ota-property-files'
976
977 def _GetPrecomputed(self, input_zip):
978 offset, size = self._GetPayloadMetadataOffsetAndSize(input_zip)
979 return ['payload_metadata.bin:{}:{}'.format(offset, size)]
980
981 @staticmethod
982 def _GetPayloadMetadataOffsetAndSize(input_zip):
983 """Computes the offset and size of the payload metadata for a given package.
984
985 (From system/update_engine/update_metadata.proto)
986 A delta update file contains all the deltas needed to update a system from
987 one specific version to another specific version. The update format is
988 represented by this struct pseudocode:
989
990 struct delta_update_file {
991 char magic[4] = "CrAU";
992 uint64 file_format_version;
993 uint64 manifest_size; // Size of protobuf DeltaArchiveManifest
994
995 // Only present if format_version > 1:
996 uint32 metadata_signature_size;
997
998 // The Bzip2 compressed DeltaArchiveManifest
999 char manifest[metadata_signature_size];
1000
1001 // The signature of the metadata (from the beginning of the payload up to
1002 // this location, not including the signature itself). This is a
1003 // serialized Signatures message.
1004 char medatada_signature_message[metadata_signature_size];
1005
1006 // Data blobs for files, no specific format. The specific offset
1007 // and length of each data blob is recorded in the DeltaArchiveManifest.
1008 struct {
1009 char data[];
1010 } blobs[];
1011
1012 // These two are not signed:
1013 uint64 payload_signatures_message_size;
1014 char payload_signatures_message[];
1015 };
1016
1017 'payload-metadata.bin' contains all the bytes from the beginning of the
1018 payload, till the end of 'medatada_signature_message'.
1019 """
1020 payload_info = input_zip.getinfo('payload.bin')
1021 (payload_offset, payload_size) = GetZipEntryOffset(input_zip, payload_info)
1022
1023 # Read the underlying raw zipfile at specified offset
1024 payload_fp = input_zip.fp
1025 payload_fp.seek(payload_offset)
1026 header_bin = payload_fp.read(24)
1027
1028 # network byte order (big-endian)
1029 header = struct.unpack("!IQQL", header_bin)
1030
1031 # 'CrAU'
1032 magic = header[0]
1033 assert magic == 0x43724155, "Invalid magic: {:x}, computed offset {}" \
1034 .format(magic, payload_offset)
1035
1036 manifest_size = header[2]
1037 metadata_signature_size = header[3]
1038 metadata_total = 24 + manifest_size + metadata_signature_size
Kelvin Zhangbf01f8b2022-08-30 18:25:43 +00001039 assert metadata_total <= payload_size
Kelvin Zhang62a7f6e2022-08-30 17:41:29 +00001040
1041 return (payload_offset, metadata_total)
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001042
1043
1044def Fnmatch(filename, pattersn):
1045 return any([fnmatch.fnmatch(filename, pat) for pat in pattersn])
1046
1047
1048def CopyTargetFilesDir(input_dir):
1049 output_dir = common.MakeTempDir("target_files")
Kelvin Zhang6b10e152023-05-02 15:48:16 -07001050 IMAGES_DIR = ["IMAGES", "PREBUILT_IMAGES", "RADIO"]
1051 for subdir in IMAGES_DIR:
1052 if not os.path.exists(os.path.join(input_dir, subdir)):
1053 continue
1054 shutil.copytree(os.path.join(input_dir, subdir), os.path.join(
1055 output_dir, subdir), dirs_exist_ok=True, copy_function=os.link)
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001056 shutil.copytree(os.path.join(input_dir, "META"), os.path.join(
1057 output_dir, "META"), dirs_exist_ok=True)
Kelvin Zhang6b10e152023-05-02 15:48:16 -07001058
Kelvin Zhang9dbe2ce2023-04-17 16:38:08 -07001059 for (dirpath, _, filenames) in os.walk(input_dir):
1060 for filename in filenames:
1061 path = os.path.join(dirpath, filename)
1062 relative_path = path.removeprefix(input_dir).removeprefix("/")
1063 if not Fnmatch(relative_path, UNZIP_PATTERN):
1064 continue
1065 if filename.endswith(".prop") or filename == "prop.default" or "/etc/vintf/" in relative_path:
1066 target_path = os.path.join(
1067 output_dir, relative_path)
1068 os.makedirs(os.path.dirname(target_path), exist_ok=True)
1069 shutil.copy(path, target_path)
1070 return output_dir