您的位置:首页 > 移动开发 > Android开发

Android OTA 升级

2014-03-11 17:41 239 查看
原文:http://fanwei51880.blog.163.com/blog/static/32406740201172325219944/
Android OTA 升级之一:编译升级包

作者: 宋立新

Email:zjujoe@yahoo.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 └── tmpL1109-1125, 填充 FACTORY 子目录的内容,
没有用到,包括:kernel 的imageL1126-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└── updaterL1159-1169, 填充 META子目录的内容,这里包含了OTA脚本需要的一些附加信息。L1170-1171,将所有内容打包。供下一阶段使用。L1173-1174,生成 META/filesystem_config.txt 并将其加入到 zip 包中。该文件保存了 system 目录下各目录、文件的权限及 owner.$
head META/filesystem_config.txtsystem 0 0 755system/usr
0 0 755system/usr/srec 0 0 755system/usr/srec/config
0 0 755system/usr/srec/config/en.us 0 0 755system/usr/srec/config/en.us/grammars
0 0 755system/usr/srec/config/en.us/grammars/phone_type_choice.g2g
0 0 644system/usr/srec/config/en.us/grammars/VoiceDialer.g2g 0
0 644system/usr/srec/config/en.us/grammars/boolean.g2g 0 0 644system/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

作者: 宋立新

Email:zjujoe@yahoo.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) <key=value>

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 print949 print
" ERROR: %s" % (e,)950 print951 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_opts="b: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_handler=option_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 print912 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_tmp917 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 compression=zipfile.ZIP_DEFLATED)922 else:923 output_zip
= zipfile.ZipFile(args[1], "w",924 compression=zipfile.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。

二.脚本ota_from_target_files(第二部分)

2011-08-23 14:56:25| 分类: Android
OTA升级 | 标签: |字号大中小 订阅

主功能: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 don't know what version it will352 #
be installed on top of. For now, we expect the API just won't353 #
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_zip=input_zip,363 input_version=GetRecoveryAPIVersion(input_zip),364 output_zip=output_zip,365 script=script,366 input_tmp=OPTIONS.input_tmp,367 metadata=metadata) 设备相关参数,不深究。 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.p2.在输出 ZIP包中生成一个脚本:recovery/etc/install-recovery.sh
, 它最后会位于system/etc/install-recovery.sh.该脚本的内容为:#!/system/bin/shif
! 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.pelse 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

作者: 宋立新

Email:zjujoe@yahoo.com
前言

得到了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

它依赖很多其它目标:
1.MKBOOTFS, MINIGZIP, MKBOOTIMG,PC端工具软件:(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)

