Android recovery分析(一)---全量升级包的编译流程

一、前言

recovery的最主要功能就是升级,而升级文件就是升级包了,那么升级包时如何编译出来的呢?文就这个问题做个简要的分析。
注:本文中的叙述纯属个人理解,欢迎批评指正。

二、升级包编译命令

1.source build/envsetup.sh
2.lunch (选择合适的配置)
3.make otapackage
注:有些平台可能没有将“recoveryimage”、“bootimage”等目标添加为“otapackage”目标的依赖,而otapackage目标必定会依赖“boot.img”和“recovery.img”这些文件,这时就需要先执行编译对应image文件的命令之后才能使“make otapackage”命令顺利执行,最终生成升级包。

三、从makefile分析升级包编译流程

寻找otapackage目标(我以正在进行的一个项目为例)
首先我们在makefile中找到otapackage目标,然后顺藤摸瓜来分析整个编译的过程。下面我贴出相关的makefile代码,代码路径:rootdir/build/core/Makefile

$(BUILT_TARGET_FILES_PACKAGE): \
        $(INSTALLED_BOOTIMAGE_TARGET) \   -------------------------------(1) 
        $(INSTALLED_RADIOIMAGE_TARGET) \
        $(INSTALLED_RECOVERYIMAGE_TARGET) \
        $(INSTALLED_SYSTEMIMAGE) \
        $(INSTALLED_USERDATAIMAGE_TARGET) \
        $(INSTALLED_CACHEIMAGE_TARGET) \
        $(INSTALLED_VENDORIMAGE_TARGET) \
        $(INSTALLED_CUSTOMIMAGE_TARGET)    \
        $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
        $(SELINUX_FC) \
        $(built_ota_tools) \
        $(APKCERTS_FILE) \
        $(HOST_OUT_EXECUTABLES)/fs_config \
        | $(ACP)                                      -----------------(2) 
    @echo "Package target files: $@"
    $(hide) rm -rf $@ $(zip_root)
    $(hide) mkdir -p $(dir $@) $(zip_root)                  --------------(3) 
    @# Components of the recovery image
    $(hide) mkdir -p $(zip_root)/RECOVERY
    $(hide) $(call package_files-copy-root, \                -------------(4) 
        $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)
    $(hide) mkdir -p $(zip_root)/OTHER
    $(hide) $(ACP) \
        $(INSTALLED_UBOOT_TARGET) $(zip_root)/OTHER/bootloader     ---(5) 
    $(hide) $(ACP) \
        $(INSTALLED_DTB_TARGET) $(zip_root)/OTHER/dtb
    $(hide) $(ACP) \
        $(INSTALLED_BOOTIMAGE_TARGET) $(zip_root)/OTHER/boot.img
    $(hide) $(ACP) \
        $(INSTALLED_LDFW_TARGET) $(zip_root)/OTHER/ldfw
    $(hide) $(ACP) \
        $(INSTALLED_RECOVERY_TARGET) $(zip_root)/OTHER/recovery.img

ifdef BOARD_KERNEL_BASE
    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base
endif
ifdef BOARD_KERNEL_PAGESIZE
    $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/RECOVERY/pagesize
endif
    @# Components of the boot image
    $(hide) mkdir -p $(zip_root)/BOOT
    $(hide) $(call package_files-copy-root, \
        $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)
ifdef INSTALLED_KERNEL_TARGET
    $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel
endif
ifdef INSTALLED_2NDBOOTLOADER_TARGET
    $(hide) $(ACP) \
        $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second
endif
ifdef BOARD_KERNEL_CMDLINE
    $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline
endif
ifdef BOARD_KERNEL_BASE
    $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base
endif
ifdef BOARD_KERNEL_PAGESIZE
    $(hide) echo "$(BOARD_KERNEL_PAGESIZE)" > $(zip_root)/BOOT/pagesize
