Upgrade package and upgrade the production process Introduction

# Upgrade package and upgrade production processes

First, the upgrade package production

1、Makefile

make otapackage .PHONY is a pseudo-objective view \ build \ core \ Makefile:

.PHONY: otapackage
otapackage:droidcore dist_files $(INTERNAL_OTA_PACKAGE_TARGET)

Otapackage can see this pseudo-objective is dependent on the $ (INTERNAL_OTA_PACKAGE_TARGET) of

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: $@"
    @echo ./build/tools/releasetools/ota_from_target_files -v \
       $(amlogic_flag) \
       $(omit_prereq_flag) \
       $(UPDATE_DTB) \
       -n \
       -p $(HOST_OUT) \
       $(wipeopt) \
       $(baksupport) \  
       -k $(KEY_CERT_PAIR) \
       $(recovery_not_patch) \
       $(dm_verity) \
       $(BUILT_TARGET_FILES_PACKAGE) $@

Can be seen, $ (INTERNAL_OTA_PACKAGE_TARGET) generated depends on the $ (BUILT_TARGET_FILES_PACKAGE) and $ (DISTTOOLS) generates the like

$ (DISTTOOLS) compiled rules are as follows:

DISTTOOLS :=  $(HOST_OUT_EXECUTABLES)/minigzip \
      $(HOST_OUT_EXECUTABLES)/mkbootfs \
      $(HOST_OUT_EXECUTABLES)/mkbootimg \
      $(HOST_OUT_EXECUTABLES)/fs_config \
      $(HOST_OUT_EXECUTABLES)/mkyaffs2image \
      $(HOST_OUT_EXECUTABLES)/zipalign \
      $(HOST_OUT_EXECUTABLES)/bsdiff \
      $(HOST_OUT_EXECUTABLES)/imgdiff \
      $(HOST_OUT_JAVA_LIBRARIES)/dumpkey.jar \
      $(HOST_OUT_JAVA_LIBRARIES)/signapk.jar \
      $(HOST_OUT_EXECUTABLES)/mkuserimg.sh \
      $(HOST_OUT_EXECUTABLES)/make_ext4fs \
      $(HOST_OUT_EXECUTABLES)/simg2img \
      $(HOST_OUT_EXECUTABLES)/e2fsck

OTATOOLS := $(DISTTOOLS) \
      $(HOST_OUT_EXECUTABLES)/aapt

.PHONY: otatools
otatools: $(OTATOOLS)

Variable $ (HOST_OUT_EXECUTABLES) refers to is out / host / linux-x86 / bin directory, and the variable $ (HOST_OUT_JAVA_LIBRARIES) / represents the out / host / linux-x86 / framework directory. As can be seen, $ (OTATOOLS) command is used to specify the tool required, for example: minigzip: for gzip file; make_ext4fs: ext4 to convert a file type; mkyaffs2image: yaffs a file system; bsdiff, imgdiff for the differential ; signapk.jar for signing and other functions.

$ (BUILT_TARGET_FILES_PACKAGE) is the completion of two things: repackage system.img file and generate a differential Resource Kit. $ (BUILT_TARGET_FILES_PACKAGE) compiled rules are as follows:

# -----------------------------------------------------------------
# A zip of the directories that map to the target filesystem.
# This zip can be used to create an OTA package or filesystem image
# as a post-build step.
#
name := $(TARGET_PRODUCT)
ifeq ($(TARGET_BUILD_TYPE),debug)
  name := $(name)_debug
endif
name := $(name)-target_files-$(FILE_NAME_TAG)

intermediates := $(call intermediates-dir-for,PACKAGING,target_files)
BUILT_TARGET_FILES_PACKAGE := $(intermediates)/$(name).zip
$(BUILT_TARGET_FILES_PACKAGE): intermediates := $(intermediates)
$(BUILT_TARGET_FILES_PACKAGE): \
        zip_root := $(intermediates)/$(name)

