Android OTA 升级

原文:http://fanwei51880.blog.163.com/blog/static/32406740201172325219944/
Android OTA 升级之一:编译升级包

作者: 宋立新

Emailzjujoe@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    └── tmpL1109-1125, 填充 FACTORY 子目录的内容, 没有用到,包括:kernel imageL1126-1143, 填充 BOOT子目录的内容,用于生成boot.img。和 RECOVERY目录类似,包括:kernel image,根文件系统的 image,根文件系统的内容:BOOT$ tree -L 2.├── kernel├── ramdisk└── RAMDISK    ├── advanced_meta_init.rc    ├── data    ├── default.prop    ├── dev    ├── init    ├── init.factory.rc    ├── init.goldfish.rc    ├── init.mt6516.rc    ├── init.rc    ├── meta_init.rc    ├── proc    ├── res -> /system/res    ├── sbin    ├── sys    └── system L1144-1146, 填充 RADIO子目录的内容, 没有用到。L1147-1149, 填充 SYSTEM子目录的内容。 这是升级的主要内容。L1150-1152, 填充 DATA子目录的内容。缺省没有用到。L1153-1156, 填充 OTA/bin子目录的内容,这是OTA升级自己使用的程序。后面会遇到。OTA/bin$ tree.├── applypatch├── applypatch_static├── check_prereq└── updaterL1159-1169, 填充 META子目录的内容,这里包含了OTA脚本需要的一些附加信息。L1170-1171,将所有内容打包。供下一阶段使用。L1173-1174,生成 META/filesystem_config.txt 并将其加入到 zip 包中。该文件保存了 system 目录下各目录、文件的权限及 owner.$ head META/filesystem_config.txtsystem 0 0 755system/usr 0 0 755system/usr/srec 0 0 755system/usr/srec/config 0 0 755system/usr/srec/config/en.us 0 0 755system/usr/srec/config/en.us/grammars 0 0 755system/usr/srec/config/en.us/grammars/phone_type_choice.g2g 0 0 644system/usr/srec/config/en.us/grammars/VoiceDialer.g2g 0 0 644system/usr/srec/config/en.us/grammars/boolean.g2g 0 0 644system/usr/srec/config/en.us/g2p 0 0 755 这里,目录由 zipinfo –l 提供, 而权限则由 fs_config 设定。此程序的源码位于:build/tools/fs_config, 其中fs_config 包含了一个头文件: 54  #include "private/android_filesystem_config.h" 这个文件(system/core/include/private/android_filesystem_config.h)hardcoding 的方式设定了 system 下各目录、文件的权限、属主。比如: 152      { 00440, AID_ROOT,      AID_SHELL,     "system/etc/init.goldfish.rc" }, 153      { 00550, AID_ROOT,      AID_SHELL,     "system/etc/init.goldfish.sh" }, 154      { 00440, AID_ROOT,      AID_SHELL,     "system/etc/init.trout.rc" }, 155      { 00550, AID_ROOT,      AID_SHELL,     "system/etc/init.ril" },  如果需要升级其它内容,比如 bootloader, 则可以在这里加入。  步骤二

编译脚本如下:

(From: build/core/Makefile)

  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

作者: 宋立新

Emailzjujoe@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_filespython 脚本,所以如果懂 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


二.脚本ota_from_target_files(第二部分)  

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

主功能:WriteFullOTAPackage

 345 def WriteFullOTAPackage(input_zip, output_zip):