endif
    $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),\
                mkdir -p $(zip_root)/RADIO; \
                $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));)
    @# Contents of the system image
    $(hide) $(call package_files-copy-root, \
        $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
    @# Contents of the data image
    $(hide) $(call package_files-copy-root, \
        $(TARGET_OUT_DATA),$(zip_root)/DATA)
ifdef BOARD_VENDORIMAGE_FILE_SYSTEM_TYPE
    @# Contents of the vendor image
    $(hide) $(call package_files-copy-root, \
        $(TARGET_OUT_VENDOR),$(zip_root)/VENDOR)
endif
    @# Extra contents of the OTA package
    $(hide) mkdir -p $(zip_root)/OTA/bin
    $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/
    $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/
    @# Files that do not end up in any images, but are necessary to
    @# build them.
    $(hide) mkdir -p $(zip_root)/META
    @# imei.dat information of the OTA package
    @echo "[imei.dat] Adding imei.dat to OTA package"
    @echo "[imei.dat] path : bootable/recovery/etc/META-INF/imei.dat"
    $(hide) $(ACP) bootable/recovery/etc/META-INF/imei.dat $(zip_root)/META/
    @# machine_match information of the OTA package
    @echo "[machine_match] Adding machine_match to OTA package"
    $(hide) $(ACP) $(machine_match_binary) $(PRODUCT_OUT)/
    $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt
    $(hide) if test -e $(tool_extensions)/releasetools.py; then $(ACP) $(tool_extensions)/releasetools.py $(zip_root)/META/; fi
    $(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt
    $(hide) echo "recovery_api_version=$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/misc_info.txt        -------------------------------------------------------------------------------------(6)
    $(hide) echo "fstab_version=$(PRIVATE_RECOVERY_FSTAB_VERSION)" >> $(zip_root)/META/misc_info.txt  
    $(hide) -$(ACP) $(PRODUCT_OUT)/firmware_type $(zip_root)/META/
ifdef BOARD_FLASH_BLOCK_SIZE
    $(hide) echo "blocksize=$(BOARD_FLASH_BLOCK_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
ifdef BOARD_BOOTIMAGE_PARTITION_SIZE
    $(hide) echo "boot_size=$(BOARD_BOOTIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt 
endif
ifdef BOARD_RECOVERYIMAGE_PARTITION_SIZE
    $(hide) echo "recovery_size=$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)" >> $(zip_root)/META/misc_info.txt
endif
ifdef BOARD_HAS_EXT4_RESERVED_BLOCKS
    $(hide) echo "has_ext4_reserved_blocks=$(BOARD_HAS_EXT4_RESERVED_BLOCKS)" >> $(zip_root)/META/misc_info.txt
endif
ifdef TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS
    @# TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS can be empty to indicate that nothing but defaults should be used.
    $(hide) echo "recovery_mount_options=$(TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS)" >> $(zip_root)/META/misc_info.txt
else
    $(hide) echo "recovery_mount_options=$(DEFAULT_TARGET_RECOVERY_FSTYPE_MOUNT_OPTIONS)" >> $(zip_root)/META/misc_info.txt
endif
    $(hide) echo "tool_extensions=$(tool_extensions)" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "default_system_dev_certificate=$(DEFAULT_SYSTEM_DEV_CERTIFICATE)" >> $(zip_root)/META/misc_info.txt
ifdef PRODUCT_EXTRA_RECOVERY_KEYS
    $(hide) echo "extra_recovery_keys=$(PRODUCT_EXTRA_RECOVERY_KEYS)" >> $(zip_root)/META/misc_info.txt
endif
    $(hide) echo 'mkbootimg_args=$(BOARD_MKBOOTIMG_ARGS)' >> $(zip_root)/META/misc_info.txt
    $(hide) echo "use_set_metadata=1" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "multistage_support=1" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "update_rename_support=1" >> $(zip_root)/META/misc_info.txt
    $(hide) echo "blockimgdiff_versions=1,2,3" >> $(zip_root)/META/misc_info.txt
ifneq ($(OEM_THUMBPRINT_PROPERTIES),)
    # OTA scripts are only interested in fingerprint related properties
    $(hide) echo "oem_fingerprint_properties=$(OEM_THUMBPRINT_PROPERTIES)" >> $(zip_root)/META/misc_info.txt
endif
    $(call generate-userimage-prop-dictionary, $(zip_root)/META/misc_info.txt)
    $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
	    ./build/tools/releasetools/make_recovery_patch $(zip_root) $(zip_root)
	@# Zip everything up, preserving symlinks
	$(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
	@# Run fs_config on all the system, vendor, boot ramdisk,
	@# and recovery ramdisk files in the zip, and save the output
	$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="SYSTEM/" } /^SYSTEM\// {print "system/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt                                                                      ---------(7)
	$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="VENDOR/" } /^VENDOR\// {print "vendor/" $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/vendor_filesystem_config.txt
	$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="BOOT/RAMDISK/" } /^BOOT\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/boot_filesystem_config.txt
	$(hide) zipinfo -1 $@ | awk 'BEGIN { FS="RECOVERY/RAMDISK/" } /^RECOVERY\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -D $(TARGET_OUT) -S $(SELINUX_FC) > $(zip_root)/META/recovery_filesystem_config.txt
	$(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt)                                    --------------------------------------------------------------------------(8)
	$(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
        ./build/tools/releasetools/add_img_to_target_files -p $(HOST_OUT) $@                                        --------------------------------------------------------------------------------(9)
    $(hide) ./build/tools/releasetools/replace_img_from_target_files.py $@ $(PRODUCT_OUT)            -----------------------------------------------------------------------------------------------(10)

.PHONY: target-files-package
target-files-package: $(BUILT_TARGET_FILES_PACKAGE)

ifneq ($(filter $(MAKECMDGOALS),target-files-package),)
$(call dist-for-goals, target-files-package, $(BUILT_TARGET_FILES_PACKAGE))
endif

ifneq ($(TARGET_PRODUCT),sdk)
ifeq ($(filter generic%,$(TARGET_DEVICE)),)
ifneq ($(TARGET_NO_KERNEL),true)
ifneq ($(recovery_fstab),)

# -----------------------------------------------------------------
# OTA update package

name := $(TARGET_PRODUCT)
ifeq ($(TARGET_BUILD_TYPE),debug)
  name := $(name)_debug
endif
name := $(name)-ota-$(FILE_NAME_TAG)

INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip

$(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)

$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS)
    @echo "Package OTA: $@"
    $(hide) PATH=$(foreach p,$(INTERNAL_USERIMAGES_BINARY_PATHS),$(p):)$$PATH MKBOOTIMG=$(MKBOOTIMG) \
       ./build/tools/releasetools/ota_from_target_files -v \      ------------------(11)       
       --block \
       -p $(HOST_OUT) \
       -k $(KEY_CERT_PAIR) \
       $(if $(OEM_OTA_CONFIG), -o $(OEM_OTA_CONFIG)) \
       $(BUILT_TARGET_FILES_PACKAGE) $@