# $(1): Directory to copy
# $(2): Location to copy it to
# The "ls -A" is to prevent "acp s/* d" from failing if s is empty.
define package_files-copy-root
  if [ -d "$(strip $(1))" -a "$$(ls -A $(1))" ]; then \
    mkdir -p $(2) && \
    $(ACP) -rd $(strip $(1))/* $(2); \
  fi
endef

built_ota_tools := \
    $(call intermediates-dir-for,EXECUTABLES,applypatch)/applypatch \
    $(call intermediates-dir-for,EXECUTABLES,applypatch_static)/applypatch_static \
    $(call intermediates-dir-for,EXECUTABLES,check_prereq)/check_prereq \
    $(call intermediates-dir-for,EXECUTABLES,sqlite3)/sqlite3 \
    $(call intermediates-dir-for,EXECUTABLES,updater)/updater
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_OTA_TOOLS := $(built_ota_tools)

$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_RECOVERY_API_VERSION := $(RECOVERY_API_VERSION)
$(BUILT_TARGET_FILES_PACKAGE): PRIVATE_RECOVERY_FSTAB_VERSION := $(RECOVERY_FSTAB_VERSION)

# Depending on the various images guarantees that the underlying
# directories are up-to-date.
#开始构建中间zip包
$(BUILT_TARGET_FILES_PACKAGE): \
        $(INSTALLED_BOOTIMAGE_TARGET) \
        $(INSTALLED_RADIOIMAGE_TARGET) \
        $(INSTALLED_RECOVERYIMAGE_TARGET) \
        $(INSTALLED_AMLOGIC_RECOVERY_TARGET) \
        $(INSTALLED_SYSTEMIMAGE) \
        $(INSTALLED_USERDATAIMAGE_TARGET) \
        $(INSTALLED_CACHEIMAGE_TARGET) \
        $(INSTALLED_VENDORIMAGE_TARGET) \
        $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
        $(SELINUX_FC) \
        $(built_ota_tools) \
        $(APKCERTS_FILE) \
        $(HOST_OUT_EXECUTABLES)/fs_config \
        | $(ACP) \
        $(INSTALLED_AML_LOGO) \
        $(TARGET_AMLOGIC_KERNEL)
    @echo "Package target files: $@"
    # 删除之前的zip文件
    $(hide) rm -rf $@ $(zip_root)
    $(hide) mkdir -p $(dir $@) $(zip_root)
    @# Components of the recovery image
    $(hide) mkdir -p $(zip_root)/RECOVERY
    $(hide) $(call package_files-copy-root, \
    $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)

    ifdef INSTALLED_KERNEL_TARGET
        $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel
    endif
    ifdef TARGET_AMLOGIC_RECOVERY_KERNEL
        $(hide) $(ACP) $(TARGET_AMLOGIC_RECOVERY_KERNEL) $(zip_root)/RECOVERY/kernel
    endif
    ifdef INSTALLED_2NDBOOTLOADER_TARGET
        $(hide) $(ACP) \
            $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second
    endif
    ifdef INSTALLED_BOARDDTB_TARGET
        $(hide) $(ACP) $(INSTALLED_BOARDDTB_TARGET) $(zip_root)/RECOVERY/second
    endif
    ifdef BOARD_KERNEL_CMDLINE
        $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/RECOVERY/cmdline
    endif
    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
    ifdef BOARD_KERNEL_OFFSET
        $(hide) echo "$(BOARD_KERNEL_OFFSET)" > $(zip_root)/RECOVERY/kernel_offset
    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 TARGET_AMLOGIC_KERNEL
        $(hide) $(ACP) $(TARGET_AMLOGIC_KERNEL) $(zip_root)/BOOT/kernel
    endif

    ......

    # 打包zip包
    #ifeq ($(PRODUCT_BUILD_SECURE_BOOT_IMAGE_DIRECTLY),true)
    $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
    @# Run fs_config on all the system, 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 -S $(SELINUX_FC) > $(zip_root)/META/filesystem_config.txt
    $(hide) zipinfo -1 $@ | awk 'BEGIN { FS="BOOT/RAMDISK/" } /^BOOT\/RAMDISK\// {print $$2}' | $(HOST_OUT_EXECUTABLES)/fs_config -C -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 -S $(SELINUX_FC) > $(zip_root)/META/recovery_filesystem_config.txt
    $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/*filesystem_config.txt)

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

This target-files-package target main function:

● 创建$(zip_root)目录,$(zip_root)即out/target/product/<product-name>/obj/PACKAGING/target_files_from_intermedias/<product-name>-target_files-<version-name>

● Create / $ (zip_root) / RECOVERY COPY directory and related documents, including: kernel image file, RAMDISK directory. This directory is used to generate the final recovery.img

● Create / $ (zip_root) / BOOT directory and COPY-related documents, including: kernel image file, RAMDISK directory, ramdisk image. This directory is used to generate the final boot.img

● COPY create other directories and files, including the SYSTEM directory, OTA / bin directory, META directory

● The $ (zip_root) for the resource poor sub-directory compression

Ota_from_target_files calls for further processing after the completion of these tasks:

$(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(DISTTOOLS)
    @echo "Package OTA: $@"
    @echo ./build/tools/releasetools/ota_from_target_files -v \
       $(amlogic_flag) \
       $(omit_prereq_flag) \
       $(UPDATE_DTB) \
       -n \
       -p $(HOST_OUT) \
       $(wipeopt) \
       $(baksupport) \
       -k $(KEY_CERT_PAIR) \
       $(recovery_not_patch) \
       $(dm_verity) \
       $(BUILT_TARGET_FILES_PACKAGE) $@

2、ota_from_target_files

/build/tools/releasetools/ota_from_target_files.py

Given a target-files zipfile, produces an OTA package that installs
that build.  An incremental OTA is produced if -i is given, otherwise
a full OTA is produced.

Usage:  ota_from_target_files [flags] input_target_files output_ota_package

  -b  (--board_config)  <file>
      Deprecated.

  -k (--package_key) <key> Key to use to sign the package (default is
      the value of default_system_dev_certificate from the input
      target-files's META/misc_info.txt, or
      "build/target/product/security/testkey" if that value is not
      specified).
      For incremental OTAs, the default value is based on the source
      target-file, not the target build.

  -i  (--incremental_from)  <file>
      Generate an incremental OTA using the given target-files zip as
      the starting build.

  -w  (--wipe_user_data)
      Generate an OTA package that will wipe the user data partition
      when installed.

  -n  (--no_prereq)
      Omit the timestamp prereq check normally included at the top of
      the build scripts (used for developer OTA packages which
      legitimately need to go back and forth).

  -e  (--extra_script)  <file>
      Insert the contents of file at the end of the update script.

  -a  (--aslr_mode)  <on|off>
      Specify whether to turn on ASLR for the package (on by default).

用法:ota_from_target_files [flags] input_target_files output_ota_package

-i: Use this option when generating incremental OTA package

-k: key used to sign the

-w: whether to clear userdata partition

-e: Are there additional running script

-p: Path of some executable files used by custom scripts

Specifically, under the execution of the script:

def main(argv):

  #将用户设定的option存入到一个OPTIONS类中
  def option_handler(o, a):
    if o in ("-b", "--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 in ("-w", "--wipe_user_data"):
      OPTIONS.wipe_user_data = True
    elif o in ("-s", "--no_wipe_system"):
      OPTIONS.no_wipe_system = True
    elif o in ("-n", "--no_prereq"):
      OPTIONS.omit_prereq = True
    elif o in ("-e", "--extra_script"):
      OPTIONS.extra_script = a
    elif o in ("-c", "--wipe_cache"):
      OPTIONS.wipe_cache_enable = True
    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 ("--worker_threads"):
      OPTIONS.worker_threads = int(a)
    elif o in ("--backup_support"):
      OPTIONS.backup_support = True
    elif o in ("--recovery_not_patch"):
      OPTIONS.recovery_not_patch = True
    elif o in ("--dm_verity"):
      OPTIONS.dm_verity_enable = True
    elif o in ("--progress_iptv"):
      OPTIONS.progress_iptv = True
    elif o in ("--dtb"):
      OPTIONS.update_dtb = True
    else:
      return False
    return True

    #解析参数,将得到的参数和参数值传回给args
    args = common.ParseOptions(argv, __doc__,
                             extra_opts="b:k:i:wsneca:",
                             extra_long_opts=["board_config=",
                                              "package_key=",
                                              "incremental_from=",
                                              "wipe_user_data",
                                              "no_wipe_system",
                                              "no_prereq",
                                              "extra_script=",
                                              "wipe_cache",
                                              "dm_verity",
                                              "progress_iptv",
                                              "dtb",
                                              "worker_threads=",
                                              "aslr_mode=",
                          "backup_support",
                          "recovery_not_patch",
                                              ],
                             extra_option_handler=option_handler)

common.py in ParseOptions, mainly in python call getopt module getopt function to parse the parameter

def ParseOptions(argv,
                 docstring,
                 extra_opts="", extra_long_opts=(),
                 extra_option_handler=None):
  """Parse the options in argv and return any arguments that aren't
  flags.  docstring is the calling module's docstring, to be displayed
  for errors and -h.  extra_opts and extra_long_opts are for flags
  defined by the caller, which are processed by passing them to
  extra_option_handler."""

  try:
    opts, args = getopt.getopt(
        argv, "ahvp:s:x:" + extra_opts,
        ["amlogic", "help", "verbose", "path=", "signapk_path=", "extra_signapk_args=",
         "java_path=", "public_key_suffix=", "private_key_suffix=",
         "device_specific=", "extra="] +
        list(extra_long_opts))
  except getopt.GetoptError, err:
    Usage(docstring)
    print "**", str(err), "**"
    sys.exit(2)

  path_specified = False

  for o, a in opts:
    if o in ("-h", "--help"):
      Usage(docstring)
      sys.exit()
    elif o in ("-a", "--amlogic"):
      OPTIONS.amlogic = True
    elif o in ("-v", "--verbose"):
      OPTIONS.verbose = True
    elif o in ("-p", "--path"):
      OPTIONS.search_path = a
    elif o in ("--signapk_path",):
      OPTIONS.signapk_path = a
    elif o in ("--extra_signapk_args",):
      OPTIONS.extra_signapk_args = shlex.split(a)
    elif o in ("--java_path",):
      OPTIONS.java_path = a
    elif o in ("--public_key_suffix",):
      OPTIONS.public_key_suffix = a
    elif o in ("--private_key_suffix",):
      OPTIONS.private_key_suffix = a
    elif o in ("-s", "--device_specific"):
      OPTIONS.device_specific = a
    elif o in ("-x", "--extra"):
      key, value = a.split("=", 1)
      OPTIONS.extras[key] = value
    else:
      if extra_option_handler is None or not extra_option_handler(o, a):
        assert False, "unknown option \"%s\"" % (o,)

  os.environ["PATH"] = (os.path.join(OPTIONS.search_path, "bin") +
                        os.pathsep + os.environ["PATH"])

  return args

Then extract the target package

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

#保存解压目录名称
OPTIONS.target_tmp = OPTIONS.input_tmp
#根据target-files-package生成的zipfile对象
OPTIONS.info_dict = common.LoadInfoDict(input_zip)

Wherein LoadInfoDict mainly three files is parsed: META / misc_info.txt, SYSTEM / build.prop, RECOVERY / RAMDISK / etc / recovery.fstab, and the file contents (k, v) in the form of key-value pairs to save OPTIONS. info_dict in.

Then according to the value incremental_source, i.e. before production -i parameters to determine the total amount of incremental package or packages:

if OPTIONS.incremental_source is None:
WriteFullOTAPackage(input_zip, output_zip)
if OPTIONS.package_key is None:
  OPTIONS.package_key = OPTIONS.info_dict.get(
      "default_system_dev_certificate",
      "build/target/product/security/testkey")
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 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)
WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)

First to analyze production WriteFullOTAPackage full amount of the package:

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.
  script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)

Built updater-script file, which is a built-in Android Edify extensible scripting language, built-in functions to perform an operation by command, is the core of the upgrade package.

Then he began to write to the updater-script:

if not OPTIONS.omit_prereq:
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)
device_specific.FullOTA_Assertions()
device_specific.FullOTA_InstallBegin()

if OPTIONS.progress_iptv:
script.ShowProgress(0.8, 40)
else:
script.ShowProgress(0.9, 95)

platform = GetBuildProp("ro.board.platform", OPTIONS.info_dict)
print "ro.board.platform: %s" % (platform)
if "meson3" in platform:
script.SetBootloaderEnv("upgrade_step", "0")
elif "meson6" in platform:
script.SetBootloaderEnv("upgrade_step", "0")
else:
script.SetBootloaderEnv("upgrade_step", "3")

if OPTIONS.wipe_user_data:
script.FormatPartition("/data")

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

if OPTIONS.dm_verity_enable:
script.FormatPartition("/system")
if not OPTIONS.recovery_not_patch:
  OPTIONS.recovery_not_patch = True
  print 'enable write recovery.img in dm-verity'
else:
if not OPTIONS.no_wipe_system:
  script.FormatPartition("/system")
  if OPTIONS.backup_support:
    script.FormatPartition("/backup")
script.Mount("/system")

if not OPTIONS.dm_verity_enable:
script.UnpackPackageDir("recovery", "/system")
script.UnpackPackageDir("system", "/system")

symlinks = CopySystemFiles(input_zip, output_zip)
script.MakeSymlinks(symlinks)

Then execute:

# 将updater-script和update-binary写入到output_zip
script.AddToZip(input_zip, output_zip)
#将metadata信息写入到output_zip的META-INF/com/android/metadata文件中
WriteMetadata(metadata, output_zip)

output_zip here is temporary zip file in the tmp directory, and finally back to main function is executed SignOutput:

SignOutput(temp_zip_file.name, args[1])

def SignOutput(temp_zip_name, output_zip_name):
  key_passwords = common.GetKeyPasswords([OPTIONS.package_key])
  pw = key_passwords[OPTIONS.package_key]

  common.SignFile(temp_zip_name, output_zip_name, OPTIONS.package_key, pw,
                  whole_file=True)

temp_zip_file.name is output_zip corresponding object file name, args [1] is the name of the final OTA zip bag. The actual implementation of what we usually order a manual signature.

Here completed the production of the full amount of the package, followed by sub-contractors for poor production WriteIncrementalOTAPackage conduct some analysis:

def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):

    script = edify_generator.EdifyGenerator(source_version,
                                          OPTIONS.target_info_dict)

    #加载源、目标zip文件中的SYSTEM目录文件
    print "Loading target..."
    target_data = LoadSystemFiles(target_zip)
    print "Loading source..."
    source_data = LoadSystemFiles(source_zip)

    matching_file_cache = {}
    for fn in source_data.keys():
    sf = source_data[fn]
    assert fn == sf.name
    matching_file_cache["path:" + fn] = sf
    # Only allow eligability for filename/sha matching
    # if there isn't a perfect path match.
    if target_data.get(sf.name) is None:
      matching_file_cache["file:" + fn.split("/")[-1]] = sf
      matching_file_cache["sha:" + sf.sha1] = sf

    for fn in sorted(target_data.keys()):
    tf = target_data[fn]
    assert fn == tf.name
    #在ClosestFileMatch构建源、目标文件对应关系
    sf = ClosestFileMatch(tf, matching_file_cache, renames)
    if sf is not None and sf.name != tf.name:
      print "File has moved from " + sf.name + " to " + tf.name
      renames[sf.name] = tf

    if sf is None or fn in OPTIONS.require_verbatim:
      # This file should be included verbatim
      if fn in OPTIONS.prohibit_verbatim:
        raise common.ExternalError("\"%s\" must be sent verbatim" % (fn,))
      print "send", fn, "verbatim"
      #写入目标文件或者增量文件到输出zip文件
      tf.AddToZip(output_zip)
      verbatim_targets.append((fn, tf.size))
    elif tf.sha1 != sf.sha1:
      # File is different; consider sending as a patch
      diffs.append(common.Difference(tf, sf))
    else:
      # Target file data identical to source (may still be renamed)
      pass

    common.ComputeDifferences(diffs)

    #检查Fingerprint是否一致以及挂载/system分区(写入脚本)
    source_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.source_info_dict)
    target_fp = GetBuildProp("ro.build.fingerprint", OPTIONS.target_info_dict)
    metadata["pre-build"] = source_fp
    metadata["post-build"] = target_fp

    script.Mount("/system")
    script.AssertSomeFingerprint(source_fp, target_fp)

    #从源zip、目标zip中分别获取boot.img、recovery.img
    if boot_img_exists:
    source_boot = common.GetBootableImage(
        "/tmp/boot.img", "boot.img", OPTIONS.source_tmp, "BOOT",
        OPTIONS.source_info_dict)
    target_boot = common.GetBootableImage(
        "/tmp/boot.img", "boot.img", OPTIONS.target_tmp, "BOOT")
    updating_boot = (source_boot.data != target_boot.data)

    if recovery_img_exists:
    source_recovery = common.GetBootableImage(
        "/tmp/recovery.img", "recovery.img", OPTIONS.source_tmp, "RECOVERY",
        OPTIONS.source_info_dict)
    target_recovery = common.GetBootableImage(
        "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")
    updating_recovery = (source_recovery.data != target_recovery.data)

    #校验所有源文件的SHA1(写入脚本)
    script.Print("Verifying current system...")

    device_specific.IncrementalOTA_VerifyBegin()

    script.ShowProgress(0.1, 0)
    total_verify_size = float(sum([i[2].size for i in patch_list]) + 1)
    if updating_boot:
    total_verify_size += source_boot.size
    if updating_bootloader:
    total_verify_size += target_bootloader.size
    if updating_dtb:
    total_verify_size += target_dtb.size
    so_far = 0

    for fn, tf, sf, size, patch_sha in patch_list:
    script.PatchCheck("/"+fn, tf.sha1, sf.sha1)
    so_far += sf.size
    script.SetProgress(so_far / total_verify_size)

    if updating_bootloader:
    so_far += target_bootloader.size
    script.SetProgress(so_far / total_verify_size)

    if updating_logo:
    so_far += target_logo.size
    script.SetProgress(so_far / total_verify_size)

    if updating_dtb:
    so_far += target_dtb.size
    script.SetProgress(so_far / total_verify_size)

    #如果boot有更新,校验boot的SHA1(写入脚本)
    if updating_boot:

    boot_type, boot_device = common.GetTypeAndDevice("/boot", OPTIONS.info_dict)

    #script.PatchCheck("%s:%s:%d:%s:%d:%s" %
    print("update boot_img %s:%s:from %d:%s:to %d:%s" %
                      (boot_type, boot_device,
                       source_boot.size, source_boot.sha1,
                       target_boot.size, target_boot.sha1))
    so_far += target_boot.size
    script.SetProgress(so_far / total_verify_size)

    #检验cache剩余空间是否足够(写入脚本)
    if patch_list or updating_recovery or updating_boot or updating_bootloader or updating_logo or updating_dtb:
        script.CacheFreeSpaceCheck(largest_source_size)

    #删除无需修改的文件(写入脚本)
    script.Print("Removing unneeded files...")
    script.DeleteFiles(["/"+i[0] for i in verbatim_targets] +
                     ["/"+i for i in sorted(source_data)
                            if i not in target_data and
                            i not in renames] +
                     ["/system/recovery.img"])

    #应用patch(写入脚本)
    script.Print("Patching system files...")
    deferred_patch_list = []
    for item in patch_list:
    fn, tf, sf, size, _ = item
    if tf.name == "system/build.prop":
      deferred_patch_list.append(item)
      continue
    script.ApplyPatch("/"+fn, "-", tf.size, tf.sha1, sf.sha1, "patch/"+fn+".p")
    so_far += tf.size

    if updating_boot:
    #如果boot有更新,写入img(写入脚本)
    script.Print("install boot image...");
    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    script.WriteRawImage("/boot", "boot.img")
    so_far += target_boot.size
    script.SetProgress(so_far / total_patch_size)
    print "boot image changed; including."
    else:
    print "boot image unchanged; skipping."

    if updating_recovery:
    #如果recovery有更新,写入img(写入脚本)      
    print("recovery_Image from:%d:%s: to %d:%s" %
                      (source_recovery.size, source_recovery.sha1,
                       target_recovery.size, target_recovery.sha1))
    script.Print("install recovery image...");
    common.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
    script.WriteRawImage("/recovery", "recovery.img")
    so_far += target_recovery.size
    script.SetProgress(so_far / total_patch_size)
    print "recovery image changed; including."
    else:
    print "recovery image unchanged; skipping."

    ......

    #将升级脚本写入到输出zip
    script.AddToZip(target_zip, output_zip)
    #将metadata写入到输出zip
    WriteMetadata(metadata, output_zip)

At this point, we need to make the difference between subcontractors completed.

Unpacking new recovery...
Renaming files...
script aborted: Rename of system/wplayerplugins/com.pptv.gstplayer.apk() to system/app/com.pptv.gstplayer.apk() failed, error No such file or directory()
Rename of system/wplayerplugins/com.pptv.gstplayer.apk() to system/app/com.pptv.gstplayer.apk() failed, error No such file or directory()
E:the child process end code is 7
E:Error in /cache/update.zip
(Status 7)
Installation aborted

ui_print("Renaming files...");
rename("system/wplayerplugins/com.pptv.gstplayer.apk", "system/app/com.pptv.gstplayer.apk");

Second, the upgrade process

recovery.cpp head notes:

/*
 * The recovery tool communicates with the main system through /cache files.
 *   /cache/recovery/command - INPUT - command line for tool, one arg per line
 *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)
 *   /cache/recovery/intent - OUTPUT - intent that was passed in
 *
 * The arguments which may be supplied in the recovery.command file:
 *   --send_intent=anystring - write the text out to recovery.intent
 *   --update_package=path - verify install an OTA package file
 *   --wipe_data - erase user data (and cache), then reboot
 *   --wipe_cache - wipe cache (but not user data), then reboot
 *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs
 *   --just_exit - do nothing; exit and reboot
 *
 * After completing, we remove /cache/recovery/command and reboot.
 * Arguments may also be supplied in the bootloader control block (BCB).
 * These important scenarios must be safely restartable at any point:
 *
 * FACTORY RESET
 * 1. user selects "factory reset"
 * 2. main system writes "--wipe_data" to /cache/recovery/command
 * 3. main system reboots into recovery
 * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"
 *    -- after this, rebooting will restart the erase --
 * 5. erase_volume() reformats /data
 * 6. erase_volume() reformats /cache
 * 7. finish_recovery() erases BCB
 *    -- after this, rebooting will restart the main system --
 * 8. main() calls reboot() to boot main system
 *
 * OTA INSTALL
 * 1. main system downloads OTA package to /cache/some-filename.zip
 * 2. main system writes "--update_package=/cache/some-filename.zip"
 * 3. main system reboots into recovery
 * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."
 *    -- after this, rebooting will attempt to reinstall the update --
 * 5. install_package() attempts to install the update
 *    NOTE: the package install must itself be restartable from any point
 * 6. finish_recovery() erases BCB
 *    -- after this, rebooting will (try to) restart the main system --
 * 7. ** if install failed **
 *    7a. prompt_and_wait() shows an error icon and waits for the user
 *    7b; the user reboots (pulling the battery, etc) into the main system
 * 8. main() calls maybe_install_firmware_update()
 *    ** if the update contained radio/hboot firmware **:
 *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
 *        -- after this, rebooting will reformat cache & restart main system --
 *    8b. m_i_f_u() writes firmware image into raw cache partition
 *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
 *        -- after this, rebooting will attempt to reinstall firmware --
 *    8d. bootloader tries to flash firmware
 *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
 *        -- after this, rebooting will reformat cache & restart main system --
 *    8f. erase_volume() reformats /cache
 *    8g. finish_recovery() erases BCB
 *        -- after this, rebooting will (try to) restart the main system --
 * 9. main() calls reboot() to boot main system
 */