2.INSTALLED_RAMDISK_TARGET,标准根文件系统 ramdisk.img:
326 BUILT_RAMDISK_TARGET
:= $(PRODUCT_OUT)/ramdisk.img328 #
We just build this directly to the install location.329 INSTALLED_RAMDISK_TARGET
:= $(BUILT_RAMDISK_TARGET) 3.INSTALLED_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_initrc,recovery模式的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)/res592 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 the619 #
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)/etc643 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.img656 $(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.img659 $(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 升级之四:进入根文件系统

作者: 宋立新

Email:zjujoe@yahoo.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
/sdcard11 mkdir
/system12 mkdir
/data13 mkdir
/cache14 mount
/tmp /tmp tmpfs15 16 on
boot17 18 ifup
lo19 hostname
localhost20 domainname
localdomain21 22 class_start
default23 24 25 service
recovery /sbin/recovery26 27 service
adbd /sbin/adbd recovery28 disabled29 30 on
property:persist.service.adb.enable=131 start
adbd32 33 on
property:persist.service.adb.enable=034 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 line92 * /cache/recovery/log
- OUTPUT - combined log file from recovery run(s)93 * /cache/recovery/intent
- OUTPUT - intent that was passed in94 *95 *
The arguments which may be supplied in the recovery.command file:96 * --send_intent=anystring
- write the text out to recovery.intent97 * --update_package=root:path
- verify install an OTA package file98 * --wipe_data
- erase user data (and cache), then reboot99 * --wipe_cache
- wipe cache (but not user data), then reboot100 * --set_encrypted_filesystem=on|off
- enables / diasables encrypted fs101 *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
RESET107 *
1. user selects "factory reset"108 *
2. main system writes "--wipe_data" to /cache/recovery/command109 *
3. main system reboots into recovery110 *
4. get_args() writes BCB with "boot-recovery" and "--wipe_data"111 * --
after this, rebooting will restart the erase --112 *
5. erase_root() reformats /data113 *
6. erase_root() reformats /cache114 *
7. finish_recovery() erases BCB115 * --
after this, rebooting will restart the main system --116 *
8. main() calls reboot() to boot main system117 *118 * OTA
INSTALL119 *
1. main system downloads OTA package to /cache/some-filename.zip120 *
2. main system writes "--update_package=CACHE:some-filename.zip"121 *
3. main system reboots into recovery122 *
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 update125 * NOTE:
the package install must itself be restartable from any point126 *
6. finish_recovery() erases BCB127 * --
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 user130 * 7b;
the user reboots (pulling the battery, etc) into the main system131 *
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 partition136 * 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 firmware139 * 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 /cache142 * 8g.
finish_recovery() erases BCB143 * --
after this, rebooting will (try to) restart the main system --144 *
9. main() calls reboot() to boot main system145 *146 * ENCRYPTED
FILE SYSTEMS ENABLE/DISABLE147 *
1. user selects "enable encrypted file systems"148 *
2. main system writes "--set_encrypted_filesystem=on|off" to149 * /cache/recovery/command150 *
3. main system reboots into recovery151 *
4. get_args() writes BCB with "boot-recovery" and152 * "--set_encrypted_filesystems=on|off"153 * --
after this, rebooting will restart the transition --154 *
5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data155 * Settings
include: property to specify the Encrypted FS istatus and156 * FS
encryption key if enabled (not yet implemented)157 *
6. erase_root() reformats /data158 *
7. erase_root() reformats /cache159 *
8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data160 * Settings
include: property to specify the Encrypted FS status and161 * FS
encryption key if enabled (not yet implemented)162 *
9. finish_recovery() erases BCB163 * --
after this, rebooting will restart the main system --164 *
10. main() calls reboot() to boot main system165 */

recovery 主程序559 int560 main(int argc,
char **argv)561 {562 time_t start = time(NULL);563564 //
If these fail, there's 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;576577 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(); 这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。592593 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;603604 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 specified617 } 根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user
data分区,install_package比较复杂,后面专门分析,其它都很简单。忽略。 618619 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 比较复杂,其它简单。 623624 //
Otherwise, get ready to boot the main system...625 finish_recovery(send_intent); 它的功能如下:1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command2)将 /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_package289 int290 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("Can't
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);316317 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);333334 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("Can't
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_package204 static
int205 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");209210 int result = try_update_binary(path,
zip);211 register_package_root(NULL, NULL); //
Unregister package root212 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("Can't
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("Can't
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:Can't
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

作者: 宋立新

Email:zjujoe@yahoo.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 it's "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->uncompLen+1);

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 edify's 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函数里。我们看一下。
RegisterBuiltins415 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);423424 RegisterFunction("less_than_int", LessThanIntFn);425 RegisterFunction("greater_than_int", GreaterThanIntFn);426 }

这些语句控制执行流程。
RegisterInstallFunctions10361037 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);10511052 RegisterFunction("getprop", GetPropFn);1053 RegisterFunction("file_getprop", FileGetPropFn);1054 RegisterFunction("write_raw_image", WriteRawImageFn);10551056 RegisterFunction("apply_patch", ApplyPatchFn);1057 RegisterFunction("apply_patch_check", ApplyPatchCheckFn);1058 RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);10591060 RegisterFunction("read_file", ReadFileFn);1061 RegisterFunction("sha1_check", Sha1CheckFn);10621063 RegisterFunction("ui_print", UIPrintFn);10641065 RegisterFunction("run_program", RunProgramFn);1066 }

这些语句执行各种功能。基本上,我们只需要知道用法就可以了。值得注意的是,run_program原语允许我们去执行自定义程序,这应该足够满足我们的个性化需求了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: