#升级包的制作与升级的流程
一、升级包的制作
1、Makefile
make otapackage是一个.PHONY伪目标,查看\build\core\Makefile:
.PHONY: otapackage
otapackage:droidcore dist_files $(INTERNAL_OTA_PACKAGE_TARGET)
可以看到otapackage这个伪目标是依赖于$(INTERNAL_OTA_PACKAGE_TARGET)的
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) $@
可以看到,$(INTERNAL_OTA_PACKAGE_TARGET)的生成依赖于$(BUILT_TARGET_FILES_PACKAGE)和$(DISTTOOLS)等生成
$(DISTTOOLS)的编译规则如下:
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)
变量$(HOST_OUT_EXECUTABLES)指代的是out/host/linux-x86/bin目录,而变量$(HOST_OUT_JAVA_LIBRARIES)/表示的是out/host/linux-x86/framework目录。可以看出,$(OTATOOLS)用于指定命令执行所需要的工具,例如:minigzip:用于gzip文件;make_ext4fs:将文件转换为ext4类型;mkyaffs2image:用于yaffs文件系统;bsdiff,imgdiff用于差分;signapk.jar用于签名等功能。
$(BUILT_TARGET_FILES_PACKAGE)主要是完成了两件事:重新打包system.img文件和生成差分资源包。$(BUILT_TARGET_FILES_PACKAGE)的编译规则如下所示:
# -----------------------------------------------------------------
# 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)
这块target-files-package目标主要实现的功能:
● 创建$(zip_root)目录,$(zip_root)即out/target/product/
● 创建/$(zip_root)/RECOVERY目录并将COPY相关文件,包括:kernel镜像文件、RAMDISK目录。此目录最终用来生成recovery.img
● 创建/$(zip_root)/BOOT目录并将COPY相关文件,包括:kernel镜像文件、RAMDISK目录、ramdisk镜像。此目录最终用来生成boot.img
● 创建其他目录并COPY文件,包括SYSTEM目录、OTA/bin目录、META目录
● 将$(zip_root)目录压缩为资源差分包
完成这些工作后调用ota_from_target_files做进一步的处理:
$(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)
Deprecated.
-k (--package_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)
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)
Insert the contents of file at the end of the update script.
-a (--aslr_mode)
Specify whether to turn on ASLR for the package (on by default).
用法:ota_from_target_files [flags] input_target_files output_ota_package
-i:生成增量OTA包时使用此选项
-k:签名所使用的密钥
-w:是否清除userdata分区
-e:是否有额外运行的脚本
-p:定义脚本用到的一些可执行文件的路径
具体来看下这个脚本的执行过程:
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中的ParseOptions,主要是调用python中的getopt模块中的getopt函数来解析参数
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
然后解压目标文件包
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)
其中LoadInfoDict主要是解析了三个文件:META/misc_info.txt、SYSTEM/build.prop、RECOVERY/RAMDISK/etc/recovery.fstab,并将文件内容以(k,v)键值对形式保存到OPTIONS.info_dict中。
随后根据incremental_source的值,也就是之前的-i参数来判断制作全量包还是增量包:
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)
先来分析全量包的制作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.
script = edify_generator.EdifyGenerator(3, OPTIONS.info_dict)
新建了updater-script文件,这是一个Android内置的Edify可扩展语言脚本,通过内建函数命令来执行操作,是整个升级包的核心。
随后开始向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)
然后执行:
# 将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是tmp目录下的临时zip文件,最后回到main函数执行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就是output_zip对象对应的文件名称,args[1]就是最终的OTA zip包的名称。实际执行的就是我们平时手动签名的命令。
到这里完成了全量包的制作,接下来对于差分包的制作WriteIncrementalOTAPackage进行一下分析:
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)
至此,我们所需的差分包就制作完成了。
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");
二、升级流程
recovery.cpp头部的注释:
/*
* 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
*/
这部分的注释详细说明了recovery的两大功能:FACTORY RESET和OTA INSTALL的流程。
这段注释告诉我们,执行恢复出厂设置或者OTA升级,我们需要先向/cache/recovery/command中写入命令:
* 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
所以先来看下制作完成的升级包是如何进入到升级流程中的。
以升级U盘中的update.zip为例,插入U盘检测到升级包之后,我们进入Settings开始进入升级流程。无论上层应用如何操作升级包的来源,最终都是调用RecoverySystem.installPackage来执行升级操作。
public static void installPackage(Context context, File packageFile)
throws IOException {
String arg = getInstallPackageArg(context, packageFile);
bootCommand(context, arg, true);
}
这里的getInstallPackageArg是比较关键的,它就是处理/cache/recovery/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;
}
继续往下看,进入了bootCommand方法中:
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?)");
}
然后进入了PMS的reboot,接着shutdownOrRebootInternal,继而ShutdownThread.reboot,接着rebootOrShutdown,最后回到了PMS的lowLevelReboot:
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();
}
}
最后带着reason=update进入kernel开始重启。
经历了重启之后,kernel加载recovery.img,起来后执行的第一个进程就是init,此进程读取init.rc启动相应的服务:
......
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
......
这样就回到了开头所说了recovery模式的入口:recovery.cpp,我们从main函数开始分析
//将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);
只有挂载到了对应的分区,才能访问前面的command文件,然后才能正确打开升级包,上面这步也是非常重要的。
挂载完相应的分区以后,就需要获取命令参数:
//从/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;
}
}
获取到对应的命令,就会执行对应的标志,后面会根据标志来执行对应的操作。做完以上的流程后,下面就是设置语言信息,创建设备,初始化recovery的UI界面,设置Selinux权限等操作:
//设置语言
if (locale == NULL) {
load_locale_from_cache();
}
//创建设备
Device* device = make_device();
//获取UI
ui = device->GetUI();
//设置当前的UI
gCurrentUI = ui;
//UI初始化
ui->Init();
......
接下来根据上面传入的命令参数,开始真正进入到重要的执行环节:
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) {
......
}
这里就是执行OTA升级和恢复出厂设置的地方了,恢复出厂设置也就是手机双清,清除/data分区和/cache分区,代码流程还是比较清楚详细的,可以跟一下,这边先不展开了。
接下来的重点就是在install_package方法上面了:
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;
}