NOTE This section details the recovery of two functions: FACTORY RESET and OTA INSTALL processes.

This comment tells us perform a factory reset or OTA upgrade, we need to write command first / cache / recovery / command in:

 * The arguments which may be supplied in the recovery.command file:
 *   --send_intent=anystring - write the text out to recovery.intent
 *   --update_package=path - verify install an OTA package file
 *   --wipe_data - erase user data (and cache), then reboot
 *   --wipe_cache - wipe cache (but not user data), then reboot
 *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs

So first look at the finished upgrade package is how to enter the upgrade process.

To update.zip upgrade U disk, for example, then insert U disk detected upgrade package, we began to enter into the Settings upgrade process. No matter how the upper application source upgrade package, ultimately calling RecoverySystem.installPackage to perform the upgrade.

public static void installPackage(Context context, File packageFile)
        throws IOException {
    String arg = getInstallPackageArg(context, packageFile);
    bootCommand(context, arg, true);
}

getInstallPackageArg here is more critical, it is the approach / cache / recovery / command in the command:

private static String getInstallPackageArg(Context context ,File packageFile)
        throws IOException {
    String filename = packageFile.getCanonicalPath();
    String strExt2Path = Environment.getExternalStorage2Directory().toString();
    String arg = null;

    if(filename.startsWith(strExt2Path)) {
        if(Environment.isExternalStorageBeSdcard()) {
            String newpath = filename.substring(4); 
            Log.w(TAG, "!!! REBOOTING TO INSTALL 1 " + newpath + " !!!");
            arg = "--update_package=" + newpath;

    String ibEnableStatus = SystemProperties.get("persist.sys.instaboot.enable");
    if ("enable".equals(ibEnableStatus) || "prepare".equals(ibEnableStatus)) {
        Log.w(TAG, "clean instaboot image");
        InstabootManager im = new InstabootManager(context);
        im.disable();
    }
            arg += "\n--locale=" + Locale.getDefault().toString();
        } else {
            String newpath = new String("/sdcard") + filename.substring(strExt2Path.length()); 
            Log.w(TAG, "!!! REBOOTING TO INSTALL 2 " + newpath + " !!!");
            arg = "--update_package=" + newpath;
            arg += "\n--locale=" + Locale.getDefault().toString();
        }
    } else if(filename.startsWith(Environment.getExternalStorageDirectory().toString())) {
        if(Environment.isExternalStorageBeSdcard()) {
            String absPath = packageFile.getAbsolutePath();
            if(SystemProperties.getInt("vold.fakesdcard.enable",0)==1 && absPath.startsWith("/mnt/sda1/")) {
                String newpath =new String("/udisk/")+absPath.substring(10); 
                Log.w(TAG, "!!! REBOOTING TO INSTALL 3-1 " + newpath + " !!!");
                arg = "--update_package=" + newpath;
                arg += "\n--locale=" + Locale.getDefault().toString();
            } else {
                String newpath = filename.substring(4); 
                Log.w(TAG, "!!! REBOOTING TO INSTALL 3-2 " + newpath + " !!!");
                arg = "--update_package=" + newpath;
                arg += "\n--locale=" + Locale.getDefault().toString();
            }
        } else {
            String newpath = new String("/media/" + packageFile.getName()); 
            Log.w(TAG, "!!! REBOOTING TO INSTALL 4 " + newpath + " !!!");
            arg = "--update_package=" + newpath;
            arg += "\n--locale=" + Locale.getDefault().toString();
        }
    } else if(filename.startsWith(Environment.getInternalStorageDirectory().toString())) {
        String newpath = new String("/media/"+packageFile.getName()); 
        Log.w(TAG, "!!! REBOOTING TO INSTALL 5 " + newpath + " !!!");
        arg = "--update_package=" + newpath;
        arg += "\n--locale=" + Locale.getDefault().toString();
    } else if(filename.startsWith("/udisk")) {
        String newpath =new String("/udisk/")+filename.substring(7);
        Log.w(TAG, "!!! REBOOTING TO INSTALL 6 " + newpath + " !!!");
        arg = "--update_package=" + newpath;
        arg += "\n--locale=" + Locale.getDefault().toString();
    } else {
        Log.w(TAG, "!!! REBOOTING TO INSTALL 7 " + filename + " !!!");
        arg = "--update_package=" + filename;
        arg += "\n--locale=" + Locale.getDefault().toString();
    }
    return arg;
}

