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

一、前言

在我的Android recovery分析(一)—全量升级包的编译流程一文中已经对全量升级包的编译流程作了分析,本文将分析增量升级包的编译流程。
注:本文中的叙述纯属个人理解,欢迎批评指正。

二、增量升级包制作步骤

  1. source build/envsetup.sh
  2. lunch (选择合适的配置)
  3. 执行make otapackage命令,生成out/target/product//obj/PACKAGING/target_files_intermediates/*target_files.zip,本文称这个zip包为source target_file.zip
  4. 再次执行make otapackage命令,生成out/target/product//obj/PACKAGING/target_files_intermediates/*target_files.zip,本文称这个zip包为dest target_file.zip
  5. ./build/tools/releasetools/ota_from_target_files -i source _target_file.zip dest_target_file.zip incremental_ota_package.zip
    全量包和增量包使用的是同一个python脚本,只是参数不一样而已。上面的命令只是最基本的编译命令,实际使用时可以根据需求添加不同的参数。如下是我在一个项目中使用到的编译增量包命令:
./build/tools/releasetools/ota_from_target_files -v \
       --block \
       -p out/host/linux-x86 \
       -k build/target/product/security/testkey \
       -i ./target_file_source.zip ./inc_upgrade/target_file_dest.zip ./update_inc.zip

-v:打印一些额外编译信息
–block:指定system以block方式升级(这种升级方式我在前面的文章中有介绍)
-p:指定编译过程中使用的一些工具存放的路径
-k:指定升级包签名使用的私钥
-i:指定当前编译的是增量升级包

三、ota_from_target_files脚本分析

在Android recovery分析(一)—全量升级包的编译流程一文中已经针对全量升级包编译流程对ota_from_target_files脚本做了讲解,本文讲解增量升级部分流程。

    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")
      common.ZipClose(output_zip)
      break

    else:
      print "unzipping source target-files..."
      OPTIONS.source_tmp, source_zip = common.UnzipTemp(                ----------------(1)
          OPTIONS.incremental_source)
      OPTIONS.target_info_dict = OPTIONS.info_dict                                  
      OPTIONS.source_info_dict = common.LoadInfoDict(source_zip)      -----------------(2)
      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:                                 -----------------(3)
        OPTIONS.package_key = OPTIONS.source_info_dict.get(
            "default_system_dev_certificate",
            "build/target/product/security/testkey")
      if OPTIONS.verbose:                                             ------------------(4)
        print "--- source info ---"
        common.DumpInfoDict(OPTIONS.source_info_dict)
      try:
        WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)    ------------------(5)
        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)

上面的代码是ota_from_target_files脚本的main方法的一个片段,在前文中针对if分支进行了讲解,而增量升级将走else分支,因此本文重点讲解else分支流程。
(1)获得source target_file.zip的文件句柄,同时将source target_file.zip解压到一个临时目录(注意,该目录/tmp/目录下,如果你的/tmp目录所挂载的那个分区没有剩余空间将会导致编译错误)
(2)处理source target_file.zip的misc_info.txt,存放在OPTIONS.source_info_dict中,后面可能会与OPTIONS.target_info_dict对比。具体如何处理我在前文中有介绍。
(3)获取签名的私钥,可用-k参数指定,不指定将使用默认key(”build/target/product/security/testkey”)
(4)参数中加-v时OPTIONS.verbose=True,条件成立,打印OPTIONS.source_info_dict
(5)WriteIncrementalOTAPackage方法中会判断是否编译block升级包(block方式升级),如果编译的是block升级包,那么会调用WriteBlockIncrementalOTAPackage方法,WriteIncrementalOTAPackage方法本身并没做什么有意义的事情。由于现在的Android基本都会使用block升级包,因此这里重点分析WriteBlockIncrementalOTAPackage方法。

def WriteIncrementalOTAPackage(target_zip, source_zip, output_zip):
  target_has_recovery_patch = HasRecoveryPatch(target_zip)
  source_has_recovery_patch = HasRecoveryPatch(source_zip)

  if (OPTIONS.block_based and
      target_has_recovery_patch and
      source_has_recovery_patch):
    return WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip)
    ...

四、WriteBlockIncrementalOTAPackage方法分析