346    if OPTIONS.script_mode == "auto": 347      script = both_generator.BothGenerator(2) 348    elif OPTIONS.script_mode == "amend": 349      script = amend_generator.AmendGenerator() 350    else: 351      # TODO: how to determine this?  We don't know what version it 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秒显示这一部分的进度。表示不自动更新,手动控制(使用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.imgpatch), 它最后会位于:system/recovery-from-boot.p2.在输出 ZIP包中生成一个脚本:recovery/etc/install-recovery.sh , 它最后会位于system/etc/install-recovery.sh.该脚本的内容为:#!/system/bin/shif ! applypatch -c MTD:recovery:2048:6a167ffb86a4a16cb993473ce0726a3067163fc1; then  log -t recovery "Installing new recovery image"  applypatch MTD:boot:2324480:9a72a20a9c2f958ba586a840ed773cf8f5244183 MTD:recovery f6c2a70c5f2b02b6a49c9f5c5507a45a42e2d389 2564096 9a72a20a9c2f958ba586a840ed773cf8f5244183:/system/recovery-from-boot.pelse  log -t recovery "Recovery image already installed"fi  395    Item.GetMetadata(input_zip) 从 META/filesystem_config.txt 中获得 system 目录下的各文件权限信息。  396    Item.Get("system").SetPermissions(script) 在脚本中增加语句,设置 system 目录下文件的权限及属主等。  398    common.CheckSize(boot_img.data, "boot.img") 检查 boot.img 文件大小是否超标.  399    common.ZipWriteStr(output_zip, "boot.img", boot_img.data) boot.img 放到输出 ZIP 包中。  400    script.ShowProgress(0.2, 0) 402    script.ShowProgress(0.2, 10) 更行进度条。  403    script.WriteRawImage("boot", "boot.img") 在脚本中增加语句,将 boot.img 写到 boot 分区。  405    script.ShowProgress(0.1, 0) 更行进度条。  406    device_specific.FullOTA_InstallEnd() Callback, 同前。  408    if OPTIONS.extra_script is not None: 409      script.AppendExtra(OPTIONS.extra_script) 如果有额外脚本,加入。  411    script.UnmountAll() 在脚本中增加语句,umount 所有分区。  412    script.AddToZip(input_zip, output_zip) 1)将前面生成的脚本输出到:META-INF/com/google/android/updater-script (对于edify  
  1. assert(getprop("ro.product.device") == "thedevicename" ||  
  2.   
  3.        getprop("ro.build.product") == "theproductname");  
  4.   
  5. show_progress(0.5000000);  
  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(0007550644"/system");  
  54.   
  55. set_perm_recursive(0200007550755"/system/bin");  
  56.   
  57. set_perm(0300302755"/system/bin/netcfg");  
  58.   
  59. set_perm(0300402755"/system/bin/ping");  
  60.   
  61. set_perm_recursive(1002100207550440"/system/etc/bluez");  
  62.   
  63. set_perm(000755"/system/etc/bluez");  
  64.   
  65. set_perm(100210020440"/system/etc/dbus.conf");  
  66.   
  67. set_perm(101420000550"/system/etc/dhcpcd/dhcpcd-run-hooks");  
  68.   
  69. set_perm(020000550"/system/etc/init.goldfish.sh");  
  70.   
  71. set_perm(000544"/system/etc/install-recovery.sh");  
  72.   
  73. set_perm_recursive(0007550555"/system/etc/ppp");  
  74.   
  75. set_perm_recursive(0200007550755"/system/xbin");  
  76.   
  77. show_progress(0.2000000);  
  78.   
  79. show_progress(0.20000010);  
  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.1000000);  
  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

作者: 宋立新

Emailzjujoe@yahoo.com

前言

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

 

recovery.img生成过程 L630-L637 依赖关系

(From: build/core/Makefile)

 

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

 

INSTALLED_RECOVERYIMAGE_TARGET 为我们的编译目标:

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

 

它依赖很多其它目标:

1MKBOOTFS, MINIGZIP, MKBOOTIMGPC端工具软件:From build/core/config.mk 265  MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX) 266  MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX) 267  MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)

 

2INSTALLED_RAMDISK_TARGET,标准根文件系统 ramdisk.img

326  BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img 328  # We just build this directly to the install location. 329  INSTALLED_RAMDISK_TARGET := $(BUILT_RAMDISK_TARGET) 3INSTALLED_BOOTIMAGE_TARGET, 即boot.img,标准内核及标准根文件系统: 362  INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img

 

4. recovery_binary, Recovery可执行程序,源码位于:bootable/recovery

590 recovery_binary := $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery

 

5. recovery_initrcrecovery模式的init.rc, 位于 bootable/recovery/etc/init.rc

586  recovery_initrc := $(call include-path-for, recovery)/etc/init.rc

 

6. recovery_kernel, recovery 模式的kernel, 同标准内核

587  recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system

 

