Android OTA 升级

来源:互联网 发布:lol检测出游戏数据异常 编辑:程序博客网 时间:2024/05/16 07:28

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)

 

 

  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.
$ head META/filesystem_config.txt 
system 0 0 755
system/usr 0 0 755
system/usr/srec 0 0 755
system/usr/srec/config 0 0 755
system/usr/srec/config/en.us 0 0 755
system/usr/srec/config/en.us/grammars 0 0 755
system/usr/srec/config/en.us/grammars/phone_type_choice.g2g 0 0 644
system/usr/srec/config/en.us/grammars/VoiceDialer.g2g 0 0 644
system/usr/srec/config/en.us/grammars/boolean.g2g 0 0 644
system/usr/srec/config/en.us/g2p 0 0 755
 
这里,目录由 zipinfo –l 提供, 而权限则由 fs_config 设定。此程序的源码位于:build/tools/fs_config, 其中fs_config 包含了一个头文件:
54 #include "private/android_filesystem_config.h"
这个文件(system/core/include/private/android_filesystem_config.h)以hardcoding 的方式设定了 system 下各目录、文件的权限、属主。比如:
152     { 00440, AID_ROOT,      AID_SHELL,     "system/etc/init.goldfish.rc" },
153     { 00550, AID_ROOT,      AID_SHELL,     "system/etc/init.goldfish.sh" },
154     { 00440, AID_ROOT,      AID_SHELL,     "system/etc/init.trout.rc" },
155     { 00550, AID_ROOT,      AID_SHELL,     "system/etc/init.ril" },
 
如果需要升级其它内容,比如 bootloader, 则可以在这里加入。
 

步骤二

编译脚本如下:

(From: build/core/Makefile)

  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包。 具体内容我们后文继续分析。


Android OTA 升级之二:脚本 ota_from_target_files

作者: 宋立新

Email:zjujoe@yahoo.com

前言

       前面介绍了ota package 的编译过程,其中最核心的部分就是一个 python 脚本:ota_from_target_files. 现在我们分析这个脚本。

先看一下帮助

不带任何参数,先看一下它的帮助:

  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 /

  -k build/target/product/security/testkey -n /

out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}

再看内容

ota_from_target_files为python 脚本,所以如果懂 python, 会更顺利一点。

文件有1000行。分析过程中,我们只是贴代码片段。 完整文件见:

build/tools/releasetools/ota_from_target_files (from Android 2.2)

 

入口:main

按照python惯例,单独执行的代码执行从__main__开始:

944 if __name__ == '__main__':
945   try:
946     main(sys.argv[1:])
947   except common.ExternalError, e:
948     print
949     print "   ERROR: %s" % (e,)
950     print
951     sys.exit(1)

 它调用 main 函数:

 

  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])
 解开输入包。
  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。

主功能: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 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)
 
  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 升级之三:生成recovery.img

前言

       得到了ota升级包后,我们就可以用它来升级系统了。Android 手机开机后,会先运行 bootloader。 Bootloader 会根据某些判定条件(比如按某个特殊键)决定是否进入 recovery 模式。Recovery 模式会装载 recovery 分区, 该分区包含recovery.img。recovery.img 包含了标准内核(和boot.img中的内核相同)以及recovery 根文件系统。下面我们看一下它是如何生成的。

 

recovery.img生成过程

L630-L637 依赖关系

(From: build/core/Makefile)

 

630 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) /
631                 $(INSTALLED_RAMDISK_TARGET) /
632                 $(INSTALLED_BOOTIMAGE_TARGET) /
633                 $(recovery_binary) /
634                 $(recovery_initrc) $(recovery_kernel) /
635                 $(INSTALLED_2NDBOOTLOADER_TARGET) /
636                 $(recovery_build_prop) $(recovery_resource_deps) /
637                 $(RECOVERY_INSTALL_OTA_KEYS)

 

INSTALLED_RECOVERYIMAGE_TARGET 为我们的编译目标:

584 INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

 

它依赖很多其它目标:

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

L638-L655 准备内容

638         @echo ----- Making recovery image ------
639         rm -rf $(TARGET_RECOVERY_OUT)
640         mkdir -p $(TARGET_RECOVERY_OUT)
641         mkdir -p $(TARGET_RECOVERY_ROOT_OUT)
642         mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc
643         mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp

 

准备recovery目录:out/target/product/{product_name}/recovery 及其子目录:

./root

./root/etc

./root/tmp

 

644         echo Copying baseline ramdisk...
645         cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT)
646         echo Modifying ramdisk contents...
647         rm -rf $(TARGET_RECOVERY_ROOT_OUT)/res

 

从标准根文件系统拷贝所有文件, 删除其res 目录。

 
648         cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/
649         cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/
 
拷贝recovery 模式的核心文件 init.rc 及 recovery
 
650         cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/
651         $(foreach item,$(recovery_resources_private), /
652           cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)
653         cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys
 
拷贝资源文件及密钥文件。
 
 
654         cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) /
655                 > $(TARGET_RECOVERY_ROOT_OUT)/default.prop
 
生成属性文件 default.prop, 它包含了标准根文件系统的default.prop (out/target/product/{product_name}/root/default.prop)以及system分区的build.prop (out/target/product/{product_name}/system/build.prop)
 

L656-L661 最终生成recovery.img

656         $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk)
 
压缩recovery根文件系统
 
657         build/quacomm/mkimage $(PRODUCT_OUT)/ramdisk-recovery.img RECOVERY > $(PRODUCT_OUT)/ramdisk_recovery.img
 
加一个标识头(RECOVERY)
 
658         mv $(PRODUCT_OUT)/ramdisk_recovery.img $(PRODUCT_OUT)/ramdisk-recovery.img
659         $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $@
660         @echo ----- Made recovery image -------- $@
661         $(hide) $(call assert-max-image-size,$@,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw)

 

和内核一起,生成recovery.img

 

附:Recovery 根文件系统目录结构

 

$ tree

.

├── advanced_meta_init.rc

├── data

├── default.prop

├── dev

├── etc

├── init

├── init.factory.rc

├── init.goldfish.rc

├── init.quacomm.rc

├── init.rc

├── meta_init.rc

├── proc

├── res

│   ├── images

│   │   ├── icon_error.png

│   │   ├── icon_installing.png

│   │   ├── indeterminate1.png

│   │   ├── indeterminate2.png

│   │   ├── indeterminate3.png

│   │   ├── indeterminate4.png

│   │   ├── indeterminate5.png

│   │   ├── indeterminate6.png

│   │   ├── progress_empty.png

│   │   └── progress_fill.png

│   └── keys

├── sbin

│   ├── adbd

│   ├── advanced_meta_init

│   ├── meta_init

│   ├── meta_tst

│   └── recovery

├── sys

├── system

└── tmp


Android OTA 升级之四:进入根文件系统

作者: 宋立新

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 /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  */

 

recovery 主程序

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

 

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包,结束处理。

handle_update_package

204 static int
205 handle_update_package(const char *path, ZipArchive *zip)
206 {
207     // Update should take the rest of the progress bar.
208     ui_print("Installing update.../n");
209 
210     int result = try_update_binary(path, zip);
211     register_package_root(NULL, NULL);  // Unregister package root
212     return result;
213 }
 

它主要调用函数try_update_binary完成功能。

try_update_binary

84 // If the package contains an update binary, extract it and run it.

 85 static int

 86 try_update_binary(const char *path, ZipArchive *zip) {

 87     const ZipEntry* binary_entry =

 88             mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);

 89     if (binary_entry == NULL) {

 90         return INSTALL_CORRUPT;

 91     }

 92

 93     char* binary = "/tmp/update_binary";

 94     unlink(binary);

 95     int fd = creat(binary, 0755);

 96     if (fd < 0) {

 97         LOGE("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函数里。我们看一下。

RegisterBuiltins

415 void RegisterBuiltins() {
416     RegisterFunction("ifelse", IfElseFn);
417     RegisterFunction("abort", AbortFn);
418     RegisterFunction("assert", AssertFn);
419     RegisterFunction("concat", ConcatFn);
420     RegisterFunction("is_substring", SubstringFn);
421     RegisterFunction("stdout", StdoutFn);
422     RegisterFunction("sleep", SleepFn);
423 
424     RegisterFunction("less_than_int", LessThanIntFn);
425     RegisterFunction("greater_than_int", GreaterThanIntFn);
426 }

这些语句控制执行流程。

RegisterInstallFunctions

1036 
1037 void RegisterInstallFunctions() {
1038     RegisterFunction("mount", MountFn);
1039     RegisterFunction("is_mounted", IsMountedFn);
1040     RegisterFunction("unmount", UnmountFn);
1041     RegisterFunction("format", FormatFn);
1042     RegisterFunction("show_progress", ShowProgressFn);
1043     RegisterFunction("set_progress", SetProgressFn);
1044     RegisterFunction("delete", DeleteFn);
1045     RegisterFunction("delete_recursive", DeleteFn);
1046     RegisterFunction("package_extract_dir", PackageExtractDirFn);
1047     RegisterFunction("package_extract_file", PackageExtractFileFn);
1048     RegisterFunction("symlink", SymlinkFn);
1049     RegisterFunction("set_perm", SetPermFn);
1050     RegisterFunction("set_perm_recursive", SetPermFn);
1051 
1052     RegisterFunction("getprop", GetPropFn);
1053     RegisterFunction("file_getprop", FileGetPropFn);
1054     RegisterFunction("write_raw_image", WriteRawImageFn);
1055 
1056     RegisterFunction("apply_patch", ApplyPatchFn);
1057     RegisterFunction("apply_patch_check", ApplyPatchCheckFn);
1058     RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);
1059 
1060     RegisterFunction("read_file", ReadFileFn);
1061     RegisterFunction("sha1_check", Sha1CheckFn);
1062 
1063     RegisterFunction("ui_print", UIPrintFn);
1064 
1065     RegisterFunction("run_program", RunProgramFn);
1066 }

这些语句执行各种功能。基本上,我们只需要知道用法就可以了。值得注意的是,run_program原语允许我们去执行自定义程序,这应该足够满足我们的个性化需求了。



原创粉丝点击