贵港网站制作,临安区规划建设局网站,软件开发过程的阶段划分,嘉兴中小企业网站制作Android OTA 升级之一#xff1a;编译升级包
作者#xff1a; 宋立新
Email#xff1a;zjujoeyahoo.com
前言 OTA 升级是 Android 系统提供的标准软件升级方式。 它功能强大#xff0c;提供了完全升级、增量升级模式#xff0c;可以通过 SD 卡升级#xff0c;也可以通… Android OTA 升级之一编译升级包
作者 宋立新
Emailzjujoeyahoo.com
前言 OTA 升级是 Android 系统提供的标准软件升级方式。 它功能强大提供了完全升级、增量升级模式可以通过 SD 卡升级也可以通过网络升级。 这里我们先研究最简单的情况通过 SD 卡进行完全升级。 如何执行升级就不多说了网上有很多资料。比如介绍HTC手机如何升级。我们感兴趣的是它是如何实现的作为开发者如何修改它以符合我们的定制化需求。 首先我们研究一下 ota 升级包的编译过程。
Quick start 首先编译出android, 然后执行
make otapackage 即可获得out/target/product/{product_name}/ {product_name}-ota-eng.{uid}.zip 将该文件改名为update.zip放到T卡根目录, 即可开始recovery模式下的 OTA 升级。
编译过程研究 主要分两步第一步 会准备一个包其中包含升级需要的内容(原材料)比如system 目录。
第二步运行python 脚本 ./build/tools/releasetools/ota_from_target_files以步骤一准备的ZIP包作为输入最终生成需要的升级包。 步骤一
编译脚本如下:
(From: build/core/Makefile) 1073 # Depending on the various images guarantees that the underlying 1074 # directories are up-to-date. 1075 $(BUILT_TARGET_FILES_PACKAGE): / 1076 $(INSTALLED_BOOTIMAGE_TARGET) / 1077 $(INSTALLED_RADIOIMAGE_TARGET) / 1078 $(INSTALLED_RECOVERYIMAGE_TARGET) / 1079 $(INSTALLED_FACTORYIMAGE_TARGET) / 1080 $(INSTALLED_SYSTEMIMAGE) / 1081 $(INSTALLED_USERDATAIMAGE_TARGET) / 1082 $(INSTALLED_SECROIMAGE_TARGET) / 1083 $(INSTALLED_ANDROID_INFO_TXT_TARGET) / 1084 $(built_ota_tools) / 1085 $(APKCERTS_FILE) / 1086 $(HOST_OUT_EXECUTABLES)/fs_config / 1087 | $(ACP) 1088 echo Package target files: $ 1089 $(hide) rm -rf $ $(zip_root) 1090 $(hide) mkdir -p $(dir $) $(zip_root) 1091 # Components of the recovery image 1092 $(hide) mkdir -p $(zip_root)/RECOVERY 1093 $(hide) $(call package_files-copy-root, / 1094 $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK) 1095 ifdef INSTALLED_KERNEL_TARGET 1096 $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel 1097 $(hide) $(ACP) $(recovery_ramdisk) $(zip_root)/RECOVERY/ramdisk 1098 endif 1099 ifdef INSTALLED_2NDBOOTLOADER_TARGET 1100 $(hide) $(ACP) / 1101 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second 1102 endif 1103 ifdef BOARD_KERNEL_CMDLINE 1104 $(hide) echo $(BOARD_KERNEL_CMDLINE) $(zip_root)/RECOVERY/cmdline 1105 endif 1106 ifdef BOARD_KERNEL_BASE 1107 $(hide) echo $(BOARD_KERNEL_BASE) $(zip_root)/RECOVERY/base 1108 endif 1109 # Components of the factory image 1110 $(hide) mkdir -p $(zip_root)/FACTORY 1111 $(hide) $(call package_files-copy-root, / 1112 $(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK) 1113 ifdef INSTALLED_KERNEL_TARGET 1114 $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/FACTORY/kernel 1115 endif 1116 ifdef INSTALLED_2NDBOOTLOADER_TARGET 1117 $(hide) $(ACP) / 1118 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/FACTORY/second 1119 endif 1120 ifdef BOARD_KERNEL_CMDLINE 1121 $(hide) echo $(BOARD_KERNEL_CMDLINE) $(zip_root)/FACTORY/cmdline 1122 endif 1123 ifdef BOARD_KERNEL_BASE 1124 $(hide) echo $(BOARD_KERNEL_BASE) $(zip_root)/FACTORY/base 1125 endif 1126 # Components of the boot image 1127 $(hide) mkdir -p $(zip_root)/BOOT 1128 $(hide) $(call package_files-copy-root, / 1129 $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK) 1130 ifdef INSTALLED_KERNEL_TARGET 1131 $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel 1132 $(hide) $(ACP) $(INSTALLED_RAMDISK_TARGET) $(zip_root)/BOOT/ramdisk 1133 endif 1134 ifdef INSTALLED_2NDBOOTLOADER_TARGET 1135 $(hide) $(ACP) / 1136 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second 1137 endif 1138 ifdef BOARD_KERNEL_CMDLINE 1139 $(hide) echo $(BOARD_KERNEL_CMDLINE) $(zip_root)/BOOT/cmdline 1140 endif 1141 ifdef BOARD_KERNEL_BASE 1142 $(hide) echo $(BOARD_KERNEL_BASE) $(zip_root)/BOOT/base 1143 endif 1144 $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),/ 1145 mkdir -p $(zip_root)/RADIO; / 1146 $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));) 1147 # Contents of the system image 1148 $(hide) $(call package_files-copy-root, / 1149 $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM) 1150 # Contents of the data image 1151 $(hide) $(call package_files-copy-root, / 1152 $(TARGET_OUT_DATA),$(zip_root)/DATA) 1153 # Extra contents of the OTA package 1154 $(hide) mkdir -p $(zip_root)/OTA/bin 1155 $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/ 1156 $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/ 1157 # Files that do not end up in any images, but are necessary to 1158 # build them. 1159 $(hide) mkdir -p $(zip_root)/META 1160 $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt 1161 $(hide) echo $(PRODUCT_OTA_PUBLIC_KEYS) $(zip_root)/META/otakeys.txt 1162 $(hide) echo $(PRIVATE_RECOVERY_API_VERSION) $(zip_root)/META/recovery-api-version.txt 1163 $(hide) echo blocksize $(BOARD_FLASH_BLOCK_SIZE) $(zip_root)/META/imagesizes.txt 1164 $(hide) echo boot $(call image-size-from-data-size,$(BOARD_BOOTIMAGE_PARTITION_SIZE)) $(zip_root)/META/imagesizes.txt 1165 $(hide) echo recovery $(call image-size-from-data-size,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE)) $(zip_root)/META/imagesizes.txt 1166 $(hide) echo system $(call image-size-from-data-size,$(BOARD_SYSTEMIMAGE_PARTITION_SIZE)) $(zip_root)/META/imagesizes.txt 1167 $(hide) echo secro $(call image-size-from-data-size,$(BOARD_SECROIMAGE_PARTITION_SIZE)) $(zip_root)/META/imagesizes.txt 1168 $(hide) echo userdata $(call image-size-from-data-size,$(BOARD_USERDATAIMAGE_PARTITION_SIZE)) $(zip_root)/META/imagesizes.txt 1169 $(hide) echo $(tool_extensions) $(zip_root)/META/tool-extensions.txt 1170 # Zip everything up, preserving symlinks 1171 $(hide) (cd $(zip_root) zip -qry ../$(notdir $) .) 1172 # Run fs_config on all the system files in the zip, and save the output 1173 $(hide) zipinfo -1 $ | awk -F/ BEGIN { OFS/ } /^SYSTEM/// {$$1 system; print} | $(HOST_OUT_EXECUTABLES)/fs_config $(zip_root)/META/filesystem_config.txt 1174 $(hide) (cd $(zip_root) zip -q ../$(notdir $) META/filesystem_config.txt) 可见往里面添加了很多内容。
L1089-1090 , 造一个目录。
L1091-1108填充 RECOVERY 子目录的内容。用于生成recovery.img。包括kernel 的image, recovery 根文件系统的 image, recovery 根文件系统的内容
RECOVERY$ tree -L 2
├── kernel
├── ramdisk
└── RAMDISK ├── advanced_meta_init.rc ├── data ├── default.prop ├── dev ├── etc ├── init ├── init.factory.rc ├── init.goldfish.rc ├── init.mt6516.rc ├── init.rc ├── meta_init.rc ├── proc ├── res ├── sbin ├── sys ├── system └── tmp
L1109-1125, 填充 FACTORY 子目录的内容 没有用到包括kernel 的image
L1126-1143, 填充 BOOT子目录的内容用于生成boot.img。和 RECOVERY目录类似包括kernel 的image,根文件系统的 image,根文件系统的内容
BOOT$ tree -L 2
.
├── kernel
├── ramdisk
└── RAMDISK ├── advanced_meta_init.rc ├── data ├── default.prop ├── dev ├── init ├── init.factory.rc ├── init.goldfish.rc ├── init.mt6516.rc ├── init.rc ├── meta_init.rc ├── proc ├── res - /system/res ├── sbin ├── sys └── system L1144-1146, 填充 RADIO子目录的内容 没有用到。
L1147-1149, 填充 SYSTEM子目录的内容。 这是升级的主要内容。
L1150-1152, 填充 DATA子目录的内容。缺省没有用到。
L1153-1156, 填充 OTA/bin子目录的内容,这是OTA升级自己使用的程序。后面会遇到。
OTA/bin$ tree
.
├── applypatch
├── applypatch_static
├── check_prereq
└── updater
L1159-1169, 填充 META子目录的内容,这里包含了OTA脚本需要的一些附加信息。
L1170-1171,将所有内容打包。供下一阶段使用。
L1173-1174,生成 META/filesystem_config.txt 并将其加入到 zip 包中。该文件保存了 system 目录下各目录、文件的权限及 owner.
$ head META/filesystem_config.txt
system 0 0 755
system/usr 0 0 755
system/usr/srec 0 0 755
system/usr/srec/config 0 0 755
system/usr/srec/config/en.us 0 0 755
system/usr/srec/config/en.us/grammars 0 0 755
system/usr/srec/config/en.us/grammars/phone_type_choice.g2g 0 0 644
system/usr/srec/config/en.us/grammars/VoiceDialer.g2g 0 0 644
system/usr/srec/config/en.us/grammars/boolean.g2g 0 0 644
system/usr/srec/config/en.us/g2p 0 0 755 这里目录由 zipinfo –l 提供 而权限则由 fs_config 设定。此程序的源码位于build/tools/fs_config, 其中fs_config 包含了一个头文件
54 #include private/android_filesystem_config.h
这个文件(system/core/include/private/android_filesystem_config.h)以hardcoding 的方式设定了 system 下各目录、文件的权限、属主。比如
152 { 00440, AID_ROOT, AID_SHELL, system/etc/init.goldfish.rc },
153 { 00550, AID_ROOT, AID_SHELL, system/etc/init.goldfish.sh },
154 { 00440, AID_ROOT, AID_SHELL, system/etc/init.trout.rc },
155 { 00550, AID_ROOT, AID_SHELL, system/etc/init.ril }, 如果需要升级其它内容比如 bootloader, 则可以在这里加入。 步骤二
编译脚本如下:
(From: build/core/Makefile) 1186 name : $(TARGET_PRODUCT) 1187 ifeq ($(TARGET_BUILD_TYPE),debug) 1188 name : $(name)_debug 1189 endif 1190 name : $(name)-ota-$(FILE_NAME_TAG) 1191 1192 INTERNAL_OTA_PACKAGE_TARGET : $(PRODUCT_OUT)/$(name).zip 1193 1194 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR : $(DEFAULT_KEY_CERT_PAIR) 1195 1196 ifeq ($(TARGET_OTA_SCRIPT_MODE),) 1197 # default to auto 1198 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode : auto 1199 else 1200 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode : $(TARGET_OTA_SCRIPT_MODE) 1201 endif 1202 1203 $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS) 1204 echo Package OTA: $ 1205 $(hide) ./build/tools/releasetools/ota_from_target_files / 1206 -m $(scriptmode) / 1207 -p $(HOST_OUT) / 1208 -k $(KEY_CERT_PAIR) / 1209 $(BUILT_TARGET_FILES_PACKAGE) $ 核心是一个python脚本: ota_from_target_files, 它以前一步骤生成的ZIP包作为输入生成可用于OTA升级的zip包。 具体内容我们后文继续分析。 Android OTA 升级之二脚本 ota_from_target_files
作者 宋立新
Emailzjujoeyahoo.com
前言 前面介绍了ota package 的编译过程其中最核心的部分就是一个 python 脚本ota_from_target_files. 现在我们分析这个脚本。
先看一下帮助
不带任何参数先看一下它的帮助 $ ./ota_from_target_files 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 build/target/product/security/testkey). -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. -m (--script_mode) mode Specify amend or edify scripts, or auto to pick automatically (this is the default). -p (--path) dir Prepend dir/bin to the list of places to search for binaries run by this script, and expect to find jars in dir/framework. -s (--device_specific) file Path to the python module containing device-specific releasetools code. -x (--extra) keyvalue Add a key/value pair to the extras dict, which device-specific extension code may look at. -v (--verbose) Show command lines being executed. -h (--help) Display this usage message and exit. 简单翻译一下
-b 过时不再使用。
-k 签名用的密钥
-i 生成增量OTA包时用于定义对比包
-w 是否清除 userdata 分区
-n 是否在升级时不检查时间戳缺省情况下只能基于老的版本升级。
-e 定义额外运行的脚本
-m 定义采用的脚本格式目前有两种amend edify, 其中amend为较老的格式。对应的升级时会采用不同的解释器。缺省情况下ota_from_target_files 会同时生成两个脚本。这提供了最大灵活性。
-p 定义脚本用到的一些可执行文件的路径
-s 定义额外运行的脚本的路径
-x 定义额外运行的脚本可能用到的键/值对
-v 老朋友冗余模式让脚本打印出执行的命令
-h 老朋友这个就不用说了吧。
我们调用如下命令生成我们的升级包 ./build/tools/releasetools/ota_from_target_files / -m auto / -p out/host/linux-x86 / -k build/target/product/security/testkey -n /
out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}
再看内容
ota_from_target_files为python 脚本所以如果懂 python 会更顺利一点。
文件有1000行。分析过程中我们只是贴代码片段。 完整文件见
build/tools/releasetools/ota_from_target_files from Android 2.2 入口main
按照python惯例单独执行的代码执行从__main__开始
944 if __name__ __main__:
945 try:
946 main(sys.argv[1:])
947 except common.ExternalError, e:
948 print
949 print ERROR: %s % (e,)
950 print
951 sys.exit(1) 它调用 main 函数 844 def main(argv): 845 846 def option_handler(o, a): 847 if o in (-b, --board_config): 848 pass # deprecated 849 elif o in (-k, --package_key): 850 OPTIONS.package_key a 851 elif o in (-i, --incremental_from): 852 OPTIONS.incremental_source a 853 elif o in (-w, --wipe_user_data): 854 OPTIONS.wipe_user_data True 855 elif o in (-n, --no_prereq): 856 OPTIONS.omit_prereq True 857 elif o in (-e, --extra_script): 858 OPTIONS.extra_script a 859 elif o in (-m, --script_mode): 860 OPTIONS.script_mode a 861 elif o in (--worker_threads): 862 OPTIONS.worker_threads int(a) 863 else: 864 return False 865 return True 866 867 args common.ParseOptions(argv, __doc__, 868 extra_optsb:k:i:d:wne:m:, 869 extra_long_opts[board_config, 870 package_key, 871 incremental_from, 872 wipe_user_data, 873 no_prereq, 874 extra_script, 875 script_mode, 876 worker_threads], 877 extra_option_handleroption_handler) 878 879 if len(args) ! 2: 880 common.Usage(__doc__) 881 sys.exit(1) 将用户设定的 Option 存入 OPTIONS 变量中。它是一个Python Class, 我们将其理解为一个C Struct 即可。 883 if OPTIONS.script_mode not in (amend, edify, auto):
884 raise ValueError(unknown script mode %s % (OPTIONS.script_mode,)) Script_mode 只能是amend/edify/auto之一 auto 目前是选择两者都支持。
可以理解是为了向前兼容,(早期 Android 使用 amend) 886 if OPTIONS.extra_script is not None:
887 OPTIONS.extra_script open(OPTIONS.extra_script).read() 读入 额外脚本的内容。如果有 889 print unzipping target target-files...
890 OPTIONS.input_tmp common.UnzipTemp(args[0]) 解开输入包。 892 if OPTIONS.device_specific is None: 893 # look for the device-specific tools extension location in the input 894 try: 895 f open(os.path.join(OPTIONS.input_tmp, META, tool-extensions.txt)) 896 ds f.read().strip() 897 f.close() 898 if ds: 899 ds os.path.normpath(ds) 900 print using device-specific extensions in, ds 901 OPTIONS.device_specific ds 902 except IOError, e: 903 if e.errno errno.ENOENT: 904 # nothing specified in the file 905 pass 906 else: 907 raise
处理 device-specific extensions 没用到。 909 common.LoadMaxSizes()
910 if not OPTIONS.max_image_size:
911 print
912 print WARNING: Failed to load max image sizes; will not enforce
913 print image size limits.
914 print 读入设定image大小的参数没用到。 916 OPTIONS.target_tmp OPTIONS.input_tmp
917 input_zip zipfile.ZipFile(args[0], r)
918 if OPTIONS.package_key:
919 temp_zip_file tempfile.NamedTemporaryFile()
920 output_zip zipfile.ZipFile(temp_zip_file, w,
921 compressionzipfile.ZIP_DEFLATED)
922 else:
923 output_zip zipfile.ZipFile(args[1], w,
924 compressionzipfile.ZIP_DEFLATED) 设定输出文件如果要签名our case,则还需要一个临时输出文件。 926 if OPTIONS.incremental_source is None:
927 WriteFullOTAPackage(input_zip, output_zip)
928 else:
929 print unzipping source target-files...
930 OPTIONS.source_tmp common.UnzipTemp(OPTIONS.incremental_source)
931 source_zip zipfile.ZipFile(OPTIONS.incremental_source, r)
932 WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 根据参数调用增量和非增量创建 ZIP 创建函数我们采用非增量模式。 934 output_zip.close()
935 if OPTIONS.package_key:
936 SignOutput(temp_zip_file.name, args[1])
937 temp_zip_file.close()
939 common.Cleanup()
941 print done. 签名如果需要的话处理完毕。 下面我们看主要功能函数WriteFullOTAPackage。
主功能WriteFullOTAPackage 345 def WriteFullOTAPackage(input_zip, output_zip):
346 if OPTIONS.script_mode auto:
347 script both_generator.BothGenerator(2)
348 elif OPTIONS.script_mode amend:
349 script amend_generator.AmendGenerator()
350 else:
351 # TODO: how to determine this? We dont know what version it will
352 # be installed on top of. For now, we expect the API just wont
353 # change very often.
354 script edify_generator.EdifyGenerator(2) 首先我们获得脚本生成器他们的实现见脚本edify_generator.py 等。 356 metadata {post-build: GetBuildProp(ro.build.fingerprint, input_zip),
357 pre-device: GetBuildProp(ro.product.device, input_zip),
358 post-timestamp: GetBuildProp(ro.build.date.utc, input_zip),
359 } 获得一些环境变量来自android 环境变量。 Google 一下即知其义。 361 device_specific common.DeviceSpecificParams(
362 input_zipinput_zip,
363 input_versionGetRecoveryAPIVersion(input_zip),
364 output_zipoutput_zip,
365 scriptscript,
366 input_tmpOPTIONS.input_tmp,
367 metadatametadata) 设备相关参数不深究。 369 if not OPTIONS.omit_prereq:
370 ts GetBuildProp(ro.build.date.utc, input_zip)
371 script.AssertOlderBuild(ts) 如果需要在脚本中增加一个Assert语句要求update zip包只能用于升级老的系统。 373 AppendAssertions(script, input_zip) 如果需要在脚本中增加一个Assert语句要求update zip包只能用于同一设备即目标设备的 ro.product.device 必须跟update.zip中的相同。 374 device_specific.FullOTA_Assertions() Callback, 用于调用设备相关代码。调用时机为即将开始升级。类似还有
FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。 376 script.ShowProgress(0.5, 0) 在升级脚本中加入显示进度的语句 参数一表示底下的操作到下一条同类语句或者到末尾将暂用的时间在总体时间的比例。参数二用于控制显示的速度。比如50 则表示底下的操作估计50秒内完成要求进度条显示线程用50秒显示这一部分的进度。0 表示不自动更新手动控制使用SetProgress) 378 if OPTIONS.wipe_user_data:
379 script.FormatPartition(userdata) 如果需要在脚本中增加语句擦除 userdata 分区。 381 script.FormatPartition(system) 在脚本中增加语句擦除 system分区。 382 script.Mount(MTD, system, /system) 在脚本中增加语句安装 system分区到 /system 目录。
383 script.UnpackPackageDir(recovery, /system)
384 script.UnpackPackageDir(system, /system)
在脚本中增加语句将recovery以及system中的内容拷贝到 /system目录。其中recovery 目录包含一个patch 以及应用该patch 的脚本。 386 symlinks CopySystemFiles(input_zip, output_zip)
387 script.MakeSymlinks(symlinks) 386 行从输入 ZIP 包 /system 拷贝文件到输出 ZIP 包 /system。由于这个过程不支持链接文件所以它将这些文件返回。 于 387 行做继续处理。该行建立这些link 文件。所有的link文件都指向 toolbox 389 boot_img File(boot.img, common.BuildBootableImage(
390 os.path.join(OPTIONS.input_tmp, BOOT)))
391 recovery_img File(recovery.img, common.BuildBootableImage(
392 os.path.join(OPTIONS.input_tmp, RECOVERY)))
393 MakeRecoveryPatch(output_zip, recovery_img, boot_img) 这个复杂MakeRecoveryPatch 做了两件事:
1.在输出 ZIP包中生成一个patch: recovery/recovery-from-boot.p(boot.img和 recovery.img的patch), 它最后会位于system/recovery-from-boot.p
2.在输出 ZIP包中生成一个脚本recovery/etc/install-recovery.sh , 它最后会位于system/etc/install-recovery.sh.
该脚本的内容为
#!/system/bin/sh
if ! applypatch -c MTD:recovery:2048:6a167ffb86a4a16cb993473ce0726a3067163fc1; then log -t recovery Installing new recovery image applypatch MTD:boot:2324480:9a72a20a9c2f958ba586a840ed773cf8f5244183 MTD:recovery f6c2a70c5f2b02b6a49c9f5c5507a45a42e2d389 2564096 9a72a20a9c2f958ba586a840ed773cf8f5244183:/system/recovery-from-boot.p
else log -t recovery Recovery image already installed
fi 395 Item.GetMetadata(input_zip) 从 META/filesystem_config.txt 中获得 system 目录下的各文件权限信息。 396 Item.Get(system).SetPermissions(script) 在脚本中增加语句设置 system 目录下文件的权限及属主等。 398 common.CheckSize(boot_img.data, boot.img) 检查 boot.img 文件大小是否超标. 399 common.ZipWriteStr(output_zip, boot.img, boot_img.data) 将boot.img 放到输出 ZIP 包中。 400 script.ShowProgress(0.2, 0)
402 script.ShowProgress(0.2, 10) 更行进度条。 403 script.WriteRawImage(boot, boot.img) 在脚本中增加语句将 boot.img 写到 boot 分区。 405 script.ShowProgress(0.1, 0) 更行进度条。 406 device_specific.FullOTA_InstallEnd() Callback, 同前。 408 if OPTIONS.extra_script is not None:
409 script.AppendExtra(OPTIONS.extra_script) 如果有额外脚本加入。 411 script.UnmountAll() 在脚本中增加语句umount 所有分区。 412 script.AddToZip(input_zip, output_zip) 1将前面生成的脚本输出到META-INF/com/google/android/updater-script 对于edify assert(getprop(ro.product.device) thedevicename || getprop(ro.build.product) theproductname); show_progress(0.500000, 0); format(MTD, system); mount(MTD, system, /system); package_extract_dir(recovery, /system); package_extract_dir(system, /system); symlink(dumpstate, /system/bin/dumpcrash); symlink(toolbox, /system/bin/cat, /system/bin/chmod, /system/bin/chown, /system/bin/cmp, /system/bin/date, /system/bin/dd, /system/bin/df, /system/bin/dmesg, /system/bin/fb2bmp, /system/bin/getevent, /system/bin/getprop, /system/bin/hd, /system/bin/id, /system/bin/ifconfig, /system/bin/iftop, /system/bin/insmod, /system/bin/ioctl, /system/bin/kill, /system/bin/ln, /system/bin/log, /system/bin/ls, /system/bin/lsmod, /system/bin/mkdir, /system/bin/mount, /system/bin/mv, /system/bin/netstat, /system/bin/newfs_msdos, /system/bin/notify, /system/bin/printenv, /system/bin/ps, /system/bin/reboot, /system/bin/renice, /system/bin/rm, /system/bin/rmdir, /system/bin/rmmod, /system/bin/route, /system/bin/schedtop, /system/bin/sendevent, /system/bin/setconsole, /system/bin/setprop, /system/bin/sleep, /system/bin/smd, /system/bin/start, /system/bin/stop, /system/bin/sync, /system/bin/top, /system/bin/umount, /system/bin/vmstat, /system/bin/watchprops, /system/bin/wipe); set_perm_recursive(0, 0, 0755, 0644, /system); set_perm_recursive(0, 2000, 0755, 0755, /system/bin); set_perm(0, 3003, 02755, /system/bin/netcfg); set_perm(0, 3004, 02755, /system/bin/ping); set_perm_recursive(1002, 1002, 0755, 0440, /system/etc/bluez); set_perm(0, 0, 0755, /system/etc/bluez); set_perm(1002, 1002, 0440, /system/etc/dbus.conf); set_perm(1014, 2000, 0550, /system/etc/dhcpcd/dhcpcd-run-hooks); set_perm(0, 2000, 0550, /system/etc/init.goldfish.sh); set_perm(0, 0, 0544, /system/etc/install-recovery.sh); set_perm_recursive(0, 0, 0755, 0555, /system/etc/ppp); set_perm_recursive(0, 2000, 0755, 0755, /system/xbin); show_progress(0.200000, 0); show_progress(0.200000, 10); assert(package_extract_file(boot.img, /tmp/boot.img), write_raw_image(/tmp/boot.img, boot), delete(/tmp/boot.img)); show_progress(0.100000, 0); unmount(/system);
2将升级程序OTA/bin/updater 从输入ZIP包中拷贝到输出ZIP包中的META-INF/com/google/android/update-binary 413 WriteMetadata(metadata, output_zip)
将前面获取的metadata 写入输出包的文件中 META-INF/com/android/metadata
至此我们就得到了一个update.zip包。可以开始升级了。
思考
1 虽然提供了更新recovery分区的机制但是没有看到触发该更新的语句。所以缺省的情况是不会更新recovery分区的。大概是为了安全的原因吧。 但是有时确实需要更新recovery 分区比如设备的硬件配置、分区表等发生改变这该如何操作呢 Android OTA 升级之三生成recovery.img 前言 得到了ota升级包后我们就可以用它来升级系统了。Android 手机开机后会先运行 bootloader。 Bootloader 会根据某些判定条件比如按某个特殊键决定是否进入 recovery 模式。Recovery 模式会装载 recovery 分区 该分区包含recovery.img。recovery.img 包含了标准内核和boot.img中的内核相同以及recovery 根文件系统。下面我们看一下它是如何生成的。 recovery.img生成过程
L630-L637 依赖关系
(From: build/core/Makefile) 630 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) /
631 $(INSTALLED_RAMDISK_TARGET) /
632 $(INSTALLED_BOOTIMAGE_TARGET) /
633 $(recovery_binary) /
634 $(recovery_initrc) $(recovery_kernel) /
635 $(INSTALLED_2NDBOOTLOADER_TARGET) /
636 $(recovery_build_prop) $(recovery_resource_deps) /
637 $(RECOVERY_INSTALL_OTA_KEYS) INSTALLED_RECOVERYIMAGE_TARGET 为我们的编译目标
584 INSTALLED_RECOVERYIMAGE_TARGET : $(PRODUCT_OUT)/recovery.img 它依赖很多其它目标
1MKBOOTFS, MINIGZIP, MKBOOTIMGPC端工具软件
From build/core/config.mk
265 MKBOOTFS : $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX)
266 MINIGZIP : $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX)
267 MKBOOTIMG : $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX) 2INSTALLED_RAMDISK_TARGET标准根文件系统 ramdisk.img
326 BUILT_RAMDISK_TARGET : $(PRODUCT_OUT)/ramdisk.img
328 # We just build this directly to the install location.
329 INSTALLED_RAMDISK_TARGET : $(BUILT_RAMDISK_TARGET) 3INSTALLED_BOOTIMAGE_TARGET 即boot.img,标准内核及标准根文件系统
362 INSTALLED_BOOTIMAGE_TARGET : $(PRODUCT_OUT)/boot.img 4. recovery_binary, Recovery可执行程序源码位于bootable/recovery
590 recovery_binary : $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery 5. recovery_initrcrecovery模式的init.rc, 位于 bootable/recovery/etc/init.rc
586 recovery_initrc : $(call include-path-for, recovery)/etc/init.rc 6. recovery_kernel, recovery 模式的kernel, 同标准内核
587 recovery_kernel : $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system 7.INSTALLED_2NDBOOTLOADER_TARGET我们不用。 8. recovery_build_prop recovery 模式的build.prop, 同标准模式。
589 recovery_build_prop : $(INSTALLED_BUILD_PROP_TARGET) 9. recovery_resource_deps recovery 模式使用的res, 位于recovery/custom/{product_name}/res, 以及设备自定义部分我们没用到
591 recovery_resources_common : $(call include-path-for, recovery)/custom/$(TARGET_PRODUCT)/res
592 recovery_resources_private : $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res))
593 recovery_resource_deps : $(shell find $(recovery_resources_common)
594 $(recovery_resources_private) -type f) 10. RECOVERY_INSTALL_OTA_KEYS, ota 密钥
618 # Generate a file containing the keys that will be read by the
619 # recovery binary.
620 RECOVERY_INSTALL_OTA_KEYS : /
621 $(call intermediates-dir-for,PACKAGING,ota_keys)/keys
L638-L655 准备内容
638 echo ----- Making recovery image ------
639 rm -rf $(TARGET_RECOVERY_OUT)
640 mkdir -p $(TARGET_RECOVERY_OUT)
641 mkdir -p $(TARGET_RECOVERY_ROOT_OUT)
642 mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc
643 mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp 准备recovery目录out/target/product/{product_name}/recovery 及其子目录
./root
./root/etc
./root/tmp 644 echo Copying baseline ramdisk...
645 cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT)
646 echo Modifying ramdisk contents...
647 rm -rf $(TARGET_RECOVERY_ROOT_OUT)/res 从标准根文件系统拷贝所有文件 删除其res 目录。 648 cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/
649 cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/ 拷贝recovery 模式的核心文件 init.rc 及 recovery 650 cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/
651 $(foreach item,$(recovery_resources_private), /
652 cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)
653 cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 拷贝资源文件及密钥文件。 654 cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) /
655 $(TARGET_RECOVERY_ROOT_OUT)/default.prop 生成属性文件 default.prop, 它包含了标准根文件系统的default.prop out/target/product/{product_name}/root/default.prop以及system分区的build.prop (out/target/product/{product_name}/system/build.prop) L656-L661 最终生成recovery.img
656 $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) $(recovery_ramdisk) 压缩recovery根文件系统 657 build/quacomm/mkimage $(PRODUCT_OUT)/ramdisk-recovery.img RECOVERY $(PRODUCT_OUT)/ramdisk_recovery.img 加一个标识头RECOVERY 658 mv $(PRODUCT_OUT)/ramdisk_recovery.img $(PRODUCT_OUT)/ramdisk-recovery.img
659 $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $
660 echo ----- Made recovery image -------- $
661 $(hide) $(call assert-max-image-size,$,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw) 和内核一起生成recovery.img 附Recovery 根文件系统目录结构 $ tree
.
├── advanced_meta_init.rc
├── data
├── default.prop
├── dev
├── etc
├── init
├── init.factory.rc
├── init.goldfish.rc
├── init.quacomm.rc
├── init.rc
├── meta_init.rc
├── proc
├── res
│ ├── images
│ │ ├── icon_error.png
│ │ ├── icon_installing.png
│ │ ├── indeterminate1.png
│ │ ├── indeterminate2.png
│ │ ├── indeterminate3.png
│ │ ├── indeterminate4.png
│ │ ├── indeterminate5.png
│ │ ├── indeterminate6.png
│ │ ├── progress_empty.png
│ │ └── progress_fill.png
│ └── keys
├── sbin
│ ├── adbd
│ ├── advanced_meta_init
│ ├── meta_init
│ ├── meta_tst
│ └── recovery
├── sys
├── system
└── tmp Android OTA 升级之四进入根文件系统
作者 宋立新
Emailzjujoeyahoo.com
前言 从bootloader 进入Recovery 模式后首先也是运行Linux内核该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。 下面我们就看看进入Recovery 根文件系统都干些啥。 init.rc 和正常启动一样内核进入文件系统会执行/init, init 的配置文件就是 /init.rc, 前面文章讲过这个文件来自bootable/recovery/etc/init.rc下面我们看看它的内容。 1 2 on init 3 export PATH /sbin 4 export ANDROID_ROOT /system 5 export ANDROID_DATA /data 6 export EXTERNAL_STORAGE /sdcard 7 8 symlink /system/etc /etc 9 10 mkdir /sdcard 11 mkdir /system 12 mkdir /data 13 mkdir /cache 14 mount /tmp /tmp tmpfs 15 16 on boot 17 18 ifup lo 19 hostname localhost 20 domainname localdomain 21 22 class_start default 23 24 25 service recovery /sbin/recovery 26 27 service adbd /sbin/adbd recovery 28 disabled 29 30 on property:persist.service.adb.enable1 31 start adbd 32 33 on property:persist.service.adb.enable0 34 stop adbd 可以看到它很非常简单
1 设置几个环境变量。备用。
2 建立 etc 链接。
3 造几个目录。备用。
4 Mount /tmp 目录为内存文件系统 tmpfs后面会用到。
5 Trival 设置,不必关心。
6 启动 recovery主程序。
7 如果是eng模式此时persist.service.adb.enable启动adb
当然init主程序还会装载属性配置文件 /default.prop, 它包含了很多系统属性设置比如ro.build.* 等等。 很明显这里最重要的就是recovery主程序下面我们分析它。
先看一段注释
Recovery.c 中作者写了一段注释对我们理解recovery的实现很有帮助下面看一下我就不翻译了
89 /* 90 * The recovery tool communicates with the main system through /cache files. 91 * /cache/recovery/command - INPUT - command line for tool, one arg per line 92 * /cache/recovery/log - OUTPUT - combined log file from recovery run(s) 93 * /cache/recovery/intent - OUTPUT - intent that was passed in 94 * 95 * The arguments which may be supplied in the recovery.command file: 96 * --send_intentanystring - write the text out to recovery.intent 97 * --update_packageroot:path - verify install an OTA package file 98 * --wipe_data - erase user data (and cache), then reboot 99 * --wipe_cache - wipe cache (but not user data), then reboot
100 * --set_encrypted_filesystemon|off - enables / diasables encrypted fs
101 *
102 * After completing, we remove /cache/recovery/command and reboot.
103 * Arguments may also be supplied in the bootloader control block (BCB).
104 * These important scenarios must be safely restartable at any point:
105 *
106 * FACTORY RESET
107 * 1. user selects factory reset
108 * 2. main system writes --wipe_data to /cache/recovery/command
109 * 3. main system reboots into recovery
110 * 4. get_args() writes BCB with boot-recovery and --wipe_data
111 * -- after this, rebooting will restart the erase --
112 * 5. erase_root() reformats /data
113 * 6. erase_root() reformats /cache
114 * 7. finish_recovery() erases BCB
115 * -- after this, rebooting will restart the main system --
116 * 8. main() calls reboot() to boot main system
117 *
118 * OTA INSTALL
119 * 1. main system downloads OTA package to /cache/some-filename.zip
120 * 2. main system writes --update_packageCACHE:some-filename.zip
121 * 3. main system reboots into recovery
122 * 4. get_args() writes BCB with boot-recovery and --update_package...
123 * -- after this, rebooting will attempt to reinstall the update --
124 * 5. install_package() attempts to install the update
125 * NOTE: the package install must itself be restartable from any point
126 * 6. finish_recovery() erases BCB
127 * -- after this, rebooting will (try to) restart the main system --
128 * 7. ** if install failed **
129 * 7a. prompt_and_wait() shows an error icon and waits for the user
130 * 7b; the user reboots (pulling the battery, etc) into the main system
131 * 8. main() calls maybe_install_firmware_update()
132 * ** if the update contained radio/hboot firmware **:
133 * 8a. m_i_f_u() writes BCB with boot-recovery and --wipe_cache
134 * -- after this, rebooting will reformat cache restart main system --
135 * 8b. m_i_f_u() writes firmware image into raw cache partition
136 * 8c. m_i_f_u() writes BCB with update-radio/hboot and --wipe_cache
137 * -- after this, rebooting will attempt to reinstall firmware --
138 * 8d. bootloader tries to flash firmware
139 * 8e. bootloader writes BCB with boot-recovery (keeping --wipe_cache)
140 * -- after this, rebooting will reformat cache restart main system --
141 * 8f. erase_root() reformats /cache
142 * 8g. finish_recovery() erases BCB
143 * -- after this, rebooting will (try to) restart the main system --
144 * 9. main() calls reboot() to boot main system
145 *
146 * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE
147 * 1. user selects enable encrypted file systems
148 * 2. main system writes --set_encrypted_filesystemon|off to
149 * /cache/recovery/command
150 * 3. main system reboots into recovery
151 * 4. get_args() writes BCB with boot-recovery and
152 * --set_encrypted_filesystemson|off
153 * -- after this, rebooting will restart the transition --
154 * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data
155 * Settings include: property to specify the Encrypted FS istatus and
156 * FS encryption key if enabled (not yet implemented)
157 * 6. erase_root() reformats /data
158 * 7. erase_root() reformats /cache
159 * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data
160 * Settings include: property to specify the Encrypted FS status and
161 * FS encryption key if enabled (not yet implemented)
162 * 9. finish_recovery() erases BCB
163 * -- after this, rebooting will restart the main system --
164 * 10. main() calls reboot() to boot main system
165 */ recovery 主程序
559 int
560 main(int argc, char **argv)
561 {
562 time_t start time(NULL);
563
564 // If these fail, theres not really anywhere to complain...
565 freopen(TEMPORARY_LOG_FILE, a, stdout); setbuf(stdout, NULL);
566 freopen(TEMPORARY_LOG_FILE, a, stderr); setbuf(stderr, NULL);
567 fprintf(stderr, Starting recovery on %s, ctime(start));
568 将标准输出和标准错误输出重定位到/tmp/recovery.log,如果是eng模式就可以通过adb pull /tmp/recovery.log, 看到当前的log信息这为我们提供了有效的调试手段。后面还会看到recovery模式运行完毕后会将其拷贝到cache分区以便后续分析。 569 ui_init(); Recovery 使用了一个简单的基于framebuffer的ui系统叫miniui,这里进行了简单的初始化主要是图形部分以及事件部分并启动了一个 event 线程用于响应用户按键。 570 get_args(argc, argv);
从misc 分区以及 CACHE:recovery/command 文件中读入参数写入到argc, argv ,并且如果有必要回写入misc分区。这样如果recovery没有操作成功比如升级还没有结束就拔电池系统会一直进入recovery模式。提醒用户继续升级直到成功。 572 int previous_runs 0;
573 const char *send_intent NULL;
574 const char *update_package NULL;
575 int wipe_data 0, wipe_cache 0;
576
577 int arg;
578 while ((arg getopt_long(argc, argv, , OPTIONS, NULL)) ! -1) {
579 switch (arg) {
580 case p: previous_runs atoi(optarg); break;
581 case s: send_intent optarg; break;
582 case u: update_package optarg; break;
583 case w: wipe_data wipe_cache 1; break;
584 case c: wipe_cache 1; break;
585 case ?:
586 LOGE(Invalid command argument/n);
587 continue;
588 }
589 }
590
解析参数p: previous_runs没有用到其它含义见前面注释。 591 device_recovery_start(); 这个函数没干什么。看名字它給设备制造商提供了一个调用机会可写入设备相关初始化代码。
592
593 fprintf(stderr, Command:);
594 for (arg 0; arg argc; arg) {
595 fprintf(stderr, /%s/, argv[arg]);
596 }
597 fprintf(stderr, /n/n);
598
打印出命令比如正常启动进入recovery模式会打印Command: /sbin/recovery
599 property_list(print_property, NULL);
600 fprintf(stderr, /n);
601
打印出所有的系统属性from default.prop到log文件。 602 int status INSTALL_SUCCESS;
603
604 if (update_package ! NULL) {
605 status install_package(update_package);
606 if (status ! INSTALL_SUCCESS) ui_print(Installation aborted./n);
607 } else if (wipe_data) {
608 if (device_wipe_data()) status INSTALL_ERROR;
609 if (erase_root(DATA:)) status INSTALL_ERROR;
610 if (wipe_cache erase_root(CACHE:)) status INSTALL_ERROR;
611 if (status ! INSTALL_SUCCESS) ui_print(Data wipe failed./n);
612 } else if (wipe_cache) {
613 if (wipe_cache erase_root(CACHE:)) status INSTALL_ERROR;
614 if (status ! INSTALL_SUCCESS) ui_print(Cache wipe failed./n);
615 } else {
616 status INSTALL_ERROR; // No command specified
617 } 根据用户提供参数调用各项功能比如安装一个升级包擦除cache分区, 擦除user data分区install_package比较复杂后面专门分析其它都很简单。忽略。 618
619 if (status ! INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);
622 if (status ! INSTALL_SUCCESS) prompt_and_wait(); 如果前面已经做了某项操作并且成功则进入重启流程。否则等待用户选择具体操作。
而用户可选操作为 reboot, 安装update.zip除cache分区, 擦除user data分区,如前所述只有安装package 比较复杂其它简单。 623
624 // Otherwise, get ready to boot the main system...
625 finish_recovery(send_intent); 它的功能如下
1将前面定义的intent字符串写入如果有的话CACHE:recovery/command
2将 /tmp/recovery.log 复制到 CACHE:recovery/log;
3清空 misc 分区这样重启就不会进入recovery模式
4)删除command 文件CACHE:recovery/command; 626 ui_print(Rebooting.../n);
627 sync();
628 reboot(RB_AUTOBOOT);
629 return EXIT_SUCCESS;
630 } 重启。
下面我们分析核心函数 install_package install_package
289 int
290 install_package(const char *root_path)
291 {
292 ui_set_background(BACKGROUND_ICON_INSTALLING);
294 ui_print(Finding update package.../n);
295 LOGI(Finding update package.../n);
296 ui_show_indeterminate_progress();
297 LOGI(Update location: %s/n, root_path);
298
更新 UI 显示
299 if (ensure_root_path_mounted(root_path) ! 0) {
300 LOGE(Cant mount %s/n, root_path);
301 reset_mark_block();
302 return INSTALL_CORRUPT;
303 }
304 确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区 305 char path[PATH_MAX] ;
306 if (translate_root_path(root_path, path, sizeof(path)) NULL) {
307 LOGE(Bad path %s/n, root_path);
308 reset_mark_block();
309 return INSTALL_CORRUPT;
310 } 将根分区转化为具体分区信息.这些信息来自全局数组g_roots 313 ui_print(Opening update package.../n);
314 LOGI(Opening update package.../n);
315 LOGI(Update file path: %s/n, path);
316
317 int numKeys;
318 RSAPublicKey* loadedKeys load_keys(PUBLIC_KEYS_FILE, numKeys);
319 if (loadedKeys NULL) {
320 LOGE(Failed to load keys/n);
321 reset_mark_block();
322 return INSTALL_CORRUPT;
323 }
324 LOGI(%d key(s) loaded from %s/n, numKeys, PUBLIC_KEYS_FILE); 从/res/keys中装载公钥。 326 // Give verification half the progress bar...
328 ui_print(Verifying update package.../n);
329 LOGI(Verifying update package.../n);
330 ui_show_progress(
331 VERIFICATION_PROGRESS_FRACTION,
332 VERIFICATION_PROGRESS_TIME);
333
334 int err;
335 err verify_file(path, loadedKeys, numKeys);
336 free(loadedKeys);
337 LOGI(verify_file returned %d/n, err);
338 if (err ! VERIFY_SUCCESS) {
339 LOGE(signature verification failed/n);
340 reset_mark_block();
341 return INSTALL_CORRUPT;
342 } 根据公钥验证升级包verify_file的注释说的很明白 // Look for an RSA signature embedded in the .ZIP file comment given // the path to the zip. Verify it matches one of the given public // keys. 344 /* Try to open the package.
345 */
346 ZipArchive zip;
347 err mzOpenZipArchive(path, zip);
348 if (err ! 0) {
349 LOGE(Cant open %s/n(%s)/n, path, err ! -1 ? strerror(err) : bad);
350 reset_mark_block();
351 return INSTALL_CORRUPT;
352 } 打开升级包将相关信息存到ZuoArchive数据机构中便于后面处理。 354 /* Verify and install the contents of the package.
355 */
356 int status handle_update_package(path, zip); 处理函数我们后面继续分析。 357 mzCloseZipArchive(zip);
358 return status;
359 } 关闭zip包结束处理。
handle_update_package
204 static int
205 handle_update_package(const char *path, ZipArchive *zip)
206 {
207 // Update should take the rest of the progress bar.
208 ui_print(Installing update.../n);
209
210 int result try_update_binary(path, zip);
211 register_package_root(NULL, NULL); // Unregister package root
212 return result;
213 } 它主要调用函数try_update_binary完成功能。
try_update_binary
84 // If the package contains an update binary, extract it and run it. 85 static int 86 try_update_binary(const char *path, ZipArchive *zip) { 87 const ZipEntry* binary_entry 88 mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME); 89 if (binary_entry NULL) { 90 return INSTALL_CORRUPT; 91 } 92 93 char* binary /tmp/update_binary; 94 unlink(binary); 95 int fd creat(binary, 0755); 96 if (fd 0) { 97 LOGE(Cant make %s/n, binary); 98 return 1; 99 }
100 bool ok mzExtractZipEntryToFile(zip, binary_entry, fd);
101 close(fd);
102
103 if (!ok) {
104 LOGE(Cant copy %s/n, ASSUMED_UPDATE_BINARY_NAME);
105 return 1;
106 } 将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary 108 int pipefd[2];
109 pipe(pipefd);
110
111 // When executing the update binary contained in the package, the
112 // arguments passed are:
113 //
114 // - the version number for this interface
115 //
116 // - an fd to which the program can write in order to update the
117 // progress bar. The program can write single-line commands:
118 //
119 // progress frac secs
120 // fill up the next frac part of of the progress bar
121 // over secs seconds. If secs is zero, use
122 // set_progress commands to manually control the
123 // progress of this segment of the bar
124 //
125 // set_progress frac
126 // frac should be between 0.0 and 1.0; sets the
127 // progress bar within the segment defined by the most
128 // recent progress command.
129 //
130 // firmware hboot|radio filename
131 // arrange to install the contents of filename in the
132 // given partition on reboot.
133 //
134 // (API v2: filename may start with PACKAGE: to
135 // indicate taking a file from the OTA package.)
136 //
137 // (API v3: this command no longer exists.)
138 //
139 // ui_print string
140 // display string on the screen.
141 //
142 // - the name of the package zip file.
143 //
144 注意看这段注释它解释了以下代码的行为。结合代码可知
1 将会创建新的进程执行/tmp/update_binary
2 同时会给该进程传入一些参数其中最重要的就是一个管道fd供新进程与原进程通信。
3 新进程诞生后原进程就变成了一个服务进程它提供若干UI更新服务
a) progress
b) set_progress
c) ui_print
这样新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。 145 char** args malloc(sizeof(char*) * 5);
146 args[0] binary;
147 args[1] EXPAND(RECOVERY_API_VERSION); // defined in Android.mk
148 args[2] malloc(10);
149 sprintf(args[2], %d, pipefd[1]);
150 args[3] (char*)path;
151 args[4] NULL;
152
153 pid_t pid fork();
154 if (pid 0) {
155 close(pipefd[0]);
156 execv(binary, args);
157 fprintf(stderr, E:Cant run %s (%s)/n, binary, strerror(errno));
158 _exit(-1);
159 }
160 close(pipefd[1]);
161
162 char buffer[1024];
163 FILE* from_child fdopen(pipefd[0], r);
164 while (fgets(buffer, sizeof(buffer), from_child) ! NULL) {
165 char* command strtok(buffer, /n);
166 if (command NULL) {
167 continue;
168 } else if (strcmp(command, progress) 0) {
169 char* fraction_s strtok(NULL, /n);
170 char* seconds_s strtok(NULL, /n);
171
172 float fraction strtof(fraction_s, NULL);
173 int seconds strtol(seconds_s, NULL, 10);
174
175 ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION),
176 seconds);
177 } else if (strcmp(command, set_progress) 0) {
178 char* fraction_s strtok(NULL, /n);
179 float fraction strtof(fraction_s, NULL);
180 ui_set_progress(fraction);
181 } else if (strcmp(command, ui_print) 0) {
182 char* str strtok(NULL, /n);
183 if (str) {
184 ui_print(str);
185 } else {
186 ui_print(/n);
187 }
188 } else {
189 LOGE(unknown command [%s]/n, command);
190 }
191 }
192 fclose(from_child);
193
194 int status;
195 waitpid(pid, status, 0);
196 if (!WIFEXITED(status) || WEXITSTATUS(status) ! 0) {
197 LOGE(Error in %s/n(Status %d)/n, path, WEXITSTATUS(status));
198 return INSTALL_ERROR;
199 }
200
201 return INSTALL_SUCCESS;
202 } 这样我们又回到了升级包中的文件META-INF/com/google/android/update-binary下回继续研究。 Android OTA 升级之五updater
作者 宋立新
Emailzjujoeyahoo.com
前言 可以说前面分析的OTA升级的各部分代码都是在搭一个舞台而主角现在终于登场它就是updater. Google的代码架构设计非常好各部分尽量松耦合。前面介绍升级脚本时可知有两种类型的脚本amend edify. 他们各自对应一个updater. 这里我们主要关注新的edify的updater. Updater可以作为学习解释器/编译器的同学一个很好的实例但是我们只关心产品化相关的内容所以并不去深究lex/yacc相关的东西。 入口函数 main
(from: bootable/recovery/updater/updater.c)
62 // Where in the package we expect to find the edify script to execute. 63 // (Note its updateR-script, not the older update-script.) 64 #define SCRIPT_NAME META-INF/com/google/android/updater-script 65 这里定义脚本的位置注释说明本updater支持edify格式的脚本。 66 int main(int argc, char** argv) { 67 // Various things log information to stdout or stderr more or less 68 // at random. The log file makes more sense if buffering is 69 // turned off so things appear in the right order. 70 setbuf(stdout, NULL); 71 setbuf(stderr, NULL); 72 73 if (argc ! 4) { 74 fprintf(stderr, unexpected number of arguments (%d)/n, argc); 75 return 1; 76 } 77 78 char* version argv[1]; 79 if ((version[0] ! 1 version[0] ! 2 version[0] ! 3) || 80 version[1] ! /0) { 81 // We support version 1, 2, or 3. 82 fprintf(stderr, wrong updater binary API; expected 1, 2, or 3; 83 got %s/n, 84 argv[1]); 85 return 2; 86 } 87
获取 version 参数。 88 // Set up the pipe for sending commands back to the parent process. 89 90 int fd atoi(argv[2]); 91 FILE* cmd_pipe fdopen(fd, wb); 92 setlinebuf(cmd_pipe); 93 获取命令管道用于图形显示等见前篇 94 // Extract the script from the package. 95 96 char* package_data argv[3]; 97 ZipArchive za; 98 int err; 99 err mzOpenZipArchive(package_data, za);
100 if (err ! 0) {
101 fprintf(stderr, failed to open package %s: %s/n,
102 package_data, strerror(err));
103 return 3;
104 }
105
106 const ZipEntry* script_entry mzFindZipEntry(za, SCRIPT_NAME);
107 if (script_entry NULL) {
108 fprintf(stderr, failed to find %s in %s/n, SCRIPT_NAME, package_data);
109 return 4;
110 }
111
112 char* script malloc(script_entry-uncompLen1);
113 if (!mzReadZipEntry(za, script_entry, script, script_entry-uncompLen)) {
114 fprintf(stderr, failed to read script from package/n);
115 return 5;
116 }
117 script[script_entry-uncompLen] /0;
118 读入脚本 META-INF/com/google/android/updater-script 119 // Configure edifys functions.
120
121 RegisterBuiltins();
122 RegisterInstallFunctions();
123 RegisterDeviceExtensions();
124 FinishRegistration();
125
注册语句处理函数
126 // Parse the script.
127
128 Expr* root;
129 int error_count 0;
130 yy_scan_string(script);
131 int error yyparse(root, error_count);
132 if (error ! 0 || error_count 0) {
133 fprintf(stderr, %d parse errors/n, error_count);
134 return 6;
135 }
136
调用yy* 库函数解析脚本。
137 // Evaluate the parsed script.
138
139 UpdaterInfo updater_info;
140 updater_info.cmd_pipe cmd_pipe;
141 updater_info.package_zip za;
142 updater_info.version atoi(version);
143
144 State state;
145 state.cookie updater_info;
146 state.script script;
147 state.errmsg NULL;
148
149 char* result Evaluate(state, root);
150 if (result NULL) {
151 if (state.errmsg NULL) {
152 fprintf(stderr, script aborted (no error message)/n);
153 fprintf(cmd_pipe, ui_print script aborted (no error message)/n);
154 } else {
155 fprintf(stderr, script aborted: %s/n, state.errmsg);
156 char* line strtok(state.errmsg, /n);
157 while (line) {
158 fprintf(cmd_pipe, ui_print %s/n, line);
159 line strtok(NULL, /n);
160 }
161 fprintf(cmd_pipe, ui_print/n);
162 }
163 free(state.errmsg);
164 return 7;
165 } else {
166 fprintf(stderr, script result was [%s]/n, result);
167 free(result);
168 }
解释执行脚本。 核心函数是 Evaluate。它会调用其他callback函数而这些callback函数又会调用Evaluate去解析不同的脚本片段。从而实现一个简单的解释器。
169
170 mzCloseZipArchive(za);
171 free(script);
172
173 return 0;
174 } 还没开始就结束了。代码非常简单因为细节隐藏在那些callback函数里。我们看一下。
RegisterBuiltins
415 void RegisterBuiltins() {
416 RegisterFunction(ifelse, IfElseFn);
417 RegisterFunction(abort, AbortFn);
418 RegisterFunction(assert, AssertFn);
419 RegisterFunction(concat, ConcatFn);
420 RegisterFunction(is_substring, SubstringFn);
421 RegisterFunction(stdout, StdoutFn);
422 RegisterFunction(sleep, SleepFn);
423
424 RegisterFunction(less_than_int, LessThanIntFn);
425 RegisterFunction(greater_than_int, GreaterThanIntFn);
426 }
这些语句控制执行流程。
RegisterInstallFunctions
1036
1037 void RegisterInstallFunctions() {
1038 RegisterFunction(mount, MountFn);
1039 RegisterFunction(is_mounted, IsMountedFn);
1040 RegisterFunction(unmount, UnmountFn);
1041 RegisterFunction(format, FormatFn);
1042 RegisterFunction(show_progress, ShowProgressFn);
1043 RegisterFunction(set_progress, SetProgressFn);
1044 RegisterFunction(delete, DeleteFn);
1045 RegisterFunction(delete_recursive, DeleteFn);
1046 RegisterFunction(package_extract_dir, PackageExtractDirFn);
1047 RegisterFunction(package_extract_file, PackageExtractFileFn);
1048 RegisterFunction(symlink, SymlinkFn);
1049 RegisterFunction(set_perm, SetPermFn);
1050 RegisterFunction(set_perm_recursive, SetPermFn);
1051
1052 RegisterFunction(getprop, GetPropFn);
1053 RegisterFunction(file_getprop, FileGetPropFn);
1054 RegisterFunction(write_raw_image, WriteRawImageFn);
1055
1056 RegisterFunction(apply_patch, ApplyPatchFn);
1057 RegisterFunction(apply_patch_check, ApplyPatchCheckFn);
1058 RegisterFunction(apply_patch_space, ApplyPatchSpaceFn);
1059
1060 RegisterFunction(read_file, ReadFileFn);
1061 RegisterFunction(sha1_check, Sha1CheckFn);
1062
1063 RegisterFunction(ui_print, UIPrintFn);
1064
1065 RegisterFunction(run_program, RunProgramFn);
1066 }
这些语句执行各种功能。基本上我们只需要知道用法就可以了。值得注意的是run_program原语允许我们去执行自定义程序这应该足够满足我们的个性化需求了。