7.INSTALLED_2NDBOOTLOADER_TARGET,我们不用。

 

8. recovery_build_prop, recovery 模式的build.prop, 同标准模式。 589  recovery_build_prop := $(INSTALLED_BUILD_PROP_TARGET)

 

9. recovery_resource_deps recovery 模式使用的res, 位于:recovery/custom/{product_name}/res, 以及设备自定义部分(我们没用到)

591  recovery_resources_common := $(call include-path-for, recovery)/custom/$(TARGET_PRODUCT)/res 592  recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res)) 593  recovery_resource_deps := $(shell find $(recovery_resources_common) 594    $(recovery_resources_private) -type f) 

10.  RECOVERY_INSTALL_OTA_KEYS, ota 密钥:

618  # Generate a file containing the keys that will be read by the 619  # recovery binary. 620  RECOVERY_INSTALL_OTA_KEYS := / 621          $(call intermediates-dir-for,PACKAGING,ota_keys)/keys L638-L655 准备内容 638          @echo ----- Making recovery image ------ 639          rm -rf $(TARGET_RECOVERY_OUT) 640          mkdir -p $(TARGET_RECOVERY_OUT) 641          mkdir -p $(TARGET_RECOVERY_ROOT_OUT) 642          mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc 643          mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp

 

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

./root

./root/etc

./root/tmp

 

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

 

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

  648          cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/ 649          cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/ 拷贝recovery 模式的核心文件 init.rc  recovery  650          cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/ 651          $(foreach item,$(recovery_resources_private), / 652            cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/) 653          cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 拷贝资源文件及密钥文件。   654          cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) / 655                  > $(TARGET_RECOVERY_ROOT_OUT)/default.prop 生成属性文件 default.prop, 它包含了标准根文件系统的default.propout/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 升级之四:进入根文件系统

作者: 宋立新

Emailzjujoe@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 line92  *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)93  *   /cache/recovery/intent - OUTPUT - intent that was passed in94  *95  * The arguments which may be supplied in the recovery.command file:96  *   --send_intent=anystring - write the text out to recovery.intent97  *   --update_package=root:path - verify install an OTA package file98  *   --wipe_data - erase user data (and cache), then reboot99  *   --wipe_cache - wipe cache (but not user data), then 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(stdoutNULL); 566      freopen(TEMPORARY_LOG_FILE"a"stderr); setbuf(stderrNULL); 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 使用了一个简单的基于framebufferui系统,叫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(argcargv""OPTIONSNULL)) != -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 < argcarg++) { 595          fprintf(stderr" /"%s/""argv[arg]); 596      } 597      fprintf(stderr"/n/n"); 598 打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery" 599      property_list(print_propertyNULL); 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/command2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";3)清空 misc 分区,这样重启就不会进入recovery模式4)删除command 文件:CACHE:recovery/command;  626      ui_print("Rebooting.../n"); 627      sync(); 628      reboot(RB_AUTOBOOT); 629      return EXIT_SUCCESS; 630  } 

重启。

下面我们分析核心函数 install_package

 

install_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"patherr != -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 *pathZipArchive *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(NULLNULL);  // 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 *pathZipArchive *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(binaryargs);

157         fprintf(stderr"E:Can't run %s (%s)/n"binarystrerror(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"pathWEXITSTATUS(status));

198         return INSTALL_ERROR;

199     }

200

201     return INSTALL_SUCCESS;

202 }

 

这样,我们又回到了升级包中的文件:META-INF/com/google/android/update-binary,下回继续研究。



Android OTA 升级之五:updater

作者: 宋立新

Emailzjujoe@yahoo.com

前言

       可以说,前面分析的OTA升级的各部分代码都是在搭一个舞台,而主角现在终于登场,它就是updater. Google的代码架构设计非常好,各部分尽量松耦合。前面介绍升级脚本时,可知有两种类型的脚本,amend & edify. 他们各自对应一个updater. 这里,我们主要关注新的edifyupdater.

       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(stdoutNULL);

 71     setbuf(stderrNULL);

 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     Exprroot;

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(&stateroot);

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原语允许我们去执行自定义程序,这应该足够满足我们的个性化需求了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值