From c96316c89b6b4fdcccdb46c8a39c79eb62468fff Mon Sep 17 00:00:00 2001 From: Tao Bao Date: Tue, 24 Jan 2017 22:10:49 -0800 Subject: [PATCH] Revert "Revert "releasetools: Generate streamable A/B OTA packages."" This reverts commit ea4325baf8a673e869cac8981781cf57c4821cb5 to re-land commit ef1bb4360f0b9ccc54afd7a39077b7ca4d8a9a36. It fixes the bug when handling a package without care_map.txt (e.g. dm-verity not enabled). In order to support streaming A/B OTA packages, we pack payload_properties.txt and care_map.txt in ZIP_STORED mode. These two entries along with payload.bin (already in ZIP_STORED prior to this CL) can be fetched directly based on the offset and length info. We write the offset and length info into the package metadata entry (META-INF/com/android/metadata), which can be parsed by the OTA server. payload_properties.txt and care_map.txt are usually less than 1-KiB. So the change only incurs marginal size increase. Bug: 33382114 Test: Generate an A/B OTA package. Verify the 'streaming-property-files' entry in the metadata file. Test: Generate an A/B OTA package on a device with dm-verity not enabled. Change-Id: I3469c8b62385a1fc58b4fb82e3f9d4690aef52ba --- tools/releasetools/ota_from_target_files.py | 62 +++++++++++++++++++-- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py index bad3f4c90d..73104701c9 100755 --- a/tools/releasetools/ota_from_target_files.py +++ b/tools/releasetools/ota_from_target_files.py @@ -1226,6 +1226,25 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, source_file=None): """Generate an Android OTA package that has A/B update payload.""" + def ComputeStreamingMetadata(zip_file): + """Compute the streaming metadata for a given zip.""" + + def ComputeEntryOffsetSize(name): + """Compute the zip entry offset and size.""" + info = zip_file.getinfo(name) + offset = info.header_offset + len(info.FileHeader()) + size = info.file_size + return '%s:%d:%d' % (name, offset, size) + + # payload.bin and payload_properties.txt must exist. + offsets = [ComputeEntryOffsetSize('payload.bin'), + ComputeEntryOffsetSize('payload_properties.txt')] + + # care_map.txt is available only if dm-verity is enabled. + if 'care_map.txt' in zip_file.namelist(): + offsets.append(ComputeEntryOffsetSize('care_map.txt')) + return ','.join(offsets) + # The place where the output from the subprocess should go. log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE @@ -1363,11 +1382,15 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, f.write("POWERWASH=1\n") metadata["ota-wipe"] = "yes" - # Add the signed payload file and properties into the zip. - common.ZipWrite(output_zip, properties_file, arcname="payload_properties.txt") + # Add the signed payload file and properties into the zip. In order to + # support streaming, we pack payload.bin, payload_properties.txt and + # care_map.txt as ZIP_STORED. So these entries can be read directly with + # the offset and length pairs. common.ZipWrite(output_zip, signed_payload_file, arcname="payload.bin", compress_type=zipfile.ZIP_STORED) - WriteMetadata(metadata, output_zip) + common.ZipWrite(output_zip, properties_file, + arcname="payload_properties.txt", + compress_type=zipfile.ZIP_STORED) # If dm-verity is supported for the device, copy contents of care_map # into A/B OTA package. @@ -1377,16 +1400,43 @@ def WriteABOTAPackageWithBrilloScript(target_file, output_file, namelist = target_zip.namelist() if care_map_path in namelist: care_map_data = target_zip.read(care_map_path) - common.ZipWriteStr(output_zip, "care_map.txt", care_map_data) + common.ZipWriteStr(output_zip, "care_map.txt", care_map_data, + compress_type=zipfile.ZIP_STORED) else: print("Warning: cannot find care map file in target_file package") common.ZipClose(target_zip) - # Sign the whole package to comply with the Android OTA package format. + # SignOutput(), which in turn calls signapk.jar, will possibly reorder the + # zip entries, as well as padding the entry headers. We sign the current + # package (without the metadata entry) to allow that to happen. Then compute + # the zip entry offsets, write the metadata and do the signing again. common.ZipClose(output_zip) - SignOutput(temp_zip_file.name, output_file) + temp_signing = tempfile.NamedTemporaryFile() + SignOutput(temp_zip_file.name, temp_signing.name) temp_zip_file.close() + # Open the signed zip. Compute the metadata that's needed for streaming. + output_zip = zipfile.ZipFile(temp_signing, "a", + compression=zipfile.ZIP_DEFLATED) + metadata['streaming-property-files'] = ComputeStreamingMetadata(output_zip) + + # Write the metadata entry into the zip. + WriteMetadata(metadata, output_zip) + common.ZipClose(output_zip) + + # Re-sign the package after adding the metadata entry, which should not + # affect the entries that are needed for streaming. Because signapk packs + # ZIP_STORED entries first, then the ZIP_DEFLATED entries such as metadata. + SignOutput(temp_signing.name, output_file) + temp_signing.close() + + # Reopen the signed zip to double check the streaming metadata. + output_zip = zipfile.ZipFile(output_file, "r") + assert (metadata['streaming-property-files'] == + ComputeStreamingMetadata(output_zip)), \ + "Mismatching streaming metadata." + common.ZipClose(output_zip) + class FileDifference(object): def __init__(self, partition, source_zip, target_zip, output_zip):