您好,登錄后才能下訂單哦!
#升級包的制作與升級的流程
一、升級包的制作
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/<product-name>/obj/PACKAGING/target_files_from_intermedias/<product-name>-target_files-<version-name>
● 創建/$(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) <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:生成增量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;
}
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。