blob: 104f02f35e022b001643f3332253a35d362ae625 [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
19import zipfile
20
Tianjiea2076132020-08-19 17:25:32 -070021import ota_metadata_pb2
Kelvin Zhangcff4d762020-07-29 16:37:51 -040022from common import (ZipDelete, ZipClose, OPTIONS, MakeTempFile,
23 ZipWriteStr, BuildInfo, LoadDictionaryFromFile,
Yifan Hong5057b952021-01-07 14:09:57 -080024 SignFile, PARTITIONS_WITH_BUILD_PROP, PartitionBuildProps)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040025
Yifan Hong125d0b62020-09-24 17:07:03 -070026logger = logging.getLogger(__name__)
Kelvin Zhang2e417382020-08-20 11:33:11 -040027
28OPTIONS.no_signing = False
29OPTIONS.force_non_ab = False
30OPTIONS.wipe_user_data = False
31OPTIONS.downgrade = False
32OPTIONS.key_passwords = {}
33OPTIONS.package_key = None
34OPTIONS.incremental_source = None
35OPTIONS.retrofit_dynamic_partitions = False
36OPTIONS.output_metadata_path = None
37OPTIONS.boot_variable_file = None
38
Kelvin Zhangcff4d762020-07-29 16:37:51 -040039METADATA_NAME = 'META-INF/com/android/metadata'
Tianjiea2076132020-08-19 17:25:32 -070040METADATA_PROTO_NAME = 'META-INF/com/android/metadata.pb'
Kelvin Zhangcff4d762020-07-29 16:37:51 -040041UNZIP_PATTERN = ['IMAGES/*', 'META/*', 'OTA/*', 'RADIO/*']
Kelvin Zhang05ff7052021-02-10 09:13:26 -050042SECURITY_PATCH_LEVEL_PROP_NAME = "ro.build.version.security_patch"
43
Kelvin Zhangcff4d762020-07-29 16:37:51 -040044
Kelvin Zhangcff4d762020-07-29 16:37:51 -040045def FinalizeMetadata(metadata, input_file, output_file, needed_property_files):
46 """Finalizes the metadata and signs an A/B OTA package.
47
48 In order to stream an A/B OTA package, we need 'ota-streaming-property-files'
49 that contains the offsets and sizes for the ZIP entries. An example
50 property-files string is as follows.
51
52 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379"
53
54 OTA server can pass down this string, in addition to the package URL, to the
55 system update client. System update client can then fetch individual ZIP
56 entries (ZIP_STORED) directly at the given offset of the URL.
57
58 Args:
59 metadata: The metadata dict for the package.
60 input_file: The input ZIP filename that doesn't contain the package METADATA
61 entry yet.
62 output_file: The final output ZIP filename.
63 needed_property_files: The list of PropertyFiles' to be generated.
64 """
65
66 def ComputeAllPropertyFiles(input_file, needed_property_files):
67 # Write the current metadata entry with placeholders.
Kelvin Zhang928c2342020-09-22 16:15:57 -040068 with zipfile.ZipFile(input_file, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040069 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070070 metadata.property_files[property_files.name] = property_files.Compute(
71 input_zip)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040072 namelist = input_zip.namelist()
73
Tianjiea2076132020-08-19 17:25:32 -070074 if METADATA_NAME in namelist or METADATA_PROTO_NAME in namelist:
75 ZipDelete(input_file, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang928c2342020-09-22 16:15:57 -040076 output_zip = zipfile.ZipFile(input_file, 'a', allowZip64=True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -040077 WriteMetadata(metadata, output_zip)
78 ZipClose(output_zip)
79
80 if OPTIONS.no_signing:
81 return input_file
82
83 prelim_signing = MakeTempFile(suffix='.zip')
84 SignOutput(input_file, prelim_signing)
85 return prelim_signing
86
87 def FinalizeAllPropertyFiles(prelim_signing, needed_property_files):
Kelvin Zhang928c2342020-09-22 16:15:57 -040088 with zipfile.ZipFile(prelim_signing, allowZip64=True) as prelim_signing_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -040089 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -070090 metadata.property_files[property_files.name] = property_files.Finalize(
91 prelim_signing_zip,
92 len(metadata.property_files[property_files.name]))
Kelvin Zhangcff4d762020-07-29 16:37:51 -040093
94 # SignOutput(), which in turn calls signapk.jar, will possibly reorder the ZIP
95 # entries, as well as padding the entry headers. We do a preliminary signing
96 # (with an incomplete metadata entry) to allow that to happen. Then compute
97 # the ZIP entry offsets, write back the final metadata and do the final
98 # signing.
99 prelim_signing = ComputeAllPropertyFiles(input_file, needed_property_files)
100 try:
101 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
102 except PropertyFiles.InsufficientSpaceException:
103 # Even with the preliminary signing, the entry orders may change
104 # dramatically, which leads to insufficiently reserved space during the
105 # first call to ComputeAllPropertyFiles(). In that case, we redo all the
106 # preliminary signing works, based on the already ordered ZIP entries, to
107 # address the issue.
108 prelim_signing = ComputeAllPropertyFiles(
109 prelim_signing, needed_property_files)
110 FinalizeAllPropertyFiles(prelim_signing, needed_property_files)
111
112 # Replace the METADATA entry.
Tianjiea2076132020-08-19 17:25:32 -0700113 ZipDelete(prelim_signing, [METADATA_NAME, METADATA_PROTO_NAME])
Kelvin Zhang928c2342020-09-22 16:15:57 -0400114 output_zip = zipfile.ZipFile(prelim_signing, 'a', allowZip64=True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400115 WriteMetadata(metadata, output_zip)
116 ZipClose(output_zip)
117
118 # Re-sign the package after updating the metadata entry.
119 if OPTIONS.no_signing:
120 output_file = prelim_signing
121 else:
122 SignOutput(prelim_signing, output_file)
123
124 # Reopen the final signed zip to double check the streaming metadata.
Kelvin Zhang928c2342020-09-22 16:15:57 -0400125 with zipfile.ZipFile(output_file, allowZip64=True) as output_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400126 for property_files in needed_property_files:
Tianjiea2076132020-08-19 17:25:32 -0700127 property_files.Verify(
128 output_zip, metadata.property_files[property_files.name].strip())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400129
130 # If requested, dump the metadata to a separate file.
131 output_metadata_path = OPTIONS.output_metadata_path
132 if output_metadata_path:
133 WriteMetadata(metadata, output_metadata_path)
134
135
Tianjiea2076132020-08-19 17:25:32 -0700136def WriteMetadata(metadata_proto, output):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400137 """Writes the metadata to the zip archive or a file.
138
139 Args:
Tianjiea2076132020-08-19 17:25:32 -0700140 metadata_proto: The metadata protobuf for the package.
141 output: A ZipFile object or a string of the output file path. If a string
142 path is given, the metadata in the protobuf format will be written to
143 {output}.pb, e.g. ota_metadata.pb
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400144 """
145
Tianjiea2076132020-08-19 17:25:32 -0700146 metadata_dict = BuildLegacyOtaMetadata(metadata_proto)
147 legacy_metadata = "".join(["%s=%s\n" % kv for kv in
148 sorted(metadata_dict.items())])
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400149 if isinstance(output, zipfile.ZipFile):
Tianjiea2076132020-08-19 17:25:32 -0700150 ZipWriteStr(output, METADATA_PROTO_NAME, metadata_proto.SerializeToString(),
151 compress_type=zipfile.ZIP_STORED)
152 ZipWriteStr(output, METADATA_NAME, legacy_metadata,
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400153 compress_type=zipfile.ZIP_STORED)
154 return
155
Tianjiea2076132020-08-19 17:25:32 -0700156 with open('{}.pb'.format(output), 'w') as f:
157 f.write(metadata_proto.SerializeToString())
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400158 with open(output, 'w') as f:
Tianjiea2076132020-08-19 17:25:32 -0700159 f.write(legacy_metadata)
160
161
162def UpdateDeviceState(device_state, build_info, boot_variable_values,
163 is_post_build):
164 """Update the fields of the DeviceState proto with build info."""
165
Tianjie2bb14862020-08-28 16:24:34 -0700166 def UpdatePartitionStates(partition_states):
167 """Update the per-partition state according to its build.prop"""
Kelvin Zhang39aea442020-08-17 11:04:25 -0400168 if not build_info.is_ab:
169 return
Tianjie2bb14862020-08-28 16:24:34 -0700170 build_info_set = ComputeRuntimeBuildInfos(build_info,
171 boot_variable_values)
Kelvin Zhang39aea442020-08-17 11:04:25 -0400172 assert "ab_partitions" in build_info.info_dict,\
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500173 "ab_partitions property required for ab update."
Kelvin Zhang39aea442020-08-17 11:04:25 -0400174 ab_partitions = set(build_info.info_dict.get("ab_partitions"))
175
176 # delta_generator will error out on unused timestamps,
177 # so only generate timestamps for dynamic partitions
178 # used in OTA update.
Yifan Hong5057b952021-01-07 14:09:57 -0800179 for partition in sorted(set(PARTITIONS_WITH_BUILD_PROP) & ab_partitions):
Tianjie2bb14862020-08-28 16:24:34 -0700180 partition_prop = build_info.info_dict.get(
181 '{}.build.prop'.format(partition))
182 # Skip if the partition is missing, or it doesn't have a build.prop
183 if not partition_prop or not partition_prop.build_props:
184 continue
185
186 partition_state = partition_states.add()
187 partition_state.partition_name = partition
188 # Update the partition's runtime device names and fingerprints
189 partition_devices = set()
190 partition_fingerprints = set()
191 for runtime_build_info in build_info_set:
192 partition_devices.add(
193 runtime_build_info.GetPartitionBuildProp('ro.product.device',
194 partition))
195 partition_fingerprints.add(
196 runtime_build_info.GetPartitionFingerprint(partition))
197
198 partition_state.device.extend(sorted(partition_devices))
199 partition_state.build.extend(sorted(partition_fingerprints))
200
201 # TODO(xunchang) set the boot image's version with kmi. Note the boot
202 # image doesn't have a file map.
203 partition_state.version = build_info.GetPartitionBuildProp(
204 'ro.build.date.utc', partition)
205
206 # TODO(xunchang), we can save a call to ComputeRuntimeBuildInfos.
Tianjiea2076132020-08-19 17:25:32 -0700207 build_devices, build_fingerprints = \
208 CalculateRuntimeDevicesAndFingerprints(build_info, boot_variable_values)
209 device_state.device.extend(sorted(build_devices))
210 device_state.build.extend(sorted(build_fingerprints))
211 device_state.build_incremental = build_info.GetBuildProp(
212 'ro.build.version.incremental')
213
Tianjie2bb14862020-08-28 16:24:34 -0700214 UpdatePartitionStates(device_state.partition_state)
Tianjiea2076132020-08-19 17:25:32 -0700215
216 if is_post_build:
217 device_state.sdk_level = build_info.GetBuildProp(
218 'ro.build.version.sdk')
219 device_state.security_patch_level = build_info.GetBuildProp(
220 'ro.build.version.security_patch')
221 # Use the actual post-timestamp, even for a downgrade case.
222 device_state.timestamp = int(build_info.GetBuildProp('ro.build.date.utc'))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400223
224
225def GetPackageMetadata(target_info, source_info=None):
Tianjiea2076132020-08-19 17:25:32 -0700226 """Generates and returns the metadata proto.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400227
Tianjiea2076132020-08-19 17:25:32 -0700228 It generates a ota_metadata protobuf that contains the info to be written
229 into an OTA package (META-INF/com/android/metadata.pb). It also handles the
230 detection of downgrade / data wipe based on the global options.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400231
232 Args:
233 target_info: The BuildInfo instance that holds the target build info.
234 source_info: The BuildInfo instance that holds the source build info, or
235 None if generating full OTA.
236
237 Returns:
Tianjiea2076132020-08-19 17:25:32 -0700238 A protobuf to be written into package metadata entry.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400239 """
240 assert isinstance(target_info, BuildInfo)
241 assert source_info is None or isinstance(source_info, BuildInfo)
242
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400243 boot_variable_values = {}
244 if OPTIONS.boot_variable_file:
245 d = LoadDictionaryFromFile(OPTIONS.boot_variable_file)
246 for key, values in d.items():
247 boot_variable_values[key] = [val.strip() for val in values.split(',')]
248
Tianjiea2076132020-08-19 17:25:32 -0700249 metadata_proto = ota_metadata_pb2.OtaMetadata()
250 # TODO(xunchang) some fields, e.g. post-device isn't necessary. We can
251 # consider skipping them if they aren't used by clients.
252 UpdateDeviceState(metadata_proto.postcondition, target_info,
253 boot_variable_values, True)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400254
255 if target_info.is_ab and not OPTIONS.force_non_ab:
Tianjiea2076132020-08-19 17:25:32 -0700256 metadata_proto.type = ota_metadata_pb2.OtaMetadata.AB
257 metadata_proto.required_cache = 0
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400258 else:
Tianjiea2076132020-08-19 17:25:32 -0700259 metadata_proto.type = ota_metadata_pb2.OtaMetadata.BLOCK
260 # cache requirement will be updated by the non-A/B codes.
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400261
262 if OPTIONS.wipe_user_data:
Tianjiea2076132020-08-19 17:25:32 -0700263 metadata_proto.wipe = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400264
265 if OPTIONS.retrofit_dynamic_partitions:
Tianjiea2076132020-08-19 17:25:32 -0700266 metadata_proto.retrofit_dynamic_partitions = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400267
268 is_incremental = source_info is not None
269 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700270 UpdateDeviceState(metadata_proto.precondition, source_info,
271 boot_variable_values, False)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400272 else:
Tianjiea2076132020-08-19 17:25:32 -0700273 metadata_proto.precondition.device.extend(
274 metadata_proto.postcondition.device)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400275
276 # Detect downgrades and set up downgrade flags accordingly.
277 if is_incremental:
Tianjiea2076132020-08-19 17:25:32 -0700278 HandleDowngradeMetadata(metadata_proto, target_info, source_info)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400279
Tianjiea2076132020-08-19 17:25:32 -0700280 return metadata_proto
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400281
282
Tianjiea2076132020-08-19 17:25:32 -0700283def BuildLegacyOtaMetadata(metadata_proto):
284 """Converts the metadata proto to a legacy metadata dict.
285
286 This metadata dict is used to build the legacy metadata text file for
287 backward compatibility. We won't add new keys to the legacy metadata format.
288 If new information is needed, we should add it as a new field in OtaMetadata
289 proto definition.
290 """
291
292 separator = '|'
293
294 metadata_dict = {}
295 if metadata_proto.type == ota_metadata_pb2.OtaMetadata.AB:
296 metadata_dict['ota-type'] = 'AB'
297 elif metadata_proto.type == ota_metadata_pb2.OtaMetadata.BLOCK:
298 metadata_dict['ota-type'] = 'BLOCK'
299 if metadata_proto.wipe:
300 metadata_dict['ota-wipe'] = 'yes'
301 if metadata_proto.retrofit_dynamic_partitions:
302 metadata_dict['ota-retrofit-dynamic-partitions'] = 'yes'
303 if metadata_proto.downgrade:
304 metadata_dict['ota-downgrade'] = 'yes'
305
306 metadata_dict['ota-required-cache'] = str(metadata_proto.required_cache)
307
308 post_build = metadata_proto.postcondition
309 metadata_dict['post-build'] = separator.join(post_build.build)
310 metadata_dict['post-build-incremental'] = post_build.build_incremental
311 metadata_dict['post-sdk-level'] = post_build.sdk_level
312 metadata_dict['post-security-patch-level'] = post_build.security_patch_level
313 metadata_dict['post-timestamp'] = str(post_build.timestamp)
314
315 pre_build = metadata_proto.precondition
316 metadata_dict['pre-device'] = separator.join(pre_build.device)
317 # incremental updates
318 if len(pre_build.build) != 0:
319 metadata_dict['pre-build'] = separator.join(pre_build.build)
320 metadata_dict['pre-build-incremental'] = pre_build.build_incremental
321
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500322 if metadata_proto.spl_downgrade:
323 metadata_dict['spl-downgrade'] = 'yes'
Tianjiea2076132020-08-19 17:25:32 -0700324 metadata_dict.update(metadata_proto.property_files)
325
326 return metadata_dict
327
328
329def HandleDowngradeMetadata(metadata_proto, target_info, source_info):
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400330 # Only incremental OTAs are allowed to reach here.
331 assert OPTIONS.incremental_source is not None
332
333 post_timestamp = target_info.GetBuildProp("ro.build.date.utc")
334 pre_timestamp = source_info.GetBuildProp("ro.build.date.utc")
335 is_downgrade = int(post_timestamp) < int(pre_timestamp)
336
Kelvin Zhang05ff7052021-02-10 09:13:26 -0500337 if OPTIONS.spl_downgrade:
338 metadata_proto.spl_downgrade = True
339
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400340 if OPTIONS.downgrade:
341 if not is_downgrade:
342 raise RuntimeError(
343 "--downgrade or --override_timestamp specified but no downgrade "
344 "detected: pre: %s, post: %s" % (pre_timestamp, post_timestamp))
Tianjiea2076132020-08-19 17:25:32 -0700345 metadata_proto.downgrade = True
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400346 else:
347 if is_downgrade:
348 raise RuntimeError(
349 "Downgrade detected based on timestamp check: pre: %s, post: %s. "
350 "Need to specify --override_timestamp OR --downgrade to allow "
351 "building the incremental." % (pre_timestamp, post_timestamp))
352
353
Tianjie2bb14862020-08-28 16:24:34 -0700354def ComputeRuntimeBuildInfos(default_build_info, boot_variable_values):
355 """Returns a set of build info objects that may exist during runtime."""
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400356
Tianjie2bb14862020-08-28 16:24:34 -0700357 build_info_set = {default_build_info}
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400358 if not boot_variable_values:
Tianjie2bb14862020-08-28 16:24:34 -0700359 return build_info_set
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400360
361 # Calculate all possible combinations of the values for the boot variables.
362 keys = boot_variable_values.keys()
363 value_list = boot_variable_values.values()
364 combinations = [dict(zip(keys, values))
365 for values in itertools.product(*value_list)]
366 for placeholder_values in combinations:
367 # Reload the info_dict as some build properties may change their values
368 # based on the value of ro.boot* properties.
Tianjie2bb14862020-08-28 16:24:34 -0700369 info_dict = copy.deepcopy(default_build_info.info_dict)
Yifan Hong5057b952021-01-07 14:09:57 -0800370 for partition in PARTITIONS_WITH_BUILD_PROP:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400371 partition_prop_key = "{}.build.prop".format(partition)
372 input_file = info_dict[partition_prop_key].input_file
373 if isinstance(input_file, zipfile.ZipFile):
Kelvin Zhang928c2342020-09-22 16:15:57 -0400374 with zipfile.ZipFile(input_file.filename, allowZip64=True) as input_zip:
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400375 info_dict[partition_prop_key] = \
376 PartitionBuildProps.FromInputFile(input_zip, partition,
377 placeholder_values)
378 else:
379 info_dict[partition_prop_key] = \
380 PartitionBuildProps.FromInputFile(input_file, partition,
381 placeholder_values)
382 info_dict["build.prop"] = info_dict["system.build.prop"]
Tianjie2bb14862020-08-28 16:24:34 -0700383 build_info_set.add(BuildInfo(info_dict, default_build_info.oem_dicts))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400384
Tianjie2bb14862020-08-28 16:24:34 -0700385 return build_info_set
386
387
388def CalculateRuntimeDevicesAndFingerprints(default_build_info,
389 boot_variable_values):
390 """Returns a tuple of sets for runtime devices and fingerprints"""
391
392 device_names = set()
393 fingerprints = set()
394 build_info_set = ComputeRuntimeBuildInfos(default_build_info,
395 boot_variable_values)
396 for runtime_build_info in build_info_set:
397 device_names.add(runtime_build_info.device)
398 fingerprints.add(runtime_build_info.fingerprint)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400399 return device_names, fingerprints
400
401
402class PropertyFiles(object):
403 """A class that computes the property-files string for an OTA package.
404
405 A property-files string is a comma-separated string that contains the
406 offset/size info for an OTA package. The entries, which must be ZIP_STORED,
407 can be fetched directly with the package URL along with the offset/size info.
408 These strings can be used for streaming A/B OTAs, or allowing an updater to
409 download package metadata entry directly, without paying the cost of
410 downloading entire package.
411
412 Computing the final property-files string requires two passes. Because doing
413 the whole package signing (with signapk.jar) will possibly reorder the ZIP
414 entries, which may in turn invalidate earlier computed ZIP entry offset/size
415 values.
416
417 This class provides functions to be called for each pass. The general flow is
418 as follows.
419
420 property_files = PropertyFiles()
421 # The first pass, which writes placeholders before doing initial signing.
422 property_files.Compute()
423 SignOutput()
424
425 # The second pass, by replacing the placeholders with actual data.
426 property_files.Finalize()
427 SignOutput()
428
429 And the caller can additionally verify the final result.
430
431 property_files.Verify()
432 """
433
434 def __init__(self):
435 self.name = None
436 self.required = ()
437 self.optional = ()
438
439 def Compute(self, input_zip):
440 """Computes and returns a property-files string with placeholders.
441
442 We reserve extra space for the offset and size of the metadata entry itself,
443 although we don't know the final values until the package gets signed.
444
445 Args:
446 input_zip: The input ZIP file.
447
448 Returns:
449 A string with placeholders for the metadata offset/size info, e.g.
450 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
451 """
452 return self.GetPropertyFilesString(input_zip, reserve_space=True)
453
454 class InsufficientSpaceException(Exception):
455 pass
456
457 def Finalize(self, input_zip, reserved_length):
458 """Finalizes a property-files string with actual METADATA offset/size info.
459
460 The input ZIP file has been signed, with the ZIP entries in the desired
461 place (signapk.jar will possibly reorder the ZIP entries). Now we compute
462 the ZIP entry offsets and construct the property-files string with actual
463 data. Note that during this process, we must pad the property-files string
464 to the reserved length, so that the METADATA entry size remains the same.
465 Otherwise the entries' offsets and sizes may change again.
466
467 Args:
468 input_zip: The input ZIP file.
469 reserved_length: The reserved length of the property-files string during
470 the call to Compute(). The final string must be no more than this
471 size.
472
473 Returns:
474 A property-files string including the metadata offset/size info, e.g.
475 "payload.bin:679:343,payload_properties.txt:378:45,metadata:69:379 ".
476
477 Raises:
478 InsufficientSpaceException: If the reserved length is insufficient to hold
479 the final string.
480 """
481 result = self.GetPropertyFilesString(input_zip, reserve_space=False)
482 if len(result) > reserved_length:
483 raise self.InsufficientSpaceException(
484 'Insufficient reserved space: reserved={}, actual={}'.format(
485 reserved_length, len(result)))
486
487 result += ' ' * (reserved_length - len(result))
488 return result
489
490 def Verify(self, input_zip, expected):
491 """Verifies the input ZIP file contains the expected property-files string.
492
493 Args:
494 input_zip: The input ZIP file.
495 expected: The property-files string that's computed from Finalize().
496
497 Raises:
498 AssertionError: On finding a mismatch.
499 """
500 actual = self.GetPropertyFilesString(input_zip)
501 assert actual == expected, \
502 "Mismatching streaming metadata: {} vs {}.".format(actual, expected)
503
504 def GetPropertyFilesString(self, zip_file, reserve_space=False):
505 """
506 Constructs the property-files string per request.
507
508 Args:
509 zip_file: The input ZIP file.
510 reserved_length: The reserved length of the property-files string.
511
512 Returns:
513 A property-files string including the metadata offset/size info, e.g.
514 "payload.bin:679:343,payload_properties.txt:378:45,metadata: ".
515 """
516
517 def ComputeEntryOffsetSize(name):
518 """Computes the zip entry offset and size."""
519 info = zip_file.getinfo(name)
520 offset = info.header_offset
521 offset += zipfile.sizeFileHeader
522 offset += len(info.extra) + len(info.filename)
523 size = info.file_size
524 return '%s:%d:%d' % (os.path.basename(name), offset, size)
525
526 tokens = []
527 tokens.extend(self._GetPrecomputed(zip_file))
528 for entry in self.required:
529 tokens.append(ComputeEntryOffsetSize(entry))
530 for entry in self.optional:
531 if entry in zip_file.namelist():
532 tokens.append(ComputeEntryOffsetSize(entry))
533
534 # 'META-INF/com/android/metadata' is required. We don't know its actual
535 # offset and length (as well as the values for other entries). So we reserve
536 # 15-byte as a placeholder ('offset:length'), which is sufficient to cover
537 # the space for metadata entry. Because 'offset' allows a max of 10-digit
538 # (i.e. ~9 GiB), with a max of 4-digit for the length. Note that all the
539 # reserved space serves the metadata entry only.
540 if reserve_space:
541 tokens.append('metadata:' + ' ' * 15)
Tianjiea2076132020-08-19 17:25:32 -0700542 tokens.append('metadata.pb:' + ' ' * 15)
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400543 else:
544 tokens.append(ComputeEntryOffsetSize(METADATA_NAME))
Tianjiea2076132020-08-19 17:25:32 -0700545 tokens.append(ComputeEntryOffsetSize(METADATA_PROTO_NAME))
Kelvin Zhangcff4d762020-07-29 16:37:51 -0400546
547 return ','.join(tokens)
548
549 def _GetPrecomputed(self, input_zip):
550 """Computes the additional tokens to be included into the property-files.
551
552 This applies to tokens without actual ZIP entries, such as
553 payload_metadata.bin. We want to expose the offset/size to updaters, so
554 that they can download the payload metadata directly with the info.
555
556 Args:
557 input_zip: The input zip file.
558
559 Returns:
560 A list of strings (tokens) to be added to the property-files string.
561 """
562 # pylint: disable=no-self-use
563 # pylint: disable=unused-argument
564 return []
565
566
567def SignOutput(temp_zip_name, output_zip_name):
568 pw = OPTIONS.key_passwords[OPTIONS.package_key]
569
570 SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
571 whole_file=True)