.PHONY: otapackage
otapackage: $(INTERNAL_OTA_PACKAGE_TARGET)

下面来分析一下makefile代码:
首先otapackage是一个伪目标,它有个INTERNAL_OTA_PACKAGE_TARGET的依赖,INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip,就是最终的那个升级包。我们继续看下面:
$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS)
$(INTERNAL_OTA_PACKAGE_TARGET)又有一些依赖,这里我只关心 $(BUILT_TARGET_FILES_PACKAGE) , $(BUILT_TARGET_FILES_PACKAGE) 目标在代码片段的最开始定义,现在重点分析一下 $(BUILT_TARGET_FILES_PACKAGE) 目标。
我们可以通过阅读makefile可以知道,\$(BUILT_TARGET_FILES_PACKAGE) =out/target/product//obj/PACKAGING/target_files_intermediates/*target_files-.zip,target_files-.zip(后续我们简称为target_file.zip)中保存着生成升级包需要的文件。下面分析代码
(1)一些依赖目标
(2)在依赖前加“|”的意思是”|”后面的依赖要比其他依赖优先编译
(3)生成目录out/target/product//obj/PACKAGING/target_files_intermediates/*target_files-(后续我们简称为target_file),后面会把一些中间文件拷贝到这个目录,最终打包成out/target/product/*/obj/PACKAGING/target_files_intermediates/target_files-.zip
(4)把生成recovery的中间文件也拷贝到target_file目录,因为升级包中的recovery.img不是跟out/target/product/*/recovery.img相同的,在执行ota_from_target_files 脚本的过程中会重新生成recovery.img。当然,现在的Android都是通过打patch(recovery.img基于boot.img的patch)的方式升级recovery的。
(5)拷贝一些文件到target_file的OTHER目录,最终的升级包一般直接把这些文件拷贝过去就ok了。
(6)把一些编译信息放在文件中,方便后面编译升级包时使用,比如分区信息(编译升级包时需要检测升级文件的大小是否大于对应的分区大小,如果大于的话当然无法升级了,只能终止编译)。
(7)先看下面命令的执行结果
这里写图片描述
这是在提取分区中文件的信息,包括文件权限,selinux相关的信息,这些信息将会在制作文件系统的时候使用。
(8)打包target_file.zip
(9)生成img文件,并将它们拷贝到target_file/IMAGE目录,后面可能会使用。
(10)调用replace_img_from_target_files.py脚本把target_file/IMAGE目录下面的img文件拷贝到out/target/product/目录。调用这个脚本的目的是使out/target/product/和升级包中的升级img文件保持一致,这跟Android的一个设计缺陷有关。下面就来了解一下
我们知道out/target/product//system.img文件是由out/target/product//system编译来的,按理说升级包中的system分区的文件来源也应该是out/target/product//system,但实际上升级包中的system分区的文件来源是target_file/SYSTEM。如果说out/target/product//system与target_file/SYSTEM两个目录的文件内容一样那也没什么问题,问题是这两个目录是不一样的,因为在执行ota_from_target_file脚本的时候会生成一个install_recovery.sh脚本(这个脚本就是在Android启动的时候执行,用来升级recovery的),然后把install_recovery.sh拷贝到target_file/SYSTEM,所以造成了最终out/target/product/*/system.img和升级包中的system(升级包中没有system.img,recovery用一种新的方法来升级system)的差异,这会引发下面的问题:
(a)如果你是用fastboot烧录system,那么你就会用到out/target/product/*/system.img,同时你升级recovery的方式是用install_recovery.sh来升级的,那你会发现你的recovery其实是没法升级的,因为在你的系统中压根就没有install_recovery.sh这个文件。
(b)如果你说,没关系,我是在recovery中直接把recovery.img写到recovery中升级的,(a)问题对我没影响。那你可能会发现你无法进行增量升级。因为增量升级包是通过比较两个版本的target_file.zip生成的,现在你的板子里面的system和增量包的基准版本不一致。
(11)执行 ./build/tools/releasetools/ota_from_target_files脚本,最终生成升级包。下面来分析一下 ./build/tools/releasetools/ota_from_target_files。

四、ota_from_target_files脚本分析

1.使用说明(常用)
-k:指定key的路径,如build/target/product/security/testkey
-i:编译增量升级包, ./build/tools/releasetools/ota_from_target_files -i

def main(argv):

  def option_handler(o, a):                              ----------------------------------(1)
    if o == "--board_config":
      pass   # deprecated
    elif o in ("-k", "--package_key"):
      OPTIONS.package_key = a
    elif o in ("-i", "--incremental_from"):
      OPTIONS.incremental_source = a
    elif o == "--full_radio":
      OPTIONS.full_radio = True
    elif o == "--full_bootloader":
      OPTIONS.full_bootloader = True
    elif o in ("-w", "--wipe_user_data"):
      OPTIONS.wipe_user_data = True
    elif o in ("-n", "--no_prereq"):
      OPTIONS.omit_prereq = True
    elif o in ("-o", "--oem_settings"):
      OPTIONS.oem_source = a
    elif o in ("-e", "--extra_script"):
      OPTIONS.extra_script = a
    elif o in ("-a", "--aslr_mode"):
      if a in ("on", "On", "true", "True", "yes", "Yes"):
        OPTIONS.aslr_mode = True
      else:
        OPTIONS.aslr_mode = False
    elif o in ("-t", "--worker_threads"):
      if a.isdigit():
        OPTIONS.worker_threads = int(a)
      else:
        raise ValueError("Cannot parse value %r for option %r - only "
                         "integers are allowed." % (a, o))
    elif o in ("-2", "--two_step"):
      OPTIONS.two_step = True
    elif o == "--no_signing":
      OPTIONS.no_signing = True
    elif o == "--verify":
      OPTIONS.verify = True
    elif o == "--block":
      OPTIONS.block_based = True
    elif o in ("-b", "--binary"):
      OPTIONS.updater_binary = a
    elif o in ("--no_fallback_to_full",):
      OPTIONS.fallback_to_full = False
    elif o == "--stash_threshold":
      try:
        OPTIONS.stash_threshold = float(a)
      except ValueError:
        raise ValueError("Cannot parse value %r for option %r - expecting "
                         "a float" % (a, o))
    else:
      return False
    return True

  args = common.ParseOptions(argv, __doc__,         ------------------------------------(2)
                             extra_opts="b:k:i:d:wne:t:a:2o:",
                             extra_long_opts=[
                                 "board_config=",
                                 "package_key=",
                                 "incremental_from=",
                                 "full_radio",
                                 "full_bootloader",
                                 "wipe_user_data",
                                 "no_prereq",
                                 "extra_script=",
                                 "worker_threads=",
                                 "aslr_mode=",
                                 "two_step",
                                 "no_signing",
                                 "block",
                                 "binary=",
                                 "oem_settings=",
                                 "verify",
                                 "no_fallback_to_full",
                                 "stash_threshold=",
                             ], extra_option_handler=option_handler)

  if len(args) != 2:
    common.Usage(__doc__)
    sys.exit(1)

  if OPTIONS.extra_script is not None:                               --------------------(3)
    OPTIONS.extra_script = open(OPTIONS.extra_script).read()

  print "unzipping target target-files..."
  OPTIONS.input_tmp, input_zip = common.UnzipTemp(args[0])          ---------------------(4)

  OPTIONS.target_tmp = OPTIONS.input_tmp
  OPTIONS.info_dict = common.LoadInfoDict(input_zip)               ----------------------(5)

  # If this image was originally labelled with SELinux contexts, make sure we
  # also apply the labels in our new image. During building, the "file_contexts"
  # is in the out/ directory tree, but for repacking from target-files.zip it's
  # in the root directory of the ramdisk.
  if "selinux_fc" in OPTIONS.info_dict:
    OPTIONS.info_dict["selinux_fc"] = os.path.join(
        OPTIONS.input_tmp, "BOOT", "RAMDISK", "file_contexts")

  if OPTIONS.verbose:
    print "--- target info ---"
    common.DumpInfoDict(OPTIONS.info_dict)

  # If the caller explicitly specified the device-specific extensions
  # path via -s/--device_specific, use that.  Otherwise, use
  # META/releasetools.py if it is present in the target target_files.
  # Otherwise, take the path of the file from 'tool_extensions' in the
  # info dict and look for that in the local filesystem, relative to
  # the current directory.

  if OPTIONS.device_specific is None:               -------------------------(6)
    from_input = os.path.join(OPTIONS.input_tmp, "META", "releasetools.py")
    if os.path.exists(from_input):
      print "(using device-specific extensions from target_files)"
      OPTIONS.device_specific = from_input
    else:
      OPTIONS.device_specific = OPTIONS.info_dict.get("tool_extensions", None)

  if OPTIONS.device_specific is not None:
    OPTIONS.device_specific = os.path.abspath(OPTIONS.device_specific)

  while True:

    if OPTIONS.no_signing:                       ---------------------------(7)
      if os.path.exists(args[1]):
        os.unlink(args[1])
      output_zip = zipfile.ZipFile(args[1], "w",
                                   compression=zipfile.ZIP_DEFLATED)
    else:
      temp_zip_file = tempfile.NamedTemporaryFile()
      output_zip = zipfile.ZipFile(temp_zip_file, "w",
                                   compression=zipfile.ZIP_DEFLATED)

    cache_size = OPTIONS.info_dict.get("cache_size", None)
    if cache_size is None:                         -------------------------------(8)
      raise RuntimeError("can't determine the cache partition size")
    OPTIONS.cache_size = cache_size

    if OPTIONS.incremental_source is None:         -------------------------------(9)
      WriteFullOTAPackage(input_zip, output_zip)   -------------------------------(10)
      if OPTIONS.package_key is None:
        OPTIONS.package_key = OPTIONS.info_dict.get(
            "default_system_dev_certificate",
            "build/target/product/security/testkey")
      common.ZipClose(output_zip)
      break

    else:
      print "unzipping source target-files..."
      OPTIONS.source_tmp, source_zip = common.UnzipTemp(
          OPTIONS.incremental_source)
      OPTIONS.target_info_dict = OPTIONS.info_dict
      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)
      if "selinux_fc" in OPTIONS.source_info_dict:
        OPTIONS.source_info_dict["selinux_fc"] = os.path.join(
            OPTIONS.source_tmp, "BOOT", "RAMDISK", "file_contexts")
      if OPTIONS.package_key is None:
        OPTIONS.package_key = OPTIONS.source_info_dict.get(
            "default_system_dev_certificate",
            "build/target/product/security/testkey")
      if OPTIONS.verbose:
        print "--- source info ---"
        common.DumpInfoDict(OPTIONS.source_info_dict)
      try:
        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
        common.ZipClose(output_zip)
        break
      except ValueError:
        if not OPTIONS.fallback_to_full:
          raise
        print "--- failed to build incremental; falling back to full ---"
        OPTIONS.incremental_source = None
        common.ZipClose(output_zip)

  if not OPTIONS.no_signing:
    SignOutput(temp_zip_file.name, args[1])                      ---------------------(11)
    temp_zip_file.close()

  print "done."

(1)首先定义一个命令参数处理的回调函数option_handler,这个回调方法把参数对应的值存储在一个全局的OPTIONS变量中,后续的编译通过判断OPTIONS变量执行不同的流程。
(2)调用common.ParseOptions方法()位于commom.py文件中,该方法的功能就是处理一些自己能处理的参数(如-h),自己不能处理的参数就交给option_handler来处理。
(3)当用户使用-e参数指定一个文件时,最终会在升级包脚本的适当位置添加指定文件的内容。这为一些私有的命令插入updater-script提供了方便。
(4)OPTIONS.input_tmp是target_file.zip解压后的临时目录;input_zip是target_file的句柄,后面可以通过这个句柄来操作target_file.zip。可以参看common.py的UnzipTemp方法。
(5)根据target_file.zip中的META/misc_info.txt文件初始化字典,这个要重点分析一下,因为这个字典里面存放着一些重要的信息。
首先我们看看misc_info.txt的内容:
这里写图片描述
再来看看具体处理misc_info.txt的流程:

def LoadInfoDict(input_file):        ---这里的input_file就是target_file.zip
  """Read and parse the META/misc_info.txt key/value pairs from the
  input target files and return a dict."""
  ---由上面的注释可以知道,LoadInfoDict方法的功能是解析target_file.zip中的META/misc_info.txt文件,并以key:value的形式存放在OPTIONS.info_dict字典中
  def read_helper(fn):
    if isinstance(input_file, zipfile.ZipFile):    
      return input_file.read(fn)        ---input_file就是zipfile.ZipFile对象,因此走if分支
    else:
      path = os.path.join(input_file, *fn.split("/"))
      try:
        with open(path) as f:
          return f.read()
      except IOError as e:
        if e.errno == errno.ENOENT:
          raise KeyError(fn)
  d = {}
  try:
    d = LoadDictionaryFromLines(read_helper("META/misc_info.txt").split("\n"))    ---处理misc_info.txt文件,下面会分析LoadDictionaryFromLines方法
  except KeyError:
    # ok if misc_info.txt doesn't exist
    pass

  # backwards compatibility: These values used to be in their own
  # files.  Look for them, in case we're processing an old
  # target_files zip.

  if "mkyaffs2_extra_flags" not in d:
    try:
      d["mkyaffs2_extra_flags"] = read_helper(
          "META/mkyaffs2-extra-flags.txt").strip()
    except KeyError:
      # ok if flags don't exist
      pass
  ----下面几个if语句就是判断misc_info.txt文件的正确性了
  if "recovery_api_version" not in d:
    try:
      d["recovery_api_version"] = read_helper(
          "META/recovery-api-version.txt").strip()
    except KeyError:
      raise ValueError("can't find recovery API version in input target-files")

  if "tool_extensions" not in d:
    try:
      d["tool_extensions"] = read_helper("META/tool-extensions.txt").strip()
    except KeyError:
      # ok if extensions don't exist
      pass

  if "fstab_version" not in d:
    d["fstab_version"] = "1"

  try:    ---我的项目中没有这个文件,不分析了
    data = read_helper("META/imagesizes.txt")
    for line in data.split("\n"):
      if not line:
        continue
      name, value = line.split(" ", 1)
      if not value:
        continue
      if name == "blocksize":
        d[name] = value
      else:
        d[name + "_size"] = value
  except KeyError:
    pass

最终OPTONS.info_dict={recovery_api_version:3, fstab_version:2, blocksize:4096, recovery_size:33554432, custom_size:536870912
recovery_mount_options:ext4=max_batch_time=0,commit=1,data=ordered,barrier=1,errors=panic,nodelalloc
tool_extensions:device/*/m96/../common,
default_system_dev_certificate:build/target/product/security/testkey,

}
至于OPTONS.info_dict的用处,后面用到了再来分析,总之,很多时候我们编译升级包出错都是因为OPTONS.info_dict的内容不合法导致的。
(6)这里应该是指定一个OEM厂商自己定制的一个Python脚本(可以通过-s参数传入),完成一些厂商自己定制的一些功能。我当前的项目没有指定-s参数,根据代码分析会使用OPTONS.info_dict中的tool_extensions:device/*/m96/../common,这显然不是一个Python脚本,如果后面使用到的话会报错的,然而我当前没有使用,这里就不去深究了。
(7)可以通过–no_signing参数指定不对升级包签名,当然一般厂商是不会这么做的。
(8)cache_size就是misc_info.txt中的“cache_size=536870912”这个字段,如果misc_info.txt文件中没有这个字段的话就会终止编译。顺便说一下cache_size的值是在BoardConfig.mk中设置的:
BOARD_CACHEIMAGE_PARTITION_SIZE := 536870912
(9)通过-i参数可以使OPTIONS.incremental_source=True,由于本文讲述的是全量升级包的编译,所有这里只讲述if分支的流程。
(10)全量升级包的生成由这个方法完成,前面的代码都是浮云。下面就来仔细看看WriteFullOTAPackage方法

def WriteFullOTAPackage(input_zip, output_zip):
  # TODO: how to determine this?  We don't know what version it will
  # be installed on top of. For now, we expect the API just won't
  # change very often. Similarly for fstab, it might have changed
  # in the target build.
  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)           --------------------(a)

  oem_props = OPTIONS.info_dict.get("oem_fingerprint_properties")                       
  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")--------------------(b)
  oem_dict = None
  if oem_props is not None and len(oem_props) > 0:
    if OPTIONS.oem_source is None:
      raise common.ExternalError("OEM source required for this build")
    script.Mount("/oem", recovery_mount_options)
    oem_dict = common.LoadDictionaryFromLines(
        open(OPTIONS.oem_source).readlines())

  metadata = {
      "post-build": CalculateFingerprint(oem_props, oem_dict,
                                         OPTIONS.info_dict),
      "pre-device": GetOemProperty("ro.product.device", oem_props, oem_dict,
                                   OPTIONS.info_dict),
      "post-timestamp": GetBuildProp("ro.build.date.utc", OPTIONS.info_dict),
  }

  device_specific = common.DeviceSpecificParams(                       
      input_zip=input_zip,
      input_version=OPTIONS.info_dict["recovery_api_version"],
      output_zip=output_zip,
      script=script,
      input_tmp=OPTIONS.input_tmp,
      metadata=metadata,
      info_dict=OPTIONS.info_dict)

  has_recovery_patch = HasRecoveryPatch(input_zip)              ---------------------------(c)
  block_based = OPTIONS.block_based and has_recovery_patch      ---------------------------(d)

  if not OPTIONS.omit_prereq:                                   ---------------------------(e)
    ts = GetBuildProp("ro.build.date.utc", OPTIONS.info_dict)
    ts_text = GetBuildProp("ro.build.date", OPTIONS.info_dict)
    script.AssertOlderBuild(ts, ts_text)

  AppendAssertions(script, OPTIONS.info_dict, oem_dict)
  device_specific.FullOTA_Assertions()
  # delete /cache/backup
  script.AppendExtra('delete_recursive(\"/cache/backup\");')
  kernel_img = common.GetOtherImage("boot.img", "boot.img", ------------------------------(f)
                                      OPTIONS.input_tmp, "OTHER")
  ldfw_img = common.GetOtherImage("ldfw", "ldfw",
                                      OPTIONS.input_tmp, "OTHER")

  if OPTIONS.two_step:                                       -----------------------------(g)
    if not OPTIONS.info_dict.get("multistage_support", None):
      assert False, "two-step packages not supported by this build"
    fs = OPTIONS.info_dict["fstab"]["/misc"]
    assert fs.fs_type.upper() == "EMMC", \
        "two-step packages only supported on devices with EMMC /misc partitions"
    bcb_dev = {"bcb_dev": fs.device}
    common.CheckSize(recovery_img.data, "recovery", OPTIONS.info_dict)
    common.ZipWriteStr(output_zip, "recovery", recovery_img.data)
    script.AppendExtra("""
if get_stage("%(bcb_dev)s") == "2/3" then
""" % bcb_dev)
    script.WriteRawImage("/recovery", "recovery")
    script.WriteRawImage("/ldfw", "ldfw")
    script.AppendExtra("""
set_stage("%(bcb_dev)s", "3/3");
reboot_now("%(bcb_dev)s", "recovery");
else if get_stage("%(bcb_dev)s") == "3/3" then
""" % bcb_dev)

  # Dump fingerprints
  script.Print("Target: %s" % CalculateFingerprint(
      oem_props, oem_dict, OPTIONS.info_dict))

  device_specific.FullOTA_InstallBegin()

  system_progress = 0.95

  if OPTIONS.wipe_user_data:
    system_progress -= 0.1
  if HasVendorPartition(input_zip):
    system_progress -= 0.1

  if "selinux_fc" in OPTIONS.info_dict:
    WritePolicyConfig(OPTIONS.info_dict["selinux_fc"], output_zip)

  recovery_mount_options = OPTIONS.info_dict.get("recovery_mount_options")

  system_items = ItemSet("system", "META/filesystem_config.txt")
  script.ShowProgress(system_progress, 100)

  if block_based:                                        ----------------------(h)
    # Full OTA is done as an "incremental" against an empty source
    # image.  This 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, OPTIONS.info_dict)
    system_tgt.ResetFileMap()
    system_diff = common.BlockDifference("system", system_tgt, src=None)
    system_diff.WriteScript(script, output_zip)
  else:
    script.FormatPartition("/system")
    script.Mount("/system", recovery_mount_options)
    if not has_recovery_patch:
      script.UnpackPackageDir("recovery", "/system")
    script.UnpackPackageDir("system", "/system")

    symlinks = CopyPartitionFiles(system_items, input_zip, output_zip)
    script.MakeSymlinks(symlinks)

  boot_img = common.GetOtherImage("boot.img", "boot.img",
                                     OPTIONS.input_tmp, "OTHER")

  if not block_based:
    def output_sink(fn, data):
      common.ZipWriteStr(output_zip, "recovery/" + fn, data)
      system_items.Get("system/" + fn)

    common.MakeRecoveryPatch(OPTIONS.input_tmp, output_sink,
                             recovery_img, boot_img)

    system_items.GetMetadata(input_zip)
    system_items.Get("system").SetPermissions(script)

  if HasVendorPartition(input_zip):
    vendor_items = ItemSet("vendor", "META/vendor_filesystem_config.txt")
    script.ShowProgress(0.1, 0)

    if block_based:
      vendor_tgt = GetImage("vendor", OPTIONS.input_tmp, OPTIONS.info_dict)
      vendor_tgt.ResetFileMap()
      vendor_diff = common.BlockDifference("vendor", vendor_tgt)
      vendor_diff.WriteScript(script, output_zip)
    else:
      script.FormatPartition("/vendor")
      script.Mount("/vendor", recovery_mount_options)
      script.UnpackPackageDir("vendor", "/vendor")

      symlinks = CopyPartitionFiles(vendor_items, input_zip, output_zip)
      script.MakeSymlinks(symlinks)

      vendor_items.GetMetadata(input_zip)
      vendor_items.Get("vendor").SetPermissions(script)

  custom_path = os.path.join(OPTIONS.input_tmp, "CUSTOM")

  script.ShowProgress(0.05, 3)
  common.CheckSize(recovery_img.data, "recovery", OPTIONS.info_dict)
  common.ZipWriteStr(output_zip, "recovery.img", recovery_img.data)
  common.CheckSize(ldfw_img.data, "ldfw", OPTIONS.info_dict)
  common.ZipWriteStr(output_zip, "ldfw", ldfw_img.data)
  script.WriteRawImage("/recovery", "recovery.img")
  #script.WriteRawImage("/dtb", "dtb")
  script.WriteRawImage("/ldfw", "ldfw")
  common.CheckSize(boot_img.data, "boot.img", OPTIONS.info_dict)
  common.ZipWriteStr(output_zip, "boot.img", boot_img.data)
  script.WriteRawImage("/boot", "boot.img")

  bootloader_img = common.GetOtherImage("bootloader", "bootloader",
                                     OPTIONS.input_tmp, "OTHER")
  common.CheckSize(bootloader_img.data, "bootloader", OPTIONS.info_dict)
  common.ZipWriteStr(output_zip, "bootloader", bootloader_img.data)

  script.WriteRawImage("/bootloader", "bootloader")

  device_specific.FullOTA_InstallEnd()

  if OPTIONS.extra_script is not None:
    script.AppendExtra(OPTIONS.extra_script)
  script.UnmountAll()

  if OPTIONS.wipe_user_data:
    script.ShowProgress(0.1, 10)
    script.FormatPartition("/data")

  if OPTIONS.two_step:
    script.AppendExtra("""
set_stage("%(bcb_dev)s", "");
""" % bcb_dev)
    script.AppendExtra("else\n")
    script.WriteRawImage("/ldfw", "ldfw")
    script.AppendExtra("""
set_stage("%(bcb_dev)s", "2/3");
reboot_now("%(bcb_dev)s", "");
endif;
endif;
""" % bcb_dev)
  script.AddToZip(input_zip, output_zip, input_path=OPTIONS.updater_binary)
  WriteMetadata(metadata, output_zip)
(a)初始化一个EdifyGenerator对象,这个对象很有用,后面读写updater-script脚本都是通过这个对象来操作的。
(b)recovery中挂载分区的参数,在misc_info.txt中指定。
(c)判断target_file.zip中是否存在SYSTEM/recovery-from-boot.p文件,SYSTEM/recovery-from-boot.p文件是recovery.img基于boot.img生成的一个patch文件,recovery的升级就靠它了。
(d)block_based是决定system的升级方式的一个变量。如果block_based=True,将以block的方式升级system;如果block_based=False,将以文件的形式升级system。而block_based=True还是               False,则由OPTIONS.block_based和has_recovery_patch共同决定,其中OPTIONS.block_based是通过-b参数指定,而SYSTEM/recovery-from-boot.p存在时has_recovery_patch=True,这里我想不明白为何要存在SYSTEM/recovery-from-boot.p才能使用block方式升级system,有知道的同学指教。
(e)可以通过在参数中加-n参数将OPTIONS.omit_prereq设置为True,如果OPTIONS.omit_prereq=Ture,那么升级时会比较升级包编译时间和当前板子里面固件版本的编译时间,如果前者比后者早,那么recovery将会终止升级。我认为这没有什么意义,只会影响升级调试。
(f)获取boot的数据,来看看是怎么获取的。
boot_img = common.GetOtherImage("boot.img", "boot.img",   OPTIONS.input_tmp, "OTHER")
def GetOtherImage(name, prebuilt_name, unpack_dir, tree_subdir,
                     info_dict=None):
  """Return a File object (with name 'name') with the desired bootable
  image.  Look for it in 'unpack_dir'/BOOTABLE_IMAGES under the name
  'prebuilt_name', otherwise look for it under 'unpack_dir'/IMAGES,
  otherwise construct it from the source files in
  'unpack_dir'/'tree_subdir'."""
---uppack_dir=OPTIONS.input_tmp,就是target_file.zip解压的那个临时目录(在/tmp目录下),prebuilt_path=/tmp/临时目录/OTHER/recovery.img,指向target_file.zip中的OTHER/recovery.img,还记得OTHER/boot.img是在那拷贝的吗?请看第三章中对makefile的分析
  prebuilt_path = os.path.join(unpack_dir, "OTHER", prebuilt_name)    
  if os.path.exists(prebuilt_path):
    print "using prebuilt %s from OTHER..." % (prebuilt_name,)
    return File.FromLocalFile(name, prebuilt_path)

  if data:
    return File(name, data)
  return None
(g)通过-2参数可以指定升级分两个步骤进行。这通常可能是在已经发布的版本的recovery存在bug,必须先升级recovery分区修复bug才能顺利的对其他分区进行升级的情况,当然你要保证升级recovery不会出问题。
(h)生成system的升级文件,对于block升级方式,system升级文件包括system.new.dat、system.patch.dat、system.transfer.list三个文件。对于文件升级方式,system升级文件包括一个system目录。现在的Android通常会采用block方式升级system,这大大的提高了升级效率。
block方式升级在updater-script脚本中通过下面命令完成:
block_image_update("/dev/block/platform/msm_sdcc.1/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat");  
block_image_update对应的函数定义在:
bootable/recovery/updater/blockimg.c:BlockImageUpdateFn()中。
代码中有一段注释用于描述transfer list文件的内容,它支持如下命令:
1) 文件的第一行是版本号,当前是1;
2) 文件的第二行是总共需要写入的block数量(后面new命令的range加起来应该等于该值);
3) erase [rangeset]: 将目标分区的range清除;
4) zero [rangeset]:将目标分区的range使用0填充;
5) new [rangeset]: 将目标分区的range使用new_data文件填充;

比如如下的一个system.transfer.list文件:(不同版本(文件中第一行数字不同)的system.transfer.list内容可能会有差异)
1
90270
erase 2,0,262144
new 28,0,32767,32768,32770,32833,32835,33347,65535,65536,65538,98304,98306,98369,98371,98883,124176,131072,131074,163840,163842,163905,163907,196608,196610,229376,229378,229441,229443
第一行1表示该transfer文件的版本为1;
第二行表示new命令总共要写入90270个block;
第三行表示删除的range是从0到262144,2表示range的区间描述数目是2个数值,即0和262144;
第四行表示从system.new.dat文件中读取block,然后依次写入如下14个区间:[0, 32767), [32768, 32770) ...这个区间的block总数刚好是前面描述的90270个。
    这样的做法实际上是一个稀疏数组的区间描述,用以降低升级包文件的大小和写入的数据量。另外还减少了升级时对文件的索引过程,极大的提高了升级速度。
文件升级方式比较原始,直接把升级包中的文件解压到system分区。

(11)对升级包签名

五、总结

升级包的编译流程可以总结为如下几个步骤:
1.编译出各个分区的升级文件(boot.img、system目录等)
2.将第一步生成的文件打包成一个target_file.zip
3.从target_file.zip中提取需要的文件到升级包中
4.对升级包签名

  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
percona-data-recovery-tool-for-innodb-0.5是一个由Percona公司提供的针对InnoDB存储引擎的数据恢复工具。它可以帮助用户在数据丢失或者损坏时恢复InnoDB数据库。 它的主要功能包括以下几个方面: 1. 支持数据恢复:当InnoDB数据库出现故障时,如数据库文件损坏、误删除数据等情况,该工具可以帮助用户恢复损坏的数据。它可以读取InnoDB存储引擎的日志文件,通过分析日志,找到丢失的数据更新,然后恢复数据。 2. 支持错误检测:该工具可以扫描InnoDB存储引擎的数据文件,检测出潜在的错误或损坏的数据页。通过识别和修复这些问题,可以帮助保障数据库的数据完整性和一致性。 3. 支持删选和修改数据:用户可以使用该工具针对InnoDB存储引擎中的数据进行删选和修改。通过设置过滤条件,用户可以选择指定类型的数据进行操作,如根据时间范围、特定字段的值等。 4. 支持日志文件处理:该工具可以解析和处理InnoDB存储引擎的日志文件,包括重做日志和撤销日志。用户可以使用它来查看、分析和恢复日志文件中的数据更新操作,以及进行相关的数据恢复操作。 总之,percona-data-recovery-tool-for-innodb-0.5是一款专门针对InnoDB存储引擎的数据恢复工具,它可以帮助用户在数据库出现故障时进行数据恢复,保障数据的完整性和可用性。这对于数据库管理员和开发者来说是非常重要的工具。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值