Merge "Add the option to reserve headroom for partition images. This is useful for devices with low disk space with different build variants."
diff --git a/core/Makefile b/core/Makefile
index 955b406..244f9cd 100644
--- a/core/Makefile
+++ b/core/Makefile
@@ -191,11 +191,16 @@
BUILDINFO_SH := build/tools/buildinfo.sh
-# TARGET_BUILD_FLAVOR and ro.build.flavor are used only by the test harness to distinguish builds.
+# TARGET_BUILD_FLAVOR and ro.build.flavor are used only by the test
+# harness to distinguish builds. Only add _asan for a sanitized build
+# if it isn't already a part of the flavor (via a dedicated lunch
+# config for example).
TARGET_BUILD_FLAVOR := $(TARGET_PRODUCT)-$(TARGET_BUILD_VARIANT)
ifdef SANITIZE_TARGET
+ifeq (,$(findstring _asan,$(TARGET_BUILD_FLAVOR)))
TARGET_BUILD_FLAVOR := $(TARGET_BUILD_FLAVOR)_asan
endif
+endif
ifdef TARGET_SYSTEM_PROP
system_prop_file := $(TARGET_SYSTEM_PROP)
@@ -1155,6 +1160,9 @@
ifeq (true,$(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_SUPPORTS_VBOOT))
$(INSTALLED_BOOTIMAGE_TARGET) : $(VBOOT_SIGNER)
endif
+ifeq (true,$(BOARD_AVB_ENABLE))
+$(INSTALLED_BOOTIMAGE_TARGET) : $(AVBTOOL)
+endif
$(INSTALLED_BOOTIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) \
$(INSTALLED_RAMDISK_TARGET) \
$(INTERNAL_RECOVERYIMAGE_FILES) \
diff --git a/core/binary.mk b/core/binary.mk
index 8a238ba..b2b8186 100644
--- a/core/binary.mk
+++ b/core/binary.mk
@@ -1684,6 +1684,12 @@
ifeq ($(my_tidy_flags),)
my_tidy_flags := $(call default_tidy_header_filter,$(LOCAL_PATH))
endif
+
+ # We might be using the static analyzer through clang-tidy.
+ # https://bugs.llvm.org/show_bug.cgi?id=32914
+ ifneq ($(my_tidy_checks),)
+ my_tidy_flags += "-extra-arg-before=-D__clang_analyzer__"
+ endif
endif
endif
diff --git a/core/definitions.mk b/core/definitions.mk
index 3e99606..64bdfe6 100644
--- a/core/definitions.mk
+++ b/core/definitions.mk
@@ -1017,12 +1017,15 @@
$(hide) echo >> $2
endef
+# b/37755219
+RS_CC_ASAN_OPTIONS := ASAN_OPTIONS=detect_leaks=0:detect_container_overflow=0
+
define transform-renderscripts-to-java-and-bc
@echo "RenderScript: $(PRIVATE_MODULE) <= $(PRIVATE_RS_SOURCE_FILES)"
$(hide) rm -rf $(PRIVATE_RS_OUTPUT_DIR)
$(hide) mkdir -p $(PRIVATE_RS_OUTPUT_DIR)/res/raw
$(hide) mkdir -p $(PRIVATE_RS_OUTPUT_DIR)/src
-$(hide) $(PRIVATE_RS_CC) \
+$(hide) $(RS_CC_ASAN_OPTIONS) $(PRIVATE_RS_CC) \
-o $(PRIVATE_RS_OUTPUT_DIR)/res/raw \
-p $(PRIVATE_RS_OUTPUT_DIR)/src \
-d $(PRIVATE_RS_OUTPUT_DIR) \
@@ -1058,7 +1061,7 @@
@echo "RenderScript: $(PRIVATE_MODULE) <= $(PRIVATE_RS_SOURCE_FILES)"
$(hide) rm -rf $(PRIVATE_RS_OUTPUT_DIR)
$(hide) mkdir -p $(PRIVATE_RS_OUTPUT_DIR)/
-$(hide) $(PRIVATE_RS_CC) \
+$(hide) $(RS_CC_ASAN_OPTIONS) $(PRIVATE_RS_CC) \
-o $(PRIVATE_RS_OUTPUT_DIR)/ \
-d $(PRIVATE_RS_OUTPUT_DIR) \
-a $@ -MD \
@@ -2000,6 +2003,9 @@
APPS_DEFAULT_VERSION_NAME := $(PLATFORM_VERSION)
endif
+# b/37750224
+AAPT_ASAN_OPTIONS := ASAN_OPTIONS=detect_leaks=0
+
# TODO: Right now we generate the asset resources twice, first as part
# of generating the Java classes, then at the end when packaging the final
# assets. This should be changed to do one of two things: (1) Don't generate
@@ -2014,7 +2020,7 @@
define create-resource-java-files
@mkdir -p $(PRIVATE_SOURCE_INTERMEDIATES_DIR)
@mkdir -p $(dir $(PRIVATE_RESOURCE_PUBLICS_OUTPUT))
-$(hide) $(AAPT) package $(PRIVATE_AAPT_FLAGS) -m \
+$(hide) $(AAPT_ASAN_OPTIONS) $(AAPT) package $(PRIVATE_AAPT_FLAGS) -m \
$(eval # PRIVATE_PRODUCT_AAPT_CONFIG is intentionally missing-- see comment.) \
$(addprefix -J , $(PRIVATE_SOURCE_INTERMEDIATES_DIR)) \
$(addprefix -M , $(PRIVATE_ANDROID_MANIFEST)) \
@@ -2394,13 +2400,16 @@
fi
endef
+# b/37756495
+IJAR_ASAN_OPTIONS := ASAN_OPTIONS=detect_leaks=0
+
## Rule to create a table of contents from a .jar file.
## Must be called with $(eval).
# $(1): A .jar file
define _transform-jar-to-toc
$1.toc: $1 | $(IJAR)
@echo Generating TOC: $$@
- $(hide) $(IJAR) $$< $$@.tmp
+ $(hide) $(IJAR_ASAN_OPTIONS) $(IJAR) $$< $$@.tmp
$$(call commit-change-for-toc,$$@)
endef
@@ -2597,7 +2606,7 @@
#values; applications can override these by explicitly stating
#them in their manifest.
define add-assets-to-package
-$(hide) $(AAPT) package -u $(PRIVATE_AAPT_FLAGS) \
+$(hide) $(AAPT_ASAN_OPTIONS) $(AAPT) package -u $(PRIVATE_AAPT_FLAGS) \
$(addprefix -c , $(PRIVATE_PRODUCT_AAPT_CONFIG)) \
$(addprefix --preferred-density , $(PRIVATE_PRODUCT_AAPT_PREF_CONFIG)) \
$(addprefix -M , $(PRIVATE_ANDROID_MANIFEST)) \
@@ -3229,6 +3238,141 @@
endef
###########################################################
+## Path Cleaning
+###########################################################
+
+# Remove "dir .." combinations (but keep ".. ..")
+#
+# $(1): The expanded path, where / is converted to ' ' to work with $(word)
+define _clean-path-strip-dotdot
+$(strip \
+ $(if $(word 2,$(1)),
+ $(if $(call streq,$(word 2,$(1)),..),
+ $(if $(call streq,$(word 1,$(1)),..),
+ $(word 1,$(1)) $(call _clean-path-strip-dotdot,$(wordlist 2,$(words $(1)),$(1)))
+ ,
+ $(call _clean-path-strip-dotdot,$(wordlist 3,$(words $(1)),$(1)))
+ )
+ ,
+ $(word 1,$(1)) $(call _clean-path-strip-dotdot,$(wordlist 2,$(words $(1)),$(1)))
+ )
+ ,
+ $(1)
+ )
+)
+endef
+
+# Remove any leading .. from the path (in case of /..)
+#
+# Should only be called if the original path started with /
+# $(1): The expanded path, where / is converted to ' ' to work with $(word)
+define _clean-path-strip-root-dotdots
+$(strip $(if $(call streq,$(firstword $(1)),..),
+ $(call _clean-path-strip-root-dotdots,$(wordlist 2,$(words $(1)),$(1))),
+ $(1)))
+endef
+
+# Call _clean-path-strip-dotdot until the path stops changing
+# $(1): Non-empty if this path started with a /
+# $(2): The expanded path, where / is converted to ' ' to work with $(word)
+define _clean-path-expanded
+$(strip \
+ $(eval _ep := $(call _clean-path-strip-dotdot,$(2)))
+ $(if $(1),$(eval _ep := $(call _clean-path-strip-root-dotdots,$(_ep))))
+ $(if $(call streq,$(2),$(_ep)),
+ $(_ep),
+ $(call _clean-path-expanded,$(1),$(_ep))))
+endef
+
+# Clean the file path -- remove //, dir/.., extra .
+#
+# This should be the same semantics as golang's filepath.Clean
+#
+# $(1): The file path to clean
+define clean-path
+$(strip \
+ $(if $(call streq,$(words $(1)),1),
+ $(eval _rooted := $(filter /%,$(1)))
+ $(eval _expanded_path := $(filter-out .,$(subst /,$(space),$(1))))
+ $(eval _path := $(if $(_rooted),/)$(subst $(space),/,$(call _clean-path-expanded,$(_rooted),$(_expanded_path))))
+ $(if $(_path),
+ $(_path),
+ .
+ )
+ ,
+ $(if $(call streq,$(words $(1)),0),
+ .,
+ $(error Call clean-path with only one path (without spaces))
+ )
+ )
+)
+endef
+
+ifeq ($(TEST_MAKE_clean_path),true)
+ define my_test
+ $(if $(call streq,$(call clean-path,$(1)),$(2)),,
+ $(eval my_failed := true)
+ $(warning clean-path test '$(1)': expected '$(2)', got '$(call clean-path,$(1))'))
+ endef
+ my_failed :=
+
+ # Already clean
+ $(call my_test,abc,abc)
+ $(call my_test,abc/def,abc/def)
+ $(call my_test,a/b/c,a/b/c)
+ $(call my_test,.,.)
+ $(call my_test,..,..)
+ $(call my_test,../..,../..)
+ $(call my_test,../../abc,../../abc)
+ $(call my_test,/abc,/abc)
+ $(call my_test,/,/)
+
+ # Empty is current dir
+ $(call my_test,,.)
+
+ # Remove trailing slash
+ $(call my_test,abc/,abc)
+ $(call my_test,abc/def/,abc/def)
+ $(call my_test,a/b/c/,a/b/c)
+ $(call my_test,./,.)
+ $(call my_test,../,..)
+ $(call my_test,../../,../..)
+ $(call my_test,/abc/,/abc)
+
+ # Remove doubled slash
+ $(call my_test,abc//def//ghi,abc/def/ghi)
+ $(call my_test,//abc,/abc)
+ $(call my_test,///abc,/abc)
+ $(call my_test,//abc//,/abc)
+ $(call my_test,abc//,abc)
+
+ # Remove . elements
+ $(call my_test,abc/./def,abc/def)
+ $(call my_test,/./abc/def,/abc/def)
+ $(call my_test,abc/.,abc)
+
+ # Remove .. elements
+ $(call my_test,abc/def/ghi/../jkl,abc/def/jkl)
+ $(call my_test,abc/def/../ghi/../jkl,abc/jkl)
+ $(call my_test,abc/def/..,abc)
+ $(call my_test,abc/def/../..,.)
+ $(call my_test,/abc/def/../..,/)
+ $(call my_test,abc/def/../../..,..)
+ $(call my_test,/abc/def/../../..,/)
+ $(call my_test,abc/def/../../../ghi/jkl/../../../mno,../../mno)
+ $(call my_test,/../abc,/abc)
+
+ # Combinations
+ $(call my_test,abc/./../def,def)
+ $(call my_test,abc//./../def,def)
+ $(call my_test,abc/../../././../def,../../def)
+
+ ifdef my_failed
+ $(error failed clean-path test)
+ endif
+endif
+
+###########################################################
## Other includes
###########################################################
diff --git a/core/dex_preopt_libart.mk b/core/dex_preopt_libart.mk
index 8ee800b..9db5dbf 100644
--- a/core/dex_preopt_libart.mk
+++ b/core/dex_preopt_libart.mk
@@ -122,6 +122,7 @@
--runtime-arg -Xnorelocate --compile-pic \
--no-generate-debug-info --generate-build-id \
--abort-on-hard-verifier-error \
+ --force-determinism \
--no-inline-from=core-oj.jar \
$(PRIVATE_DEX_PREOPT_FLAGS) \
$(PRIVATE_ART_FILE_PREOPT_FLAGS) \
diff --git a/core/package_internal.mk b/core/package_internal.mk
index f9f8e35..6b07be6 100644
--- a/core/package_internal.mk
+++ b/core/package_internal.mk
@@ -92,6 +92,7 @@
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
else
need_compile_res := true
+ LOCAL_RESOURCE_DIR := $(foreach d,$(LOCAL_RESOURCE_DIR),$(call clean-path,$(d)))
endif
package_resource_overlays := $(strip \
diff --git a/core/soong_config.mk b/core/soong_config.mk
index 9cda3b1..0a2208b 100644
--- a/core/soong_config.mk
+++ b/core/soong_config.mk
@@ -79,6 +79,7 @@
echo ' "Safestack": $(if $(filter true,$(USE_SAFESTACK)),true,false),'; \
echo ' "EnableCFI": $(if $(filter false,$(ENABLE_CFI)),false,true),'; \
echo ' "Device_uses_hwc2": $(if $(filter true,$(TARGET_USES_HWC2)),true,false),'; \
+ echo ' "Override_rs_driver": "$(OVERRIDE_RS_DRIVER)",'; \
echo ''; \
echo ' "ArtUseReadBarrier": $(if $(filter false,$(PRODUCT_ART_USE_READ_BARRIER)),false,true),'; \
echo ''; \
diff --git a/core/static_java_library.mk b/core/static_java_library.mk
index a8b0103..343b949 100644
--- a/core/static_java_library.mk
+++ b/core/static_java_library.mk
@@ -39,6 +39,7 @@
# A static Java library needs to explicily set LOCAL_RESOURCE_DIR.
ifdef LOCAL_RESOURCE_DIR
need_compile_res := true
+LOCAL_RESOURCE_DIR := $(foreach d,$(LOCAL_RESOURCE_DIR),$(call clean-path,$(d)))
endif
ifdef LOCAL_USE_AAPT2
ifneq ($(LOCAL_STATIC_ANDROID_LIBRARIES),)
diff --git a/tools/releasetools/blockimgdiff.py b/tools/releasetools/blockimgdiff.py
index e385866..b8123c0 100644
--- a/tools/releasetools/blockimgdiff.py
+++ b/tools/releasetools/blockimgdiff.py
@@ -41,10 +41,10 @@
cmd = ['imgdiff', '-z'] if imgdiff else ['bsdiff']
cmd.extend([srcfile, tgtfile, patchfile])
- # Not using common.Run(), which would otherwise dump all the bsdiff/imgdiff
- # commands when OPTIONS.verbose is True - not useful for the case here, since
- # they contain temp filenames only.
- p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+ # Don't dump the bsdiff/imgdiff commands, which are not useful for the case
+ # here, since they contain temp filenames only.
+ p = common.Run(cmd, verbose=False, stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT)
output, _ = p.communicate()
if p.returncode != 0:
diff --git a/tools/releasetools/common.py b/tools/releasetools/common.py
index e200f9f..925a523 100644
--- a/tools/releasetools/common.py
+++ b/tools/releasetools/common.py
@@ -107,10 +107,15 @@
pass
-def Run(args, **kwargs):
- """Create and return a subprocess.Popen object, printing the command
- line on the terminal if -v was specified."""
- if OPTIONS.verbose:
+def Run(args, verbose=None, **kwargs):
+ """Create and return a subprocess.Popen object.
+
+ Caller can specify if the command line should be printed. The global
+ OPTIONS.verbose will be used if not specified.
+ """
+ if verbose is None:
+ verbose = OPTIONS.verbose
+ if verbose:
print(" running: ", " ".join(args))
return subprocess.Popen(args, **kwargs)
diff --git a/tools/releasetools/ota_from_target_files.py b/tools/releasetools/ota_from_target_files.py
index 1a7e10e..2090400 100755
--- a/tools/releasetools/ota_from_target_files.py
+++ b/tools/releasetools/ota_from_target_files.py
@@ -181,14 +181,14 @@
OPTIONS.payload_signer = None
OPTIONS.payload_signer_args = []
OPTIONS.extracted_input = None
+OPTIONS.key_passwords = []
METADATA_NAME = 'META-INF/com/android/metadata'
UNZIP_PATTERN = ['IMAGES/*', 'META/*']
def SignOutput(temp_zip_name, output_zip_name):
- key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
- pw = key_passwords[OPTIONS.package_key]
+ pw = OPTIONS.key_passwords[OPTIONS.package_key]
common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
whole_file=True)
@@ -1021,21 +1021,17 @@
# The place where the output from the subprocess should go.
log_file = sys.stdout if OPTIONS.verbose else subprocess.PIPE
- # Setup signing keys.
- if OPTIONS.package_key is None:
- OPTIONS.package_key = OPTIONS.info_dict.get(
- "default_system_dev_certificate",
- "build/target/product/security/testkey")
-
# A/B updater expects a signing key in RSA format. Gets the key ready for
# later use in step 3, unless a payload_signer has been specified.
if OPTIONS.payload_signer is None:
cmd = ["openssl", "pkcs8",
"-in", OPTIONS.package_key + OPTIONS.private_key_suffix,
- "-inform", "DER", "-nocrypt"]
+ "-inform", "DER"]
+ pw = OPTIONS.key_passwords[OPTIONS.package_key]
+ cmd.extend(["-passin", "pass:" + pw] if pw else ["-nocrypt"])
rsa_key = common.MakeTempFile(prefix="key-", suffix=".key")
cmd.extend(["-out", rsa_key])
- p1 = common.Run(cmd, stdout=log_file, stderr=subprocess.STDOUT)
+ p1 = common.Run(cmd, verbose=False, stdout=log_file, stderr=subprocess.STDOUT)
p1.communicate()
assert p1.returncode == 0, "openssl pkcs8 failed"
@@ -1383,6 +1379,17 @@
ab_update = OPTIONS.info_dict.get("ab_update") == "true"
+ # Use the default key to sign the package if not specified with package_key.
+ # package_keys are needed on ab_updates, so always define them if an
+ # ab_update is getting created.
+ if not OPTIONS.no_signing or ab_update:
+ if OPTIONS.package_key is None:
+ OPTIONS.package_key = OPTIONS.info_dict.get(
+ "default_system_dev_certificate",
+ "build/target/product/security/testkey")
+ # Get signing keys
+ OPTIONS.key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
+
if ab_update:
if OPTIONS.incremental_source is not None:
OPTIONS.target_info_dict = OPTIONS.info_dict
@@ -1448,13 +1455,6 @@
raise common.ExternalError(
"--- target build has specified no recovery ---")
- # Use the default key to sign the package if not specified with package_key.
- if not OPTIONS.no_signing:
- if OPTIONS.package_key is None:
- OPTIONS.package_key = OPTIONS.info_dict.get(
- "default_system_dev_certificate",
- "build/target/product/security/testkey")
-
# Set up the output zip. Create a temporary zip file if signing is needed.
if OPTIONS.no_signing:
if os.path.exists(args[1]):
diff --git a/tools/releasetools/ota_package_parser.py b/tools/releasetools/ota_package_parser.py
new file mode 100755
index 0000000..331122b
--- /dev/null
+++ b/tools/releasetools/ota_package_parser.py
@@ -0,0 +1,228 @@
+#!/usr/bin/env python
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import logging
+import sys
+import traceback
+import zipfile
+
+from rangelib import RangeSet
+
+class Stash(object):
+ """Build a map to track stashed blocks during update simulation."""
+
+ def __init__(self):
+ self.blocks_stashed = 0
+ self.overlap_blocks_stashed = 0
+ self.max_stash_needed = 0
+ self.current_stash_size = 0
+ self.stash_map = {}
+
+ def StashBlocks(self, SHA1, blocks):
+ if SHA1 in self.stash_map:
+ logging.info("already stashed {}: {}".format(SHA1, blocks))
+ return
+ self.blocks_stashed += blocks.size()
+ self.current_stash_size += blocks.size()
+ self.max_stash_needed = max(self.current_stash_size, self.max_stash_needed)
+ self.stash_map[SHA1] = blocks
+
+ def FreeBlocks(self, SHA1):
+ assert self.stash_map.has_key(SHA1), "stash {} not found".format(SHA1)
+ self.current_stash_size -= self.stash_map[SHA1].size()
+ del self.stash_map[SHA1]
+
+ def HandleOverlapBlocks(self, SHA1, blocks):
+ self.StashBlocks(SHA1, blocks)
+ self.overlap_blocks_stashed += blocks.size()
+ self.FreeBlocks(SHA1)
+
+
+class OtaPackageParser(object):
+ """Parse a block-based OTA package."""
+
+ def __init__(self, package):
+ self.package = package
+ self.new_data_size = 0
+ self.patch_data_size = 0
+ self.block_written = 0
+ self.block_stashed = 0
+
+ @staticmethod
+ def GetSizeString(size):
+ assert size >= 0
+ base = 1024.0
+ if size <= base:
+ return "{} bytes".format(size)
+ for units in ['K', 'M', 'G']:
+ if size <= base * 1024 or units == 'G':
+ return "{:.1f}{}".format(size / base, units)
+ base *= 1024
+
+ def ParseTransferList(self, name):
+ """Simulate the transfer commands and calculate the amout of I/O."""
+
+ logging.info("\nSimulating commands in '{}':".format(name))
+ lines = self.package.read(name).strip().splitlines()
+ assert len(lines) >= 4, "{} is too short; Transfer list expects at least" \
+ "4 lines, it has {}".format(name, len(lines))
+ assert int(lines[0]) >= 3
+ logging.info("(version: {})".format(lines[0]))
+
+ blocks_written = 0
+ my_stash = Stash()
+ for line in lines[4:]:
+ cmd_list = line.strip().split(" ")
+ cmd_name = cmd_list[0]
+ try:
+ if cmd_name == "new" or cmd_name == "zero":
+ assert len(cmd_list) == 2, "command format error: {}".format(line)
+ target_range = RangeSet.parse_raw(cmd_list[1])
+ blocks_written += target_range.size()
+ elif cmd_name == "move":
+ # Example: move <onehash> <tgt_range> <src_blk_count> <src_range>
+ # [<loc_range> <stashed_blocks>]
+ assert len(cmd_list) >= 5, "command format error: {}".format(line)
+ target_range = RangeSet.parse_raw(cmd_list[2])
+ blocks_written += target_range.size()
+ if cmd_list[4] == '-':
+ continue
+ SHA1 = cmd_list[1]
+ source_range = RangeSet.parse_raw(cmd_list[4])
+ if target_range.overlaps(source_range):
+ my_stash.HandleOverlapBlocks(SHA1, source_range)
+ elif cmd_name == "bsdiff" or cmd_name == "imgdiff":
+ # Example: bsdiff <offset> <len> <src_hash> <tgt_hash> <tgt_range>
+ # <src_blk_count> <src_range> [<loc_range> <stashed_blocks>]
+ assert len(cmd_list) >= 8, "command format error: {}".format(line)
+ target_range = RangeSet.parse_raw(cmd_list[5])
+ blocks_written += target_range.size()
+ if cmd_list[7] == '-':
+ continue
+ source_SHA1 = cmd_list[3]
+ source_range = RangeSet.parse_raw(cmd_list[7])
+ if target_range.overlaps(source_range):
+ my_stash.HandleOverlapBlocks(source_SHA1, source_range)
+ elif cmd_name == "stash":
+ assert len(cmd_list) == 3, "command format error: {}".format(line)
+ SHA1 = cmd_list[1]
+ source_range = RangeSet.parse_raw(cmd_list[2])
+ my_stash.StashBlocks(SHA1, source_range)
+ elif cmd_name == "free":
+ assert len(cmd_list) == 2, "command format error: {}".format(line)
+ SHA1 = cmd_list[1]
+ my_stash.FreeBlocks(SHA1)
+ except:
+ logging.error("failed to parse command in: " + line)
+ raise
+
+ self.block_written += blocks_written
+ self.block_stashed += my_stash.blocks_stashed
+
+ logging.info("blocks written: {} (expected: {})".format(
+ blocks_written, lines[1]))
+ logging.info("max blocks stashed simultaneously: {} (expected: {})".
+ format(my_stash.max_stash_needed, lines[3]))
+ logging.info("total blocks stashed: {}".format(my_stash.blocks_stashed))
+ logging.info("blocks stashed implicitly: {}".format(
+ my_stash.overlap_blocks_stashed))
+
+ def PrintDataInfo(self, partition):
+ logging.info("\nReading data info for {} partition:".format(partition))
+ new_data = self.package.getinfo(partition + ".new.dat")
+ patch_data = self.package.getinfo(partition + ".patch.dat")
+ logging.info("{:<40}{:<40}".format(new_data.filename, patch_data.filename))
+ logging.info("{:<40}{:<40}".format(
+ "compress_type: " + str(new_data.compress_type),
+ "compress_type: " + str(patch_data.compress_type)))
+ logging.info("{:<40}{:<40}".format(
+ "compressed_size: " + OtaPackageParser.GetSizeString(
+ new_data.compress_size),
+ "compressed_size: " + OtaPackageParser.GetSizeString(
+ patch_data.compress_size)))
+ logging.info("{:<40}{:<40}".format(
+ "file_size: " + OtaPackageParser.GetSizeString(new_data.file_size),
+ "file_size: " + OtaPackageParser.GetSizeString(patch_data.file_size)))
+
+ self.new_data_size += new_data.file_size
+ self.patch_data_size += patch_data.file_size
+
+ def AnalyzePartition(self, partition):
+ assert partition in ("system", "vendor")
+ assert partition + ".new.dat" in self.package.namelist()
+ assert partition + ".patch.dat" in self.package.namelist()
+ assert partition + ".transfer.list" in self.package.namelist()
+
+ self.PrintDataInfo(partition)
+ self.ParseTransferList(partition + ".transfer.list")
+
+ def PrintMetadata(self):
+ metadata_path = "META-INF/com/android/metadata"
+ logging.info("\nMetadata info:")
+ metadata_info = {}
+ for line in self.package.read(metadata_path).strip().splitlines():
+ index = line.find("=")
+ metadata_info[line[0 : index].strip()] = line[index + 1:].strip()
+ assert metadata_info.get("ota-type") == "BLOCK"
+ assert "pre-device" in metadata_info
+ logging.info("device: {}".format(metadata_info["pre-device"]))
+ if "pre-build" in metadata_info:
+ logging.info("pre-build: {}".format(metadata_info["pre-build"]))
+ assert "post-build" in metadata_info
+ logging.info("post-build: {}".format(metadata_info["post-build"]))
+
+ def Analyze(self):
+ logging.info("Analyzing ota package: " + self.package.filename)
+ self.PrintMetadata()
+ assert "system.new.dat" in self.package.namelist()
+ self.AnalyzePartition("system")
+ if "vendor.new.dat" in self.package.namelist():
+ self.AnalyzePartition("vendor")
+
+ #TODO Add analysis of other partitions(e.g. bootloader, boot, radio)
+
+ BLOCK_SIZE = 4096
+ logging.info("\nOTA package analyzed:")
+ logging.info("new data size (uncompressed): " +
+ OtaPackageParser.GetSizeString(self.new_data_size))
+ logging.info("patch data size (uncompressed): " +
+ OtaPackageParser.GetSizeString(self.patch_data_size))
+ logging.info("total data written: " +
+ OtaPackageParser.GetSizeString(self.block_written * BLOCK_SIZE))
+ logging.info("total data stashed: " +
+ OtaPackageParser.GetSizeString(self.block_stashed * BLOCK_SIZE))
+
+
+def main(argv):
+ parser = argparse.ArgumentParser(description='Analyze an OTA package.')
+ parser.add_argument("ota_package", help='Path of the OTA package.')
+ args = parser.parse_args(argv)
+
+ logging_format = '%(message)s'
+ logging.basicConfig(level=logging.INFO, format=logging_format)
+
+ try:
+ with zipfile.ZipFile(args.ota_package, 'r') as package:
+ package_parser = OtaPackageParser(package)
+ package_parser.Analyze()
+ except:
+ logging.error("Failed to read " + args.ota_package)
+ traceback.print_exc()
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])