Merge "Add Traceur to user builds."
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index d0ee6ae..d09f60c 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -25,6 +25,7 @@
import re
import shlex
import shutil
+import string
import subprocess
import sys
import tempfile
@@ -34,6 +35,7 @@
from hashlib import sha1, sha256
import blockimgdiff
+import sparse_img
class Options(object):
def __init__(self):
@@ -124,6 +126,11 @@
return subprocess.Popen(args, **kwargs)
+def RoundUpTo4K(value):
+ rounded_up = value + 4095
+ return rounded_up - (rounded_up % 4096)
+
+
def CloseInheritedPipes():
""" Gmake in MAC OS has file descriptor (PIPE) leak. We close those fds
before doing other work."""
@@ -618,6 +625,56 @@
return tmp, zipfile.ZipFile(filename, "r")
+def GetSparseImage(which, tmpdir, input_zip):
+ """Returns a SparseImage object suitable for passing to BlockImageDiff.
+
+ This function loads the specified sparse image from the given path, and
+ performs additional processing for OTA purpose. For example, it always adds
+ block 0 to clobbered blocks list. It also detects files that cannot be
+ reconstructed from the block list, for whom we should avoid applying imgdiff.
+
+ Args:
+ which: The partition name, which must be "system" or "vendor".
+ tmpdir: The directory that contains the prebuilt image and block map file.
+ input_zip: The target-files ZIP archive.
+
+ Returns:
+ A SparseImage object, with file_map info loaded.
+ """
+ assert which in ("system", "vendor")
+
+ path = os.path.join(tmpdir, "IMAGES", which + ".img")
+ mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
+
+ # The image and map files must have been created prior to calling
+ # ota_from_target_files.py (since LMP).
+ assert os.path.exists(path) and os.path.exists(mappath)
+
+ # In ext4 filesystems, block 0 might be changed even being mounted R/O. We add
+ # it to clobbered_blocks so that it will be written to the target
+ # unconditionally. Note that they are still part of care_map. (Bug: 20939131)
+ clobbered_blocks = "0"
+
+ image = sparse_img.SparseImage(path, mappath, clobbered_blocks)
+
+ # block.map may contain less blocks, because mke2fs may skip allocating blocks
+ # if they contain all zeros. We can't reconstruct such a file from its block
+ # list. Tag such entries accordingly. (Bug: 65213616)
+ for entry in image.file_map:
+ # "/system/framework/am.jar" => "SYSTEM/framework/am.jar".
+ arcname = string.replace(entry, which, which.upper(), 1)[1:]
+ # Skip artificial names, such as "__ZERO", "__NONZERO-1".
+ if arcname not in input_zip.namelist():
+ continue
+
+ info = input_zip.getinfo(arcname)
+ ranges = image.file_map[entry]
+ if RoundUpTo4K(info.file_size) > ranges.size() * 4096:
+ ranges.extra['incomplete'] = True
+
+ return image
+
+
def GetKeyPasswords(keylist):
"""Given a list of keys, prompt the user to enter passwords for
those which require them. Return a {key: password} dict. password
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index ab6c9ad..12b01c4 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -140,7 +140,6 @@
import common
import edify_generator
-import sparse_img
if sys.hexversion < 0x02070000:
print("Python 2.7 or newer is required.", file=sys.stderr)
@@ -360,6 +359,122 @@
return out_file
+class Payload(object):
+ """Manages the creation and the signing of an A/B OTA Payload."""
+
+ PAYLOAD_BIN = 'payload.bin'
+ PAYLOAD_PROPERTIES_TXT = 'payload_properties.txt'
+
+ def __init__(self):
+ # The place where the output from the subprocess should go.
+ self._log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE
+ self.payload_file = None
+ self.payload_properties = None
+
+ def Generate(self, target_file, source_file=None, additional_args=None):
+ """Generates a payload from the given target-files zip(s).
+
+ Args:
+ target_file: The filename of the target build target-files zip.
+ source_file: The filename of the source build target-files zip; or None if
+ generating a full OTA.
+ additional_args: A list of additional args that should be passed to
+ brillo_update_payload script; or None.
+ """
+ if additional_args is None:
+ additional_args = []
+
+ payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
+ cmd = ["brillo_update_payload", "generate",
+ "--payload", payload_file,
+ "--target_image", target_file]
+ if source_file is not None:
+ cmd.extend(["--source_image", source_file])
+ cmd.extend(additional_args)
+ p = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT)
+ stdoutdata, _ = p.communicate()
+ assert p.returncode == 0, \
+ "brillo_update_payload generate failed: {}".format(stdoutdata)
+
+ self.payload_file = payload_file
+ self.payload_properties = None
+
+ def Sign(self, payload_signer):
+ """Generates and signs the hashes of the payload and metadata.
+
+ Args:
+ payload_signer: A PayloadSigner() instance that serves the signing work.
+
+ Raises:
+ AssertionError: On any failure when calling brillo_update_payload script.
+ """
+ assert isinstance(payload_signer, PayloadSigner)
+
+ # 1. Generate hashes of the payload and metadata files.
+ payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
+ metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
+ cmd = ["brillo_update_payload", "hash",
+ "--unsigned_payload", self.payload_file,
+ "--signature_size", "256",
+ "--metadata_hash_file", metadata_sig_file,
+ "--payload_hash_file", payload_sig_file]
+ p1 = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT)
+ p1.communicate()
+ assert p1.returncode == 0, "brillo_update_payload hash failed"
+
+ # 2. Sign the hashes.
+ signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
+ signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
+
+ # 3. Insert the signatures back into the payload file.
+ signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
+ suffix=".bin")
+ cmd = ["brillo_update_payload", "sign",
+ "--unsigned_payload", self.payload_file,
+ "--payload", signed_payload_file,
+ "--signature_size", "256",
+ "--metadata_signature_file", signed_metadata_sig_file,
+ "--payload_signature_file", signed_payload_sig_file]
+ p1 = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT)
+ p1.communicate()
+ assert p1.returncode == 0, "brillo_update_payload sign failed"
+
+ # 4. Dump the signed payload properties.
+ properties_file = common.MakeTempFile(prefix="payload-properties-",
+ suffix=".txt")
+ cmd = ["brillo_update_payload", "properties",
+ "--payload", signed_payload_file,
+ "--properties_file", properties_file]
+ p1 = common.Run(cmd, stdout=self._log_file, stderr=subprocess.STDOUT)
+ p1.communicate()
+ assert p1.returncode == 0, "brillo_update_payload properties failed"
+
+ if OPTIONS.wipe_user_data:
+ with open(properties_file, "a") as f:
+ f.write("POWERWASH=1\n")
+
+ self.payload_file = signed_payload_file
+ self.payload_properties = properties_file
+
+ def WriteToZip(self, output_zip):
+ """Writes the payload to the given zip.
+
+ Args:
+ output_zip: The output ZipFile instance.
+ """
+ assert self.payload_file is not None
+ assert self.payload_properties is not None
+
+ # Add the signed payload file and properties into the zip. In order to
+ # support streaming, we pack them as ZIP_STORED. So these entries can be
+ # read directly with the offset and length pairs.
+ common.ZipWrite(output_zip, self.payload_file, arcname=Payload.PAYLOAD_BIN,
+ compress_type=zipfile.ZIP_STORED)
+ common.ZipWrite(output_zip, self.payload_properties,
+ arcname=Payload.PAYLOAD_PROPERTIES_TXT,
+ compress_type=zipfile.ZIP_STORED)
+
+
def SignOutput(temp_zip_name, output_zip_name):
pw = OPTIONS.key_passwords[OPTIONS.package_key]
@@ -452,31 +567,6 @@
source_info.GetBuildProp("ro.build.thumbprint"))
-def GetImage(which, tmpdir):
- """Returns an image object suitable for passing to BlockImageDiff.
-
- 'which' partition must be "system" or "vendor". A prebuilt image and file
- map must already exist in tmpdir.
- """
-
- assert which in ("system", "vendor")
-
- path = os.path.join(tmpdir, "IMAGES", which + ".img")
- mappath = os.path.join(tmpdir, "IMAGES", which + ".map")
-
- # The image and map files must have been created prior to calling
- # ota_from_target_files.py (since LMP).
- assert os.path.exists(path) and os.path.exists(mappath)
-
- # Bug: http://b/20939131
- # In ext4 filesystems, block 0 might be changed even being mounted
- # R/O. We add it to clobbered_blocks so that it will be written to the
- # target unconditionally. Note that they are still part of care_map.
- clobbered_blocks = "0"
-
- return sparse_img.SparseImage(path, mappath, clobbered_blocks)
-
-
def AddCompatibilityArchiveIfTrebleEnabled(target_zip, output_zip, target_info,
source_info=None):
"""Adds compatibility info into the output zip if it's Treble-enabled target.
@@ -662,7 +752,7 @@
# has the effect of writing new data from the package to the entire
# partition, but lets us reuse the updater code that writes incrementals to
# do it.
- system_tgt = GetImage("system", OPTIONS.input_tmp)
+ system_tgt = common.GetSparseImage("system", OPTIONS.input_tmp, input_zip)
system_tgt.ResetFileMap()
system_diff = common.BlockDifference("system", system_tgt, src=None)
system_diff.WriteScript(script, output_zip)
@@ -673,7 +763,7 @@
if HasVendorPartition(input_zip):
script.ShowProgress(0.1, 0)
- vendor_tgt = GetImage("vendor", OPTIONS.input_tmp)
+ vendor_tgt = common.GetSparseImage("vendor", OPTIONS.input_tmp, input_zip)
vendor_tgt.ResetFileMap()
vendor_diff = common.BlockDifference("vendor", vendor_tgt)
vendor_diff.WriteScript(script, output_zip)
@@ -778,6 +868,10 @@
'post-build' : target_info.fingerprint,
'post-build-incremental' : target_info.GetBuildProp(
'ro.build.version.incremental'),
+ 'post-sdk-level' : target_info.GetBuildProp(
+ 'ro.build.version.sdk'),
+ 'post-security-patch-level' : target_info.GetBuildProp(
+ 'ro.build.version.security_patch'),
}
if target_info.is_ab:
@@ -846,8 +940,8 @@
target_recovery = common.GetBootableImage(
"/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
- system_src = GetImage("system", OPTIONS.source_tmp)
- system_tgt = GetImage("system", OPTIONS.target_tmp)
+ system_src = common.GetSparseImage("system", OPTIONS.source_tmp, source_zip)
+ system_tgt = common.GetSparseImage("system", OPTIONS.target_tmp, target_zip)
blockimgdiff_version = max(
int(i) for i in target_info.get("blockimgdiff_versions", "1").split(","))
@@ -872,8 +966,8 @@
if HasVendorPartition(target_zip):
if not HasVendorPartition(source_zip):
raise RuntimeError("can't generate incremental that adds /vendor")
- vendor_src = GetImage("vendor", OPTIONS.source_tmp)
- vendor_tgt = GetImage("vendor", OPTIONS.target_tmp)
+ vendor_src = common.GetSparseImage("vendor", OPTIONS.source_tmp, source_zip)
+ vendor_tgt = common.GetSparseImage("vendor", OPTIONS.target_tmp, target_zip)
# Check first block of vendor partition for remount R/W only if
# disk type is ext4
@@ -1122,12 +1216,6 @@
value += ' ' * (expected_length - len(value))
return value
- # The place where the output from the subprocess should go.
- log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE
-
- # Get the PayloadSigner to be used in step 3.
- payload_signer = PayloadSigner()
-
# Stage the output zip package for package signing.
temp_zip_file = tempfile.NamedTemporaryFile()
output_zip = zipfile.ZipFile(temp_zip_file, "w",
@@ -1143,72 +1231,15 @@
# Metadata to comply with Android OTA package format.
metadata = GetPackageMetadata(target_info, source_info)
- # 1. Generate payload.
- payload_file = common.MakeTempFile(prefix="payload-", suffix=".bin")
- cmd = ["brillo_update_payload", "generate",
- "--payload", payload_file,
- "--target_image", target_file]
- if source_file is not None:
- cmd.extend(["--source_image", source_file])
- p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
- p1.communicate()
- assert p1.returncode == 0, "brillo_update_payload generate failed"
+ # Generate payload.
+ payload = Payload()
+ payload.Generate(target_file, source_file)
- # 2. Generate hashes of the payload and metadata files.
- payload_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
- metadata_sig_file = common.MakeTempFile(prefix="sig-", suffix=".bin")
- cmd = ["brillo_update_payload", "hash",
- "--unsigned_payload", payload_file,
- "--signature_size", "256",
- "--metadata_hash_file", metadata_sig_file,
- "--payload_hash_file", payload_sig_file]
- p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
- p1.communicate()
- assert p1.returncode == 0, "brillo_update_payload hash failed"
+ # Sign the payload.
+ payload.Sign(PayloadSigner())
- # 3. Sign the hashes and insert them back into the payload file.
- # 3a. Sign the payload hash.
- signed_payload_sig_file = payload_signer.Sign(payload_sig_file)
-
- # 3b. Sign the metadata hash.
- signed_metadata_sig_file = payload_signer.Sign(metadata_sig_file)
-
- # 3c. Insert the signatures back into the payload file.
- signed_payload_file = common.MakeTempFile(prefix="signed-payload-",
- suffix=".bin")
- cmd = ["brillo_update_payload", "sign",
- "--unsigned_payload", payload_file,
- "--payload", signed_payload_file,
- "--signature_size", "256",
- "--metadata_signature_file", signed_metadata_sig_file,
- "--payload_signature_file", signed_payload_sig_file]
- p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
- p1.communicate()
- assert p1.returncode == 0, "brillo_update_payload sign failed"
-
- # 4. Dump the signed payload properties.
- properties_file = common.MakeTempFile(prefix="payload-properties-",
- suffix=".txt")
- cmd = ["brillo_update_payload", "properties",
- "--payload", signed_payload_file,
- "--properties_file", properties_file]
- p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
- p1.communicate()
- assert p1.returncode == 0, "brillo_update_payload properties failed"
-
- if OPTIONS.wipe_user_data:
- with open(properties_file, "a") as f:
- f.write("POWERWASH=1\n")
-
- # 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)
- common.ZipWrite(output_zip, properties_file,
- arcname="payload_properties.txt",
- compress_type=zipfile.ZIP_STORED)
+ # Write the payload into output zip.
+ payload.WriteToZip(output_zip)
# If dm-verity is supported for the device, copy contents of care_map
# into A/B OTA package.
@@ -1219,6 +1250,8 @@
namelist = target_zip.namelist()
if care_map_path in namelist:
care_map_data = target_zip.read(care_map_path)
+ # In order to support streaming, care_map.txt needs to be packed as
+ # ZIP_STORED.
common.ZipWriteStr(output_zip, "care_map.txt", care_map_data,
compress_type=zipfile.ZIP_STORED)
else:
diff --git a/tools/releasetools/rangelib.py b/tools/releasetools/rangelib.py
index 87380a5..8af61c3 100644
--- a/tools/releasetools/rangelib.py
+++ b/tools/releasetools/rangelib.py
@@ -25,6 +25,7 @@
def __init__(self, data=None):
self.monotonic = False
+ self._extra = {}
if isinstance(data, str):
self._parse_internal(data)
elif data:
@@ -56,6 +57,10 @@
def __repr__(self):
return '<RangeSet("' + self.to_string() + '")>'
+ @property
+ def extra(self):
+ return self._extra
+
@classmethod
def parse(cls, text):
"""Parse a text string consisting of a space-separated list of
diff --git a/tools/releasetools/test_ota_from_target_files.py b/tools/releasetools/test_ota_from_target_files.py
index fa6655b..de03be3 100644
--- a/tools/releasetools/test_ota_from_target_files.py
+++ b/tools/releasetools/test_ota_from_target_files.py
@@ -15,12 +15,14 @@
#
import copy
+import os
import os.path
import unittest
+import zipfile
import common
from ota_from_target_files import (
- _LoadOemDicts, BuildInfo, GetPackageMetadata, PayloadSigner,
+ _LoadOemDicts, BuildInfo, GetPackageMetadata, Payload, PayloadSigner,
WriteFingerprintAssertion)
@@ -318,6 +320,8 @@
'ro.product.device' : 'product-device',
'ro.build.fingerprint' : 'build-fingerprint-target',
'ro.build.version.incremental' : 'build-version-incremental-target',
+ 'ro.build.version.sdk' : '27',
+ 'ro.build.version.security_patch' : '2017-12-01',
'ro.build.date.utc' : '1500000000',
},
}
@@ -327,6 +331,8 @@
'ro.product.device' : 'product-device',
'ro.build.fingerprint' : 'build-fingerprint-source',
'ro.build.version.incremental' : 'build-version-incremental-source',
+ 'ro.build.version.sdk' : '25',
+ 'ro.build.version.security_patch' : '2016-12-01',
'ro.build.date.utc' : '1400000000',
},
}
@@ -349,6 +355,8 @@
'ota-required-cache' : '0',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'post-timestamp' : '1500000000',
'pre-device' : 'product-device',
},
@@ -367,6 +375,8 @@
'ota-required-cache' : '0',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'post-timestamp' : '1500000000',
'pre-device' : 'product-device',
'pre-build' : 'build-fingerprint-source',
@@ -382,6 +392,8 @@
'ota-type' : 'BLOCK',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'post-timestamp' : '1500000000',
'pre-device' : 'product-device',
},
@@ -397,6 +409,8 @@
'ota-type' : 'BLOCK',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'post-timestamp' : '1500000000',
'pre-device' : 'product-device',
'pre-build' : 'build-fingerprint-source',
@@ -414,6 +428,8 @@
'ota-wipe' : 'yes',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'post-timestamp' : '1500000000',
'pre-device' : 'product-device',
},
@@ -457,6 +473,8 @@
'ota-wipe' : 'yes',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'pre-device' : 'product-device',
'pre-build' : 'build-fingerprint-source',
'pre-build-incremental' : 'build-version-incremental-source',
@@ -479,6 +497,8 @@
'ota-type' : 'BLOCK',
'post-build' : 'build-fingerprint-target',
'post-build-incremental' : 'build-version-incremental-target',
+ 'post-sdk-level' : '27',
+ 'post-security-patch-level' : '2017-12-01',
'post-timestamp' : '1500000001',
'pre-device' : 'product-device',
'pre-build' : 'build-fingerprint-source',
@@ -564,3 +584,157 @@
verify_file = os.path.join(self.testdata_dir, self.SIGNED_SIGFILE)
self._assertFilesEqual(verify_file, signed_file)
+
+
+class PayloadTest(unittest.TestCase):
+
+ def setUp(self):
+ self.testdata_dir = get_testdata_dir()
+ self.assertTrue(os.path.exists(self.testdata_dir))
+
+ common.OPTIONS.wipe_user_data = False
+ common.OPTIONS.payload_signer = None
+ common.OPTIONS.payload_signer_args = None
+ common.OPTIONS.package_key = os.path.join(self.testdata_dir, 'testkey')
+ common.OPTIONS.key_passwords = {
+ common.OPTIONS.package_key : None,
+ }
+
+ def tearDown(self):
+ common.Cleanup()
+
+ @staticmethod
+ def _construct_target_files():
+ target_files = common.MakeTempFile(prefix='target_files-', suffix='.zip')
+ with zipfile.ZipFile(target_files, 'w') as target_files_zip:
+ # META/update_engine_config.txt
+ target_files_zip.writestr(
+ 'META/update_engine_config.txt',
+ "PAYLOAD_MAJOR_VERSION=2\nPAYLOAD_MINOR_VERSION=4\n")
+
+ # META/ab_partitions.txt
+ ab_partitions = ['boot', 'system', 'vendor']
+ target_files_zip.writestr(
+ 'META/ab_partitions.txt',
+ '\n'.join(ab_partitions))
+
+ # Create dummy images for each of them.
+ for partition in ab_partitions:
+ target_files_zip.writestr('IMAGES/' + partition + '.img',
+ os.urandom(len(partition)))
+
+ return target_files
+
+ def _create_payload_full(self):
+ target_file = self._construct_target_files()
+ payload = Payload()
+ payload.Generate(target_file)
+ return payload
+
+ def _create_payload_incremental(self):
+ target_file = self._construct_target_files()
+ source_file = self._construct_target_files()
+ payload = Payload()
+ payload.Generate(target_file, source_file)
+ return payload
+
+ def test_Generate_full(self):
+ payload = self._create_payload_full()
+ self.assertTrue(os.path.exists(payload.payload_file))
+
+ def test_Generate_incremental(self):
+ payload = self._create_payload_incremental()
+ self.assertTrue(os.path.exists(payload.payload_file))
+
+ def test_Generate_additionalArgs(self):
+ target_file = self._construct_target_files()
+ source_file = self._construct_target_files()
+ payload = Payload()
+ # This should work the same as calling payload.Generate(target_file,
+ # source_file).
+ payload.Generate(
+ target_file, additional_args=["--source_image", source_file])
+ self.assertTrue(os.path.exists(payload.payload_file))
+
+ def test_Generate_invalidInput(self):
+ target_file = self._construct_target_files()
+ common.ZipDelete(target_file, 'IMAGES/vendor.img')
+ payload = Payload()
+ self.assertRaises(AssertionError, payload.Generate, target_file)
+
+ def test_Sign_full(self):
+ payload = self._create_payload_full()
+ payload.Sign(PayloadSigner())
+
+ output_file = common.MakeTempFile(suffix='.zip')
+ with zipfile.ZipFile(output_file, 'w') as output_zip:
+ payload.WriteToZip(output_zip)
+
+ import check_ota_package_signature
+ check_ota_package_signature.VerifyAbOtaPayload(
+ os.path.join(self.testdata_dir, 'testkey.x509.pem'),
+ output_file)
+
+ def test_Sign_incremental(self):
+ payload = self._create_payload_incremental()
+ payload.Sign(PayloadSigner())
+
+ output_file = common.MakeTempFile(suffix='.zip')
+ with zipfile.ZipFile(output_file, 'w') as output_zip:
+ payload.WriteToZip(output_zip)
+
+ import check_ota_package_signature
+ check_ota_package_signature.VerifyAbOtaPayload(
+ os.path.join(self.testdata_dir, 'testkey.x509.pem'),
+ output_file)
+
+ def test_Sign_withDataWipe(self):
+ common.OPTIONS.wipe_user_data = True
+ payload = self._create_payload_full()
+ payload.Sign(PayloadSigner())
+
+ with open(payload.payload_properties) as properties_fp:
+ self.assertIn("POWERWASH=1", properties_fp.read())
+
+ def test_Sign_badSigner(self):
+ """Tests that signing failure can be captured."""
+ payload = self._create_payload_full()
+ payload_signer = PayloadSigner()
+ payload_signer.signer_args.append('bad-option')
+ self.assertRaises(AssertionError, payload.Sign, payload_signer)
+
+ def test_WriteToZip(self):
+ payload = self._create_payload_full()
+ payload.Sign(PayloadSigner())
+
+ output_file = common.MakeTempFile(suffix='.zip')
+ with zipfile.ZipFile(output_file, 'w') as output_zip:
+ payload.WriteToZip(output_zip)
+
+ with zipfile.ZipFile(output_file) as verify_zip:
+ # First make sure we have the essential entries.
+ namelist = verify_zip.namelist()
+ self.assertIn(Payload.PAYLOAD_BIN, namelist)
+ self.assertIn(Payload.PAYLOAD_PROPERTIES_TXT, namelist)
+
+ # Then assert these entries are stored.
+ for entry_info in verify_zip.infolist():
+ if entry_info.filename not in (Payload.PAYLOAD_BIN,
+ Payload.PAYLOAD_PROPERTIES_TXT):
+ continue
+ self.assertEqual(zipfile.ZIP_STORED, entry_info.compress_type)
+
+ def test_WriteToZip_unsignedPayload(self):
+ """Unsigned payloads should not be allowed to be written to zip."""
+ payload = self._create_payload_full()
+
+ output_file = common.MakeTempFile(suffix='.zip')
+ with zipfile.ZipFile(output_file, 'w') as output_zip:
+ self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
+
+ # Also test with incremental payload.
+ payload = self._create_payload_incremental()
+
+ output_file = common.MakeTempFile(suffix='.zip')
+ with zipfile.ZipFile(output_file, 'w') as output_zip:
+ self.assertRaises(AssertionError, payload.WriteToZip, output_zip)
diff --git a/tools/releasetools/validate_target_files.py b/tools/releasetools/validate_target_files.py
index b590392..1b3eb73 100755
--- a/tools/releasetools/validate_target_files.py
+++ b/tools/releasetools/validate_target_files.py
@@ -29,35 +29,17 @@
import sys
import common
-import sparse_img
-
-
-def _GetImage(which, tmpdir):
- assert which in ('system', 'vendor')
-
- path = os.path.join(tmpdir, 'IMAGES', which + '.img')
- mappath = os.path.join(tmpdir, 'IMAGES', which + '.map')
-
- # Map file must exist (allowed to be empty).
- assert os.path.exists(path) and os.path.exists(mappath)
-
- clobbered_blocks = '0'
- return sparse_img.SparseImage(path, mappath, clobbered_blocks)
def _ReadFile(file_name, unpacked_name, round_up=False):
"""Constructs and returns a File object. Rounds up its size if needed."""
- def RoundUpTo4K(value):
- rounded_up = value + 4095
- return rounded_up - (rounded_up % 4096)
-
assert os.path.exists(unpacked_name)
with open(unpacked_name, 'r') as f:
file_data = f.read()
file_size = len(file_data)
if round_up:
- file_size_rounded_up = RoundUpTo4K(file_size)
+ file_size_rounded_up = common.RoundUpTo4K(file_size)
file_data += '\0' * (file_size_rounded_up - file_size)
return common.File(file_name, file_data)
@@ -79,33 +61,28 @@
def CheckAllFiles(which):
logging.info('Checking %s image.', which)
- image = _GetImage(which, input_tmp)
+ image = common.GetSparseImage(which, input_tmp, input_zip)
prefix = '/' + which
for entry in image.file_map:
+ # Skip entries like '__NONZERO-0'.
if not entry.startswith(prefix):
continue
# Read the blocks that the file resides. Note that it will contain the
# bytes past the file length, which is expected to be padded with '\0's.
ranges = image.file_map[entry]
+
+ incomplete = ranges.extra.get('incomplete', False)
+ if incomplete:
+ logging.warning('Skipping %s that has incomplete block list', entry)
+ continue
+
blocks_sha1 = image.RangeSha1(ranges)
# The filename under unpacked directory, such as SYSTEM/bin/sh.
unpacked_name = os.path.join(
input_tmp, which.upper(), entry[(len(prefix) + 1):])
unpacked_file = _ReadFile(entry, unpacked_name, True)
- file_size = unpacked_file.size
-
- # block.map may contain less blocks, because mke2fs may skip allocating
- # blocks if they contain all zeros. We can't reconstruct such a file from
- # its block list. (Bug: 65213616)
- if file_size > ranges.size() * 4096:
- logging.warning(
- 'Skipping %s that has less blocks: file size %d-byte,'
- ' ranges %s (%d-byte)', entry, file_size, ranges,
- ranges.size() * 4096)
- continue
-
file_sha1 = unpacked_file.sha1
assert blocks_sha1 == file_sha1, \
'file: %s, range: %s, blocks_sha1: %s, file_sha1: %s' % (