升级包的制作与升级流程简介
#升级包的制作与升级的流程
一、升级包的制作
1、Makefile
make otapackage是一个.PHONY伪目标,查看\build\core\Makefile:
.PHONY: otapackageotapackage: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)/e2fsckOTATOOLS := $(DISTTOOLS) \ $(HOST_OUT_EXECUTABLES)/aapt.PHONY: otatoolsotatools: $(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)_debugendifname := $(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); \ fiendefbuilt_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-packagetarget-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 installsthat build. An incremental OTA is produced if -i is given, otherwisea 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_dictOPTIONS.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_zipscript.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 7E:Error in /cache/update.zip(Status 7)Installation abortedui_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.fstabload_volume_table();//在recovery中挂载/cache/recovery/last_log这个文件ensure_path_mounted(LAST_LOG_FILE);// Rename last_log -> last_log.1 -> last_log.2 -> ... -> last_log.$maxrotate_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();//获取UIui = device->GetUI();//设置当前的UIgCurrentUI = 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方法上面了:
intinstall_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;}
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。