Continue to look down into the bootCommand method:

private static void bootCommand(Context context, String arg, Boolean update) throws IOException {
    RECOVERY_DIR.mkdirs();  // 创建/cache/recovery
    COMMAND_FILE.delete();  // 清除/cache/recovery/command
    LOG_FILE.delete();

    FileWriter command = new FileWriter(COMMAND_FILE);
    FileOutputStream fos = new FileOutputStream(COMMAND_FILE);
    try {
        command.write(arg);//写入传进来的arg
        command.write("\n");
    } finally {
        command.close();
        FileUtils.sync(fos);
    }

    ......

    // Having written the command file, go ahead and reboot
    PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    if(update)
        pm.reboot("update");//重启升级
    else 
        pm.reboot("recovery");

    throw new IOException("Reboot failed (no permissions?)");
}

Then enter the reboot PMS, and then shutdownOrRebootInternal, then ShutdownThread.reboot, then rebootOrShutdown, finally returned to the lowLevelReboot PMS:

public static void lowLevelReboot(String reason) {
    if (reason == null) {
        reason = "";
    }
    SystemProperties.set("sys.powerctl", "reboot," + reason);
    try {
        Thread.sleep(20000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
}

Finally, with reason = update into the kernel restart at the beginning.

After a restart, kernel load recovery.img, the first process is executed after it is init, this process reads init.rc start the appropriate service:

......

on init
    //设置环境变量
    sysclktz 0
    loglevel 7
    export PATH /sbin:/system/sbin:/system/bin:/system/xbin
    export LD_LIBRARY_PATH /system/lib
    export ANDROID_ROOT /system
    export ANDROID_DATA /data
    export EXTERNAL_STORAGE /sdcard

    //建立连接
    symlink /system/etc /etc
    mkdir /system/bin
    symlink /sbin/sh /system/bin/sh

    //建立目录
    mkdir /sdcard
    mkdir /system
    mkdir /data
    mkdir /cache
    //挂载/tmp为内存文件系统tmpfs
    mount /tmp /tmp tmpfs

......

//启动recovery服务  
service recovery /sbin/recovery

//启动adbd服务
service adbd /sbin/adbd recovery
    disabled
    socket adbd stream 660 system system

......

This back entrance said at the beginning of the recovery mode: recovery.cpp, we start from the main function analysis

//将recovery产生的log信息重定向到/tmp/recovery.log这个文件里
freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);
freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);

