Android OTA 升级之一:编译升级包
作者: 宋立新
Email:[email protected]
前言
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)
[c-sharp] view plaincopyprint?
1. 1073 # Depending on the various images guarantees that the underlying
2. 1074 # directories are up-to-date.
3. 1075 $(BUILT_TARGET_FILES_PACKAGE): /
4. 1076 $(INSTALLED_BOOTIMAGE_TARGET) /
5. 1077 $(INSTALLED_RADIOIMAGE_TARGET) /
6. 1078 $(INSTALLED_RECOVERYIMAGE_TARGET) /
7. 1079 $(INSTALLED_FACTORYIMAGE_TARGET) /
8. 1080 $(INSTALLED_SYSTEMIMAGE) /
9. 1081 $(INSTALLED_USERDATAIMAGE_TARGET) /
10. 1082 $(INSTALLED_SECROIMAGE_TARGET) /
11. 1083 $(INSTALLED_ANDROID_INFO_TXT_TARGET) /
12. 1084 $(built_ota_tools) /
13. 1085 $(APKCERTS_FILE) /
14. 1086 $(HOST_OUT_EXECUTABLES)/fs_config /
15. 1087 | $(ACP)
16. 1088 @echo "Package target files: $@"
17. 1089 $(hide) rm -rf $@ $(zip_root)
18. 1090 $(hide) mkdir -p $(dir $@) $(zip_root)
19. 1091 @# Components of the recovery image
20. 1092 $(hide) mkdir -p $(zip_root)/RECOVERY
21. 1093 $(hide) $(call package_files-copy-root, /
22. 1094 $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)
23. 1095 ifdef INSTALLED_KERNEL_TARGET
24. 1096 $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel
25. 1097 $(hide) $(ACP) $(recovery_ramdisk) $(zip_root)/RECOVERY/ramdisk
26. 1098 endif
27. 1099 ifdef INSTALLED_2NDBOOTLOADER_TARGET
28. 1100 $(hide) $(ACP) /
29. 1101 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second
30. 1102 endif
31. 1103 ifdef BOARD_KERNEL_CMDLINE
32. 1104 $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/RECOVERY/cmdline
33. 1105 endif
34. 1106 ifdef BOARD_KERNEL_BASE
35. 1107 $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base
36. 1108 endif
37. 1109 @# Components of the factory image
38. 1110 $(hide) mkdir -p $(zip_root)/FACTORY
39. 1111 $(hide) $(call package_files-copy-root, /
40. 1112 $(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK)
41. 1113 ifdef INSTALLED_KERNEL_TARGET
42. 1114 $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/FACTORY/kernel
43. 1115 endif
44. 1116 ifdef INSTALLED_2NDBOOTLOADER_TARGET
45. 1117 $(hide) $(ACP) /
46. 1118 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/FACTORY/second
47. 1119 endif
48. 1120 ifdef BOARD_KERNEL_CMDLINE
49. 1121 $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/FACTORY/cmdline
50. 1122 endif
51. 1123 ifdef BOARD_KERNEL_BASE
52. 1124 $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/FACTORY/base
53. 1125 endif
54. 1126 @# Components of the boot image
55. 1127 $(hide) mkdir -p $(zip_root)/BOOT
56. 1128 $(hide) $(call package_files-copy-root, /
57. 1129 $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)
58. 1130 ifdef INSTALLED_KERNEL_TARGET
59. 1131 $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel
60. 1132 $(hide) $(ACP) $(INSTALLED_RAMDISK_TARGET) $(zip_root)/BOOT/ramdisk
61. 1133 endif
62. 1134 ifdef INSTALLED_2NDBOOTLOADER_TARGET
63. 1135 $(hide) $(ACP) /
64. 1136 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second
65. 1137 endif
66. 1138 ifdef BOARD_KERNEL_CMDLINE
67. 1139 $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline
68. 1140 endif
69. 1141 ifdef BOARD_KERNEL_BASE
70. 1142 $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base
71. 1143 endif
72. 1144 $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),/
73. 1145 mkdir -p $(zip_root)/RADIO; /
74. 1146 $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));)
75. 1147 @# Contents of the system image
76. 1148 $(hide) $(call package_files-copy-root, /
77. 1149 $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)
78. 1150 @# Contents of the data image
79. 1151 $(hide) $(call package_files-copy-root, /
80. 1152 $(TARGET_OUT_DATA),$(zip_root)/DATA)
81. 1153 @# Extra contents of the OTA package
82. 1154 $(hide) mkdir -p $(zip_root)/OTA/bin
83. 1155 $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/
84. 1156 $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/
85. 1157 @# Files that do not end up in any images, but are necessary to
86. 1158 @# build them.
87. 1159 $(hide) mkdir -p $(zip_root)/META
88. 1160 $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt
89. 1161 $(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt
90. 1162 $(hide) echo "$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/recovery-api-version.txt
91. 1163 $(hide) echo "blocksize $(BOARD_FLASH_BLOCK_SIZE)" > $(zip_root)/META/imagesizes.txt
92. 1164 $(hide) echo "boot $(call image-size-from-data-size,$(BOARD_BOOTIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt
93. 1165 $(hide) echo "recovery $(call image-size-from-data-size,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt
94. 1166 $(hide) echo "system $(call image-size-from-data-size,$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt
95. 1167 $(hide) echo "secro $(call image-size-from-data-size,$(BOARD_SECROIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt
96. 1168 $(hide) echo "userdata $(call image-size-from-data-size,$(BOARD_USERDATAIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt
97. 1169 $(hide) echo "$(tool_extensions)" > $(zip_root)/META/tool-extensions.txt
98. 1170 @# Zip everything up, preserving symlinks
99. 1171 $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)
100. 1172 @# Run fs_config on all the system files in the zip, and save the output
101. 1173 $(hide) zipinfo -1 $@ | awk -F/ 'BEGIN { OFS="/" } /^SYSTEM/// {$$1 = "system"; print}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/filesystem_config.txt
102. 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.
$ headMETA/filesystem_config.txt
system 0 0 755
system/usr 0 0 755
system/usr/srec 0 0755
system/usr/srec/config0 0 755
system/usr/srec/config/en.us0 0 755
system/usr/srec/config/en.us/grammars0 0 755
system/usr/srec/config/en.us/grammars/phone_type_choice.g2g0 0 644
system/usr/srec/config/en.us/grammars/VoiceDialer.g2g0 0 644
system/usr/srec/config/en.us/grammars/boolean.g2g0 0 644
system/usr/srec/config/en.us/g2p0 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)
[c-sharp] view plaincopyprint?
1. 1186 name := $(TARGET_PRODUCT)
2. 1187 ifeq ($(TARGET_BUILD_TYPE),debug)
3. 1188 name := $(name)_debug
4. 1189 endif
5. 1190 name := $(name)-ota-$(FILE_NAME_TAG)
6. 1191
7. 1192 INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip
8. 1193
9. 1194 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)
10. 1195
11. 1196 ifeq ($(TARGET_OTA_SCRIPT_MODE),)
12. 1197 # default to "auto"
13. 1198 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode := auto
14. 1199 else
15. 1200 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode := $(TARGET_OTA_SCRIPT_MODE)
16. 1201 endif
17. 1202
18. 1203 $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS)
19. 1204 @echo "Package OTA: $@"
20. 1205 $(hide) ./build/tools/releasetools/ota_from_target_files /
21. 1206 -m $(scriptmode) /
22. 1207 -p $(HOST_OUT) /
23. 1208 -k $(KEY_CERT_PAIR) /
24. 1209 $(BUILT_TARGET_FILES_PACKAGE) $@
核心是一个python脚本: ota_from_target_files, 它以前一步骤生成的ZIP包作为输入,生成可用于OTA升级的zip包。具体内容我们后文继续分析。
分类: Android2011-02-26 12:06 13604人阅读 评论(39) 收藏 举报
脚本androidoutputinputsystemcompression
目录(?)[+]
Android OTA 升级之二:脚本 ota_from_target_files
作者: 宋立新
Email:[email protected]
前面介绍了ota package 的编译过程,其中最核心的部分就是一个 python 脚本:ota_from_target_files. 现在我们分析这个脚本。
不带任何参数,先看一下它的帮助:
[c-sharp] view plaincopyprint?
1. $ ./ota_from_target_files
2.
3. Given a target-files zipfile, produces an OTA package that installs
4.
5. that build. An incremental OTA is produced if -i is given, otherwise
6.
7. a full OTA is produced.
8.
9.
10.
11. Usage: ota_from_target_files [flags] input_target_files output_ota_package
12.
13. -b (--board_config) <file>
14.
15. Deprecated.
16.
17. -k (--package_key) <key>
18.
19. Key to use to sign the package (default is
20.
21. "build/target/product/security/testkey").
22.
23. -i (--incremental_from) <file>
24.
25. Generate an incremental OTA using the given target-files zip as
26.
27. the starting build.
28.
29. -w (--wipe_user_data)
30.
31. Generate an OTA package that will wipe the user data partition
32.
33. when installed.
34.
35. -n (--no_prereq)
36.
37. Omit the timestamp prereq check normally included at the top of
38.
39. the build scripts (used for developer OTA packages which
40.
41. legitimately need to go back and forth).
42.
43. -e (--extra_script) <file>
44.
45. Insert the contents of file at the end of the update script.
46.
47. -m (--script_mode) <mode>
48.
49. Specify 'amend' or 'edify' scripts, or 'auto' to pick
50.
51. automatically (this is the default).
52.
53. -p (--path) <dir>
54.
55. Prepend <dir>/bin to the list of places to search for binaries
56.
57. run by this script, and expect to find jars in <dir>/framework.
58.
59. -s (--device_specific) <file>
60.
61. Path to the python module containing device-specific
62.
63. releasetools code.
64.
65. -x (--extra) <key=value>
66.
67. Add a key/value pair to the 'extras' dict, which device-specific
68.
69. extension code may look at.
70.
71. -v (--verbose)
72.
73. Show command lines being executed.
74.
75. -h (--help)
76.
77. 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 /
-kbuild/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)
按照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 函数:
[python] view plaincopyprint?
1. 844 def main(argv):
2. 845
3. 846 def option_handler(o, a):
4. 847 if o in ("-b", "--board_config"):
5. 848 pass # deprecated
6. 849 elif o in ("-k", "--package_key"):
7. 850 OPTIONS.package_key = a
8. 851 elif o in ("-i", "--incremental_from"):
9. 852 OPTIONS.incremental_source = a
10. 853 elif o in ("-w", "--wipe_user_data"):
11. 854 OPTIONS.wipe_user_data = True
12. 855 elif o in ("-n", "--no_prereq"):
13. 856 OPTIONS.omit_prereq = True
14. 857 elif o in ("-e", "--extra_script"):
15. 858 OPTIONS.extra_script = a
16. 859 elif o in ("-m", "--script_mode"):
17. 860 OPTIONS.script_mode = a
18. 861 elif o in ("--worker_threads"):
19. 862 OPTIONS.worker_threads = int(a)
20. 863 else:
21. 864 return False
22. 865 return True
23. 866
24. 867 args = common.ParseOptions(argv, __doc__,
25. 868 extra_opts="b:k:i:d:wne:m:",
26. 869 extra_long_opts=["board_config=",
27. 870 "package_key=",
28. 871 "incremental_from=",
29. 872 "wipe_user_data",
30. 873 "no_prereq",
31. 874 "extra_script=",
32. 875 "script_mode=",
33. 876 "worker_threads="],
34. 877 extra_option_handler=option_handler)
35. 878
36. 879 if len(args) != 2:
37. 880 common.Usage(__doc__)
38. 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])
解开输入包。
[python] view plaincopyprint?
1. 892 if OPTIONS.device_specific is None:
2. 893 # look for the device-specific tools extension location in the input
3. 894 try:
4. 895 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
5. 896 ds = f.read().strip()
6. 897 f.close()
7. 898 if ds:
8. 899 ds = os.path.normpath(ds)
9. 900 print "using device-specific extensions in", ds
10. 901 OPTIONS.device_specific = ds
11. 902 except IOError, e:
12. 903 if e.errno == errno.ENOENT:
13. 904 # nothing specified in the file
14. 905 pass
15. 906 else:
16. 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 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。
345 defWriteFullOTAPackage(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 will
352 # be installed on top of. For now, we expect the API just won't
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_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.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)
[python] view plaincopyprint?
1. assert(getprop("ro.product.device") == "thedevicename" ||
2.
3. getprop("ro.build.product") == "theproductname");
4.
5. show_progress(0.500000, 0);
6.
7. format("MTD", "system");
8.
9. mount("MTD", "system", "/system");
10.
11. package_extract_dir("recovery", "/system");
12.
13. package_extract_dir("system", "/system");
14.
15. symlink("dumpstate", "/system/bin/dumpcrash");
16.
17. symlink("toolbox", "/system/bin/cat", "/system/bin/chmod",
18.
19. "/system/bin/chown", "/system/bin/cmp", "/system/bin/date",
20.
21. "/system/bin/dd", "/system/bin/df", "/system/bin/dmesg",
22.
23. "/system/bin/fb2bmp", "/system/bin/getevent", "/system/bin/getprop",
24.
25. "/system/bin/hd", "/system/bin/id", "/system/bin/ifconfig",
26.
27. "/system/bin/iftop", "/system/bin/insmod", "/system/bin/ioctl",
28.
29. "/system/bin/kill", "/system/bin/ln", "/system/bin/log",
30.
31. "/system/bin/ls", "/system/bin/lsmod", "/system/bin/mkdir",
32.
33. "/system/bin/mount", "/system/bin/mv", "/system/bin/netstat",
34.
35. "/system/bin/newfs_msdos", "/system/bin/notify", "/system/bin/printenv",
36.
37. "/system/bin/ps", "/system/bin/reboot", "/system/bin/renice",
38.
39. "/system/bin/rm", "/system/bin/rmdir", "/system/bin/rmmod",
40.
41. "/system/bin/route", "/system/bin/schedtop", "/system/bin/sendevent",
42.
43. "/system/bin/setconsole", "/system/bin/setprop", "/system/bin/sleep",
44.
45. "/system/bin/smd", "/system/bin/start", "/system/bin/stop",
46.
47. "/system/bin/sync", "/system/bin/top", "/system/bin/umount",
48.
49. "/system/bin/vmstat", "/system/bin/watchprops",
50.
51. "/system/bin/wipe");
52.
53. set_perm_recursive(0, 0, 0755, 0644, "/system");
54.
55. set_perm_recursive(0, 2000, 0755, 0755, "/system/bin");
56.
57. set_perm(0, 3003, 02755, "/system/bin/netcfg");
58.
59. set_perm(0, 3004, 02755, "/system/bin/ping");
60.
61. set_perm_recursive(1002, 1002, 0755, 0440, "/system/etc/bluez");
62.
63. set_perm(0, 0, 0755, "/system/etc/bluez");
64.
65. set_perm(1002, 1002, 0440, "/system/etc/dbus.conf");
66.
67. set_perm(1014, 2000, 0550, "/system/etc/dhcpcd/dhcpcd-run-hooks");
68.
69. set_perm(0, 2000, 0550, "/system/etc/init.goldfish.sh");
70.
71. set_perm(0, 0, 0544, "/system/etc/install-recovery.sh");
72.
73. set_perm_recursive(0, 0, 0755, 0555, "/system/etc/ppp");
74.
75. set_perm_recursive(0, 2000, 0755, 0755, "/system/xbin");
76.
77. show_progress(0.200000, 0);
78.
79. show_progress(0.200000, 10);
80.
81. assert(package_extract_file("boot.img", "/tmp/boot.img"),
82.
83. write_raw_image("/tmp/boot.img", "boot"),
84.
85. delete("/tmp/boot.img"));
86.
87. show_progress(0.100000, 0);
88.
89. 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 升级之一:编译升级包
分类: Android2011-03-08 08:20 12000人阅读 评论(1) 收藏 举报
androidresourcesbuildwildcardsystemimage
目录(?)[+]
Android OTA 升级之三:生成recovery.img
作者: 宋立新
Email:[email protected]
得到了ota升级包后,我们就可以用它来升级系统了。Android 手机开机后,会先运行 bootloader。 Bootloader 会根据某些判定条件(比如按某个特殊键)决定是否进入 recovery 模式。Recovery 模式会装载 recovery 分区, 该分区包含recovery.img。recovery.img 包含了标准内核(和boot.img中的内核相同)以及recovery 根文件系统。下面我们看一下它是如何生成的。
(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.img
328 # 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)/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
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)
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
$ 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
分类: Android2011-03-13 10:08 7914人阅读 评论(8) 收藏 举报
androidcachepathcommandsystemnull
目录(?)[+]
Android OTA 升级之四:进入根文件系统
作者: 宋立新
Email:[email protected]
从bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。
下面,我们就看看进入Recovery 根文件系统都干些啥。
和正常启动一样,内核进入文件系统会执行/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.enable=1
31 start adbd
32
33 on property:persist.service.adb.enable=0
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_intent=anystring - write the text out to recovery.intent
97 * --update_package=root: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_filesystem=on|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_package=CACHE: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_filesystem=on|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_filesystems=on|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 */
559 int
560 main(int argc, char **argv)
561 {
562 time_t start = time(NULL);
563
564 // 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;
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
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("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);
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("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包,结束处理。
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完成功能。
84 // If the package contains an update binary,extract it and run it.
85 staticint
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 updatebinary 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 orderto 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 theprogress 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 themost
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,下回继续研究。
分类: Android2011-03-15 20:14 6596人阅读 评论(8) 收藏 举报
android脚本callbacknullcmdpatch
目录(?)[+]
Android OTA 升级之五:updater
作者: 宋立新
Email:[email protected]
可以说,前面分析的OTA升级的各部分代码都是在搭一个舞台,而主角现在终于登场,它就是updater.Google的代码架构设计非常好,各部分尽量松耦合。前面介绍升级脚本时,可知有两种类型的脚本,amend& edify. 他们各自对应一个updater. 这里,我们主要关注新的edify的updater.
Updater可以作为学习解释器/编译器的同学一个很好的实例,但是我们只关心产品化相关的内容,所以并不去深究lex/yacc相关的东西。
(from:bootable/recovery/updater/updater.c)
62 // Where in the package we expect to find theedify script to execute.
63 // (Note it's "updateR-script", notthe 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 loginformation to stdout or stderr more or less
68 // at random. The log file makes more sense if buffering is
69 // turned off so thingsappear in the right order.
70 setbuf(stdout, NULL);
71 setbuf(stderr, NULL);
72
73 if (argc != 4) {
74 fprintf(stderr, "unexpected number ofarguments (%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 binaryAPI; expected 1, 2, or 3; "
83 "got %s/n",
84 argv[1]);
85 return 2;
86 }
87
获取 version 参数。
88 // Set up the pipe forsending 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 fromthe 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 scriptfrom package/n");
115 return 5;
116 }
117 script[script_entry->uncompLen]= '/0';
118
读入脚本 META-INF/com/google/android/updater-script
119 // Configure edify'sfunctions.
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 parsedscript.
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 (noerror 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函数里。我们看一下。
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 }
这些语句控制执行流程。
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原语允许我们去执行自定义程序,这应该足够满足我们的个性化需求了。