def WriteBlockIncrementalOTAPackage(target_zip, source_zip, output_zip):
  source_version = OPTIONS.source_info_dict["recovery_api_version"]
  target_version = OPTIONS.target_info_dict["recovery_api_version"]

  if source_version == 0:
    print ("WARNING: generating edify script for a source that "
           "can't install it.")
  script = edify_generator.EdifyGenerator(
      source_version, OPTIONS.target_info_dict,
      fstab=OPTIONS.source_info_dict["fstab"])

  metadata = {      ---生成元数据,最后会输出到META-INF/com/android/metadata
      "pre-device": GetBuildProp("ro.product.device",
                                 OPTIONS.source_info_dict),
      "post-timestamp": GetBuildProp("ro.build.date.utc",
                                     OPTIONS.target_info_dict),
  }

  ...

  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 = (not OPTIONS.two_step and                                    ------------(1)
                   (source_boot.data != target_boot.data))

  target_recovery = common.GetBootableImage(
      "/tmp/recovery.img", "recovery.img", OPTIONS.target_tmp, "RECOVERY")

  system_src = GetImage("system", OPTIONS.source_tmp, OPTIONS.source_info_dict)  ----------(2)
  system_tgt = GetImage("system", OPTIONS.target_tmp, OPTIONS.target_info_dict)

  blockimgdiff_version = 1
  if OPTIONS.info_dict:
    blockimgdiff_version = max(
        int(i) for i in
        OPTIONS.info_dict.get("blockimgdiff_versions", "1").split(","))

  system_diff = common.BlockDifference("system", system_tgt, system_src,   -------------(3)
                                       version=blockimgdiff_version)

 ...

  oem_props = OPTIONS.target_info_dict.get("oem_fingerprint_properties")
  recovery_mount_options = OPTIONS.source_info_dict.get(
      "recovery_mount_options")

  ...

  # delete /cache/backup
  script.AppendExtra('delete_recursive(\"/cache/backup\");')         ------------------(4)

  if OPTIONS.two_step:
    if not OPTIONS.source_info_dict.get("multistage_support", None):
      assert False, "two-step packages not supported by this build"
    fs = OPTIONS.source_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.ZipWriteStr(output_zip, "recovery.img", target_recovery.data)
    script.AppendExtra("""
if get_stage("%(bcb_dev)s") == "2/3" then
""" % bcb_dev)
    script.AppendExtra("sleep(20);\n")
    script.WriteRawImage("/recovery", "recovery.img")
    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)
...

  script.Print("Verifying current system...")

  ...
  if updating_boot:
    boot_type, boot_device = common.GetTypeAndDevice(
        "/boot", OPTIONS.source_info_dict)
    d = common.Difference(target_boot, source_boot)                      -----------------(5)
    _, _, d = d.ComputePatch()
    if d is None:
      include_full_boot = True
      common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    else:
      include_full_boot = False

      print "boot      target: %d  source: %d  diff: %d" % (
          target_boot.size, source_boot.size, len(d))

      common.ZipWriteStr(output_zip, "patch/boot.img.p", d)

      script.PatchCheck("%s:%s:%d:%s:%d:%s" %
                        (boot_type, boot_device,
                         source_boot.size, source_boot.sha1,
                         target_boot.size, target_boot.sha1))

  if OPTIONS.two_step:
    script.WriteRawImage("/boot", "recovery.img")       
    script.AppendExtra("""
set_stage("%(bcb_dev)s", "2/3");
reboot_now("%(bcb_dev)s", "");
else
""" % bcb_dev)

  # Verify the existing partitions.
  system_diff.WriteVerifyScript(script)                              -------------(6)

  script.Comment("---- start making changes here ----")
  system_diff.WriteScript(script, output_zip,                        -------------(7)
                          progress=0.8 if vendor_diff else 0.9)

  size = []
  #script.ShowProgress(0.8, 0)
  total_patch_size = 1.0

  # Note that this call will mess up the trees of Items, so make sure
  # we're done with them.
  target_symlinks_d = dict([(i[1], i[0]) for i in target_symlinks])
  source_symlinks_d = dict([(i[1], i[0]) for i in source_symlinks])

  # Delete all the symlinks in source that aren't in target.  This
  # needs to happen before verbatim files are unpacked, in case a
  # symlink in the source is replaced by a real file in the target.
  to_delete = []
  for dest, link in source_symlinks:
    if link not in target_symlinks_d:
      to_delete.append(link)
  script.DeleteFiles(to_delete)

  # Create all the symlinks that don't already exist, or point to
  # somewhere different than what we want.  Delete each symlink before
  # creating it, since the 'symlink' command won't overwrite.
  to_create = []
  for dest, link in target_symlinks:
    if link in source_symlinks_d:
      if dest != source_symlinks_d[link]:
        to_create.append((dest, link))
    else:
      to_create.append((dest, link))
  script.DeleteFiles([i[1] for i in to_create])
  script.MakeSymlinks(to_create)

  # Now that the symlinks are created, we can set all the
  # permissions.
  script.AppendScript(temp_script)

  if OPTIONS.two_step:
    common.ZipWriteStr(output_zip, "boot.img", target_boot.data)
    script.WriteRawImage("/boot", "boot.img")
    print "writing full boot image (forced by two-step mode)"

  if not OPTIONS.two_step:
    if updating_boot:
    ---如果前面生成了boot.img.p,则include_full_boot=False,当include_full_boot=True时,升级boot分区的方式与全量升级包相同,即把boot.img直接写入到/dev/block/*/boot。否则通过apply_patch命令把boot.img.p patch到/dev/block/*/boot分区。
      if include_full_boot:
        print "boot image changed; including full."
        script.Print("Installing boot image...")
        script.WriteRawImage("/boot", "boot.img")
      else:
        # Produce the boot image by applying a patch to the current
        # contents of the boot partition, and write it back to the
        # partition.
        print "boot image changed; including patch."
        script.Print("Patching boot image...")
        script.ShowProgress(0.1, 10)
        script.ApplyPatch("%s:%s:%d:%s:%d:%s"
                          % (boot_type, boot_device,
                             source_boot.size, source_boot.sha1,
                             target_boot.size, target_boot.sha1),
                          "-",
                          target_boot.size, target_boot.sha1,
                          source_boot.sha1, "patch/boot.img.p")
    else:
      print "boot image unchanged; skipping."

  # Do device-specific installation (eg, write radio image).

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

  if OPTIONS.wipe_user_data:    ---加-w参数会走下面流程
    script.Print("Erasing user data...")
    ---通过下面方法可以在updater-script脚本中添加"format("ext4", "emmc", "分区节点", "分区大小", "挂载选项");"命令,对应recovery中格式化某个分区
    script.FormatPartition("/data")   

  if OPTIONS.two_step:
    script.AppendExtra("""
set_stage("%(bcb_dev)s", "");
endif;
endif;
""" % bcb_dev)

  script.SetProgress(1)
  script.AddToZip(target_zip, output_zip, input_path=OPTIONS.updater_binary)    ------(8)
  WriteMetadata(metadata, output_zip)                                           ------(9)

(1)在之前已经得到了source target_file.zip中的boot.img和dest target_file.zip的boot.img数据分别为source_boot 与 target_boot,这里只是判断source_boot 与 target_boot是否相同来决定是否需要升级boot分区
(2)分别从source target_file.zip和dest target_file.zip中获取system数据,为后面system.new.dat、system.patch.dat、system.transfer.list三个文件提供数据来源
(3)生成system.new.dat、system.patch.dat、system.transfer.list三个文件
(4)删除/cache/backup分区。增量升级会把一些临时文件保存在/cache,必须保证/cache有足够的空间能存放这些文件。
(5)当boot.img有变化时生成boot.img.p文件,在updater-script脚本中对应下面命令:

apply_patch("EMMC:/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/boot:10029952:f612ec8c2b942b00d24869b30b6f30ae9c51242e:10029952:b5e0802411a106af93cf1984c62d44a6a0be3a4b",
            "-", b5e0802411a106af93cf1984c62d44a6a0be3a4b, 10029952,
            f612ec8c2b942b00d24869b30b6f30ae9c51242e, package_extract_file("patch/boot.img.p"));

对应recovery中的ApplyPatchFn函数,ApplyPatchFn函数的功能就是把boot.img.p中的数据填充到boot分析适当的位置。
(6)校验升级前的system分区是否为基准版本,如果不是的话当然无法升级,因为system.new.dat、system.patch.dat、system.transfer.list是基于基准版本生成的。
在updater-script脚本中对应下面命令:

if (range_sha1("/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system", "72,1,32770,32929,32931,33439,65535,65536,65538,66046,98303,98304,98306,98465,98467,98975,131071,131072,131074,131582,163839,163840,163842,164001,164003,164511,196607,196608,196610,197118,229375,229376,229378,229537,229539,230047,262143,262144,262146,262654,294911,294912,294914,295073,295075,295583,327679,327680,327682,328190,355086,360448,360450,393216,393218,425984,425986,458752,458754,491520,491522,524288,524290,557056,557058,589824,589826,622592,622594,623102,650190,650191,655320") == "3168346b9a857c0dd0e962e86032bf464a007957" || block_image_verify("/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat")) then
ui_print("Verified system image...");
else
abort("system partition has unexpected contents");
endif;

对应recovery中的RangeSha1Fn函数
(7)生成升级system的命令,在updater-script脚本中对应下面命令:

block_image_update("/dev/block/platform/mtk-msdc.0/11230000.msdc0/by-name/system", package_extract_file("system.transfer.list"), "system.new.dat", "system.patch.dat");

对应recovery中的BlockImageUpdateFn函数
(8)把updater可执行程序放进升级包中,重命名为update-binary。update-binary是完成升级的程序,负责解析updater-script脚本中的命令并执行对应的C函数。
(9)把元数据输出到升级包的META-INF/com/android/metadata中。

你可能感兴趣的:(recovery)