//判断是否使用adb的sideload来传入,通过参数--adbd来判断
if (argc == 2 && strcmp(argv[1], "--adbd") == 0) {
    adb_main();
    return 0;
}

//初始化并装载recovery的分区表recovery.fstab
load_volume_table();

//在recovery中挂载/cache/recovery/last_log这个文件
ensure_path_mounted(LAST_LOG_FILE);
// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$max
rotate_last_logs(10);

Only mounted to the corresponding partitions to access the front of the command file before you can open the correct upgrade package, on top of this step is very important.

After completion of the appropriate partition to mount, you need to get command parameters:

//从/cache/recovery/command获取参数
get_args(&argc, &argv);

int arg;
while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
    switch (arg) {
    case 'q': aml_update_version = optarg; break;
    case 'p': previous_runs = atoi(optarg); break;
    case 's': send_intent = optarg; break;
    case 'u': update_package = optarg; break;
    case 'x': update_patch = optarg; break;
#ifdef RECOVERY_HAS_PARAM
    case 'w': wipe_data = wipe_cache = wipe_param = 1; break;
#else
    case 'w': wipe_data = wipe_cache = 1; break;
#endif
    case 'g': restore_system = 1; break;
    case 'c': wipe_cache = 1; break;
    case 't': show_text = 1; break;
    //case 'x': just_exit = true; break;
    case 'l': locale = optarg; break;
    case 'f': reboot_to_factorymode = 1; break;
    case 'n': usb_burning = 1; break;
    case 'o': without_format = 1; break;    
case 'z': file_copy_from_partition_args = optarg; break;
#ifdef RECOVERY_HAS_MEDIA
    case 'm': wipe_media = 1; break;
#endif /* RECOVERY_HAS_MEDIA */
#ifdef RECOVERY_HAS_PARAM
    case 'P': wipe_param = 1; break;
#endif /* RECOVERY_HAS_PARAM */

#ifdef RECOVERY_HAS_EFUSE
    case 'v': set_efuse_version = 1; efuse_version = optarg; break;
    case 'd': set_efuse_ethernet_mac = 1; break;
    case 'b': set_efuse_bluetooth_mac = 1; break;
#ifdef EFUSE_LICENCE_ENABLE
    case 'a': set_efuse_audio_license = 1; break;
#endif /* EFUSE_LICENCE_ENABLE */

#endif /* RECOVERY_HAS_EFUSE */

#ifdef RECOVERY_WRITE_KEY
    case 'B': flash_write_mac = 1; break;
    case 'C': flash_write_mac_force = 1; break;
    case 'D': flash_write_mac_bt = 1; break;
    case 'E': flash_write_mac_bt_force = 1; break;
    case 'F': flash_write_mac_wifi = 1; break;
    case 'G': flash_write_mac_wifi_force = 1; break;
    case 'H': flash_write_hdcp = 1; break;
    case 'I': flash_write_hdcp_force = 1; break;
    case 'J': flash_write_usid = 1; break;
    case 'K': flash_write_usid_force = 1; break;
#endif /* RECOVERY_WRITE_KEY */

    case 'R': restore_systembackup = 1; break;
    case 'e': just_exit = true; break;
    case 'r': run_cmd = 1; cmd_args = optarg; break;
    case 'h': keep_file_path = optarg; wipe_data = wipe_cache = 1; break;
    case 'y': erase_data = 1; break; //Add for The data partition is full when OTA upgrades
    case '?':
        LOGE("Invalid command argument\n");
        continue;
    }
}

To obtain the corresponding command, it executes the corresponding flags, flag back will be performed according to the corresponding operation. After finish the above procedure, the following information is to set the language, creation device, initialize the recovery of the UI interface, set Selinux permissions:

//设置语言
if (locale == NULL) {
    load_locale_from_cache();
}

//创建设备
Device* device = make_device();
//获取UI
ui = device->GetUI();
//设置当前的UI
gCurrentUI = ui;
//UI初始化
ui->Init();

......

The next parameter passed in the above command, began to truly enter into the implementation of important aspects:

if (update_package != NULL) {
    unlink("/data/property/persist.sys.needcheckdata");
    ui->SetBackground(RecoveryUI::INSTALLING_UPDATE);
    ui->SetProgressType(RecoveryUI::DETERMINATE);

    //执行install_package进行升级
    status = install_package(update_package, &wipe_cache, TEMPORARY_INSTALL_FILE);

    if (status != INSTALL_SUCCESS) {
        ui->Print("Installation aborted.\n");
        ui->ShowText(true);
    } else {
        ui->SetBackground(RecoveryUI::UPDATED);
        sleep(3);
    }
}

if (wipe_data) {

    ......

} else if (wipe_cache) {

    ......

}

Here is the place to perform OTA upgrade and restore factory settings, and factory reset the phone that is double clear, clear / data partition and / cache partitions, code flow is quite clear and detailed, can talk about, not here first started.

The next focus is on install_package the above method:

int
install_package(const char* path, int* wipe_cache, const char* install_file)
{
    FILE* install_log = fopen_path(install_file, "w");
    if (install_log) {
        fputs(path, install_log);
        fputc('\n', install_log);
    } else {
        LOGE("failed to open last_install: %s\n", strerror(errno));
    }
    int result;

    //设置安装挂载对应的节点,也就是判断挂载的路径是/tmp或者/cache
    if (setup_install_mounts() != 0) {
        LOGE("failed to set up expected mounts for install; aborting\n");
        set_upgrade_step("2");
        result = INSTALL_ERROR;
    } else {
        result = really_install_package(path, wipe_cache);
    }
    if (install_log) {
        fputc(result == INSTALL_SUCCESS ? '1' : '0', install_log);
        fputc('\n', install_log);
        fclose(install_log);
    }
    return result;
}

Guess you like

Origin blog.51cto.com/14367739/2402482