Chinaunix首页 | 论坛 | 博客
  • 博客访问: 438266
  • 博文数量: 205
  • 博客积分: 5630
  • 博客等级: 大校
  • 技术积分: 1945
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-06 20:28
文章分类

全部博文(205)

文章存档

2016年(1)

2015年(6)

2014年(9)

2013年(10)

2012年(53)

2011年(25)

2010年(87)

2009年(14)

分类: LINUX

2011-11-14 11:25:22

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

作者: 宋立新

Email

前言

       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 包含了一个头文件: #include ""这个文件(system/core/include/private/android_filesystem_config.h)hardcoding 的方式设定了 system 下各目录、文件的权限、属主。比如:     { 00440, ,      ,     "system/etc/init.goldfish.rc" },     { 00550, ,      ,     "system/etc/init.goldfish.sh" },     { 00440, ,      ,     "system/etc/init.trout.rc" },     { 00550, ,      ,     "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

前言

       前面介绍了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)    
  14.   
  15.       Deprecated.  
  16.   
  17.   -k  (--package_key)    
  18.   
  19.       Key to use to sign the package (default is  
  20.   
  21.       "build/target/product/security/testkey").  
  22.   
  23.   -i  (--incremental_from)    
  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)    
  44.   
  45.       Insert the contents of file at the end of the update script.  
  46.   
  47.   -m  (--script_mode)    
  48.   
  49.       Specify 'amend' or 'edify' scripts, or 'auto' to pick  
  50.   
  51.       automatically (this is the default).  
  52.   
  53.   -p  (--path)    
  54.   
  55.       Prepend /bin to the list of places to search for binaries  
  56.   
  57.       run by this script, and expect to find jars in /framework.  
  58.   
  59.   -s  (--device_specific)   
  60.   
  61.       Path to the python module containing device-specific  
  62.   
  63.       releasetools code.  
  64.   
  65.   -x  (--extra)    
  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__开始:

 if __name__ == '__main__':   try:     main(sys.argv[1:])   except common.ExternalError, e:     print     print "   ERROR: %s" % (e,)     print     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 即可。    if OPTIONS.script_mode not in ("amend", "edify", "auto"):     raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) Script_mode 只能是amend/edify/auto之一, auto 目前是选择两者都支持。可以理解是为了向前兼容,(早期 Android 使用 amend)    if OPTIONS.extra_script is not None:     OPTIONS.extra_script = open(OPTIONS.extra_script).read() 读入 额外脚本的内容。(如果有)    print "unzipping target target-files..."   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, 没用到。    common.LoadMaxSizes()   if not OPTIONS.max_image_size:     print     print "  WARNING:  Failed to load max image sizes; will not enforce"     print "  image size limits."     print 读入设定image大小的参数,没用到。    OPTIONS.target_tmp = OPTIONS.input_tmp   input_zip = zipfile.ZipFile(args[0], "r")   if OPTIONS.package_key:     temp_zip_file = tempfile.NamedTemporaryFile()     output_zip = zipfile.ZipFile(temp_zip_file, "w",                                  compression=zipfile.ZIP_DEFLATED)   else:     output_zip = zipfile.ZipFile(args[1], "w",                  compression=zipfile.ZIP_DEFLATED) 设定输出文件,如果要签名(our case,则还需要一个临时输出文件。    if OPTIONS.incremental_source is None:     WriteFullOTAPackage(input_zip, output_zip)   else:     print "unzipping source target-files..."     OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)     source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 根据参数,调用增量和非增量创建 ZIP 创建函数,我们采用非增量模式。    output_zip.close()   if OPTIONS.package_key:     SignOutput(temp_zip_file.name, args[1])     temp_zip_file.close()   common.Cleanup()   print "done."

 签名(如果需要的话),处理完毕。

 

下面我们看主要功能函数:WriteFullOTAPackage


二.脚本ota_from_target_files(第二部分)  

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

主功能:WriteFullOTAPackage

  def WriteFullOTAPackage(input_zip, output_zip):

   if OPTIONS.script_mode == "auto":     script = both_generator.BothGenerator(2)   elif OPTIONS.script_mode == "amend":     script = amend_generator.AmendGenerator()   else:     # TODO: how to determine this?  We don't know what version it will     # be installed on top of.  For now, we expect the API just won't     # change very often.     script = edify_generator.EdifyGenerator(2) 首先,我们获得脚本生成器,他们的实现见脚本:edify_generator.py 等。    metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),               "pre-device": GetBuildProp("ro.product.device", input_zip),               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),               } 获得一些环境变量,来自android 环境变量。 Google 一下即知其义。    device_specific = common.DeviceSpecificParams(       input_zip=input_zip,       input_version=GetRecoveryAPIVersion(input_zip),       output_zip=output_zip,       script=script,       input_tmp=OPTIONS.input_tmp,       metadata=metadata) 设备相关参数,不深究。    if not OPTIONS.omit_prereq:     ts = GetBuildProp("ro.build.date.utc", input_zip)     script.AssertOlderBuild(ts) 如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于升级老的系统。    AppendAssertions(script, input_zip) 如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于同一设备,即目标设备的 ro.product.device必须跟update.zip中的相同。

 

   device_specific.FullOTA_Assertions() Callback, 用于调用设备相关代码。调用时机为即将开始升级。类似还有:FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。    script.ShowProgress(0.5, 0) 在升级脚本中加入显示进度的语句, 参数一表示底下的操作(到下一条同类语句或者到末尾)将暂用的时间在总体时间的比例。参数二用于控制显示的速度。比如,50 则表示底下的操作估计50秒内完成,要求进度条显示线程用50秒显示这一部分的进度。表示不自动更新,手动控制(使用SetProgress)    if OPTIONS.wipe_user_data:     script.FormatPartition("userdata") 如果需要,在脚本中增加语句,擦除 userdata 分区。    script.FormatPartition("system") 在脚本中增加语句,擦除 system分区。    script.Mount("MTD", "system", "/system") 在脚本中增加语句,安装 system分区到 /system 目录。   script.UnpackPackageDir("recovery", "/system")   script.UnpackPackageDir("system", "/system")在脚本中增加语句,将recovery以及system中的内容拷贝到 /system目录。其中recovery 目录包含一个patch 以及应用该patch 的脚本。    symlinks = CopySystemFiles(input_zip, output_zip)   script.MakeSymlinks(symlinks) 386 行从输入 ZIP  /system 拷贝文件到输出 ZIP  /system。由于这个过程不支持链接文件,所以它将这些文件返回。 于 387 行做继续处理。该行建立这些link 文件。所有的link文件都指向 toolbox    boot_img = File("boot.img", common.BuildBootableImage(       os.path.join(OPTIONS.input_tmp, "BOOT")))   recovery_img = File("recovery.img", common.BuildBootableImage(       os.path.join(OPTIONS.input_tmp, "RECOVERY")))   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    Item.GetMetadata(input_zip) 从 META/filesystem_config.txt 中获得 system 目录下的各文件权限信息。    Item.Get("system").SetPermissions(script) 在脚本中增加语句,设置 system 目录下文件的权限及属主等。    common.CheckSize(boot_img.data, "boot.img") 检查 boot.img 文件大小是否超标.    common.ZipWriteStr(output_zip, "boot.img", boot_img.data) boot.img 放到输出 ZIP 包中。    script.ShowProgress(0.2, 0)   script.ShowProgress(0.2, 10) 更行进度条。    script.WriteRawImage("boot", "boot.img") 在脚本中增加语句,将 boot.img 写到 boot 分区。    script.ShowProgress(0.1, 0) 更行进度条。    device_specific.FullOTA_InstallEnd() Callback, 同前。    if OPTIONS.extra_script is not None:     script.AppendExtra(OPTIONS.extra_script) 如果有额外脚本,加入。    script.UnmountAll() 在脚本中增加语句,umount 所有分区。    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    WriteMetadata(metadata, output_zip)

将前面获取的metadata 写入输出包的文件中: META-INF/com/android/metadata

至此,我们就得到了一个update.zip包。可以开始升级了。

思考

1 虽然提供了更新recovery分区的机制,但是没有看到触发该更新的语句。所以,缺省的情况是不会更新recovery分区的。大概是为了安全的原因吧。 但是,有时确实需要更新recovery 分区(比如,设备的硬件配置、分区表等发生改变),这该如何操作呢?


Android OTA 升级之三:生成recovery.img

作者: 宋立新

Email

前言

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

 

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

(From: build/core/Makefile)

 

 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) /                 $(INSTALLED_RAMDISK_TARGET) /                 $(INSTALLED_BOOTIMAGE_TARGET) /                 $(recovery_binary) /                 $(recovery_initrc) $(recovery_kernel) /                 $(INSTALLED_2NDBOOTLOADER_TARGET) /                 $(recovery_build_prop) $(recovery_resource_deps) /                 $(RECOVERY_INSTALL_OTA_KEYS)

 

INSTALLED_RECOVERYIMAGE_TARGET 为我们的编译目标:

 INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

 

它依赖很多其它目标:

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

 

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

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

 

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

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

 

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

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

 

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

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

 

7.INSTALLED_2NDBOOTLOADER_TARGET,我们不用。

 

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

 

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

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

10.  RECOVERY_INSTALL_OTA_KEYS, ota 密钥:

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

 

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

./root

./root/etc

./root/tmp

 

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

 

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

          cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/         cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/ 拷贝recovery 模式的核心文件 init.rc  recovery          cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/         $(foreach item,$(recovery_resources_private), /           cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)         cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 拷贝资源文件及密钥文件。           cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) /                 > $(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         $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk) 压缩recovery根文件系统          build/quacomm/mkimage $(PRODUCT_OUT)/ramdisk-recovery.img RECOVERY > $(PRODUCT_OUT)/ramdisk_recovery.img 加一个标识头(RECOVERY         mv $(PRODUCT_OUT)/ramdisk_recovery.img $(PRODUCT_OUT)/ramdisk-recovery.img         $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $@         @echo ----- Made recovery image -------- $@         $(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

前言

       bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。

       下面,我们就看看进入Recovery 根文件系统都干些啥。

 

init.rc

       和正常启动一样,内核进入文件系统会执行/init, init 的配置文件就是 /init.rc, 前面文章讲过,这个文件来自:bootable/recovery/etc/init.rc,下面,我们看看它的内容。

 

     on init       export PATH /sbin       export ANDROID_ROOT /system       export ANDROID_DATA /data       export EXTERNAL_STORAGE /sdcard         symlink /system/etc /etc        mkdir /sdcard     mkdir /system     mkdir /data     mkdir /cache     mount /tmp /tmp tmpfs  on boot      ifup lo     hostname localhost     domainname localdomain      class_start default   service recovery /sbin/recovery  service adbd /sbin/adbd recovery     disabled  on property:persist.service.adb.enable=1     start adbd  on property:persist.service.adb.enable=0     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的实现很有帮助,下面看一下:(我就不翻译了)

 /*  * The recovery tool communicates with the main system through /cache files.  *   /cache/recovery/command - INPUT - command line for tool, one arg per line  *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)  *   /cache/recovery/intent - OUTPUT - intent that was passed in  *  * The arguments which may be supplied in the recovery.command file:  *   --send_intent=anystring - write the text out to recovery.intent  *   --update_package=root:path - verify install an OTA package file  *   --wipe_data - erase user data (and cache), then reboot  *   --wipe_cache - wipe cache (but not user data), then reboot  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs  *  * After completing, we remove /cache/recovery/command and reboot.  * Arguments may also be supplied in the bootloader control block (BCB).  * These important scenarios must be safely restartable at any point:  *  * FACTORY RESET  * 1. user selects "factory reset"  * 2. main system writes "--wipe_data" to /cache/recovery/command  * 3. main system reboots into recovery  * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"  *    -- after this, rebooting will restart the erase --  * 5. erase_root() reformats /data  * 6. erase_root() reformats /cache  * 7. finish_recovery() erases BCB  *    -- after this, rebooting will restart the main system --  * 8. main() calls reboot() to boot main system  *  * OTA INSTALL  * 1. main system downloads OTA package to /cache/some-filename.zip  * 2. main system writes "--update_package=CACHE:some-filename.zip"  * 3. main system reboots into recovery  * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."  *    -- after this, rebooting will attempt to reinstall the update --  * 5. install_package() attempts to install the update  *    NOTE: the package install must itself be restartable from any point  * 6. finish_recovery() erases BCB  *    -- after this, rebooting will (try to) restart the main system --  * 7. ** if install failed **  *    7a. prompt_and_wait() shows an error icon and waits for the user  *    7b; the user reboots (pulling the battery, etc) into the main system  * 8. main() calls maybe_install_firmware_update()  *    ** if the update contained radio/hboot firmware **:  *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"  *        -- after this, rebooting will reformat cache & restart main system --  *    8b. m_i_f_u() writes firmware image into raw cache partition  *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"  *        -- after this, rebooting will attempt to reinstall firmware --  *    8d. bootloader tries to flash firmware  *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")  *        -- after this, rebooting will reformat cache & restart main system --  *    8f. erase_root() reformats /cache  *    8g. finish_recovery() erases BCB  *        -- after this, rebooting will (try to) restart the main system --  * 9. main() calls reboot() to boot main system  *  * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE  * 1. user selects "enable encrypted file systems"  * 2. main system writes "--set_encrypted_filesystem=on|off" to  *    /cache/recovery/command  * 3. main system reboots into recovery  * 4. get_args() writes BCB with "boot-recovery" and  *    "--set_encrypted_filesystems=on|off"  *    -- after this, rebooting will restart the transition --  * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data  *    Settings include: property to specify the Encrypted FS istatus and  *    FS encryption key if enabled (not yet implemented)  * 6. erase_root() reformats /data  * 7. erase_root() reformats /cache  * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data  *    Settings include: property to specify the Encrypted FS status and  *    FS encryption key if enabled (not yet implemented)  * 9. finish_recovery() erases BCB  *    -- after this, rebooting will restart the main system --  * 10. main() calls reboot() to boot main system  */

 

recovery 主程序 int (int , char **) {       = ();     // If these fail, there's not really anywhere to complain...     ("a"); ();     ("a"); ();     ("Starting recovery on %s"(&)); 

将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。后面还会看到,recovery模式运行完毕后,会将其拷贝到cache分区,以便后续分析。

      (); Recovery 使用了一个简单的基于framebufferui系统,叫miniui,这里,进行了简单的初始化(主要是图形部分以及事件部分),并启动了一个 event 线程用于响应用户按键。      (&, &);

misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv ,并且,如果有必要,回写入misc分区。这样,如果recovery没有操作成功(比如,升级还没有结束,就拔电池),系统会一直进入recovery模式。提醒用户继续升级,直到成功。

     int previous_runs = 0;     const char *send_intent = ;     const char *update_package = ;     int  = 0, wipe_cache = 0;     int ;     while (( = ("")) != -1) {         switch () {         case 'p': previous_runs = (); break;         case 's': send_intent = ; break;         case 'u': update_package = ; break;         case 'w' = wipe_cache = 1; break;         case 'c': wipe_cache = 1; break;         case '?':             ("Invalid command argument/n");             continue;         }     }解析参数,p: previous_runs没有用到,其它含义见前面注释。      device_recovery_start();这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。     ("Command:");     for ( = 0;  < ++) {         (" /"%s/""[]);     }     ("/n/n");打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery"     ();     ("/n");打印出所有的系统属性(from default.prop)到log文件。      int  = INSTALL_SUCCESS;     if (update_package != ) {          = (update_package);         if ( != INSTALL_SUCCESS) ("Installation aborted./n");     } else if () {         if (device_wipe_data())  = INSTALL_ERROR;         if (("DATA:"))  = INSTALL_ERROR;         if (wipe_cache && ("CACHE:"))  = INSTALL_ERROR;         if ( != INSTALL_SUCCESS) ("Data wipe failed./n");     } else if (wipe_cache) {         if (wipe_cache && ("CACHE:"))  = INSTALL_ERROR;         if ( != INSTALL_SUCCESS) ("Cache wipe failed./n");     } else {          = INSTALL_ERROR;  // No command specified     } 根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区擦除user data分区,比较复杂,后面专门分析,其它都很简单。忽略。      if ( != INSTALL_SUCCESS) (BACKGROUND_ICON_ERROR);     if ( != INSTALL_SUCCESS) (); 而用户可选操作为: reboot, 安装update.zip,除cache分区擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。      // Otherwise, get ready to boot the main system...     (send_intent); 它的功能如下:1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";3)清空 misc 分区,这样重启就不会进入recovery模式4)删除command 文件:CACHE:recovery/command;      ("Rebooting.../n");     ();     ();     return ; } 

重启。

下面我们分析核心函数 

 

 int (const char *root_path) {     (BACKGROUND_ICON_INSTALLING);     ui_print("Finding update package.../n");     ("Finding update package.../n");     ();     ("Update location: %s/n", root_path);更新 UI 显示     if ((root_path) != 0) {         ("Can't mount %s/n", root_path);         ();         return INSTALL_CORRUPT;     } 确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区      char [] = "";     if ((root_path, , sizeof()) == ) {         ("Bad path %s/n", root_path);         ();         return INSTALL_CORRUPT;     } 将根分区转化为具体分区信息.这些信息来自:全局数组:      ui_print("Opening update package.../n");     ("Opening update package.../n");     ("Update file path: %s/n");     int numKeys;     RSAPublicKey* loadedKeys = (, &numKeys);     if (loadedKeys == ) {         ("Failed to load keys/n");         ();         return INSTALL_CORRUPT;     }     ("%d key(s) loaded from %s/n", numKeys, ); /res/keys中装载公钥。     // Give verification half the progress bar...     ui_print("Verifying update package.../n");     ("Verifying update package.../n");     (             ,             );     int ;      = (, loadedKeys, numKeys);     (loadedKeys);     ("verify_file returned %d/n");     if ( != ) {         ("signature verification failed/n");         ();         return INSTALL_CORRUPT;     } 根据公钥验证升级包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.      /* Try to open the package.      */      zip;      = (, &zip);     if ( != 0) {         ("Can't open %s/n(%s)/n" != -1 ? () : "bad");         ();         return INSTALL_CORRUPT;     } 打开升级包,将相关信息存到ZuoArchive数据机构中,便于后面处理。     /* Verify and install the contents of the package.      */     int  = (, &zip); 处理函数,我们后面继续分析。     (&zip);     return ; } 关闭zip包,结束处理。  static int (const char * *zip) {     // Update should take the rest of the progress bar.     ("Installing update.../n");     int  = (, zip);     ();  // Unregister package root     return ; } 

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

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

  static int

  (const char *,  *zip) {

      const * binary_entry =

              (zip, );

      if (binary_entry == ) {

          return INSTALL_CORRUPT;

      }

 

      char*  = "/tmp/update_binary";

      ();

      int  = (, 0755);

      if ( < 0) {

          ("Can't make %s/n", );

          return 1;

      }

       = (zip, binary_entry, );

     ();

     if (!) {

         ("Can't copy %s/n", );

         return 1;

     }

 将升级包内文件META-INF/com/google/android/update-binary 复制为/tmp/update_binary

 

     int pipefd[2];

     (pipefd);

     // When executing the update binary contained in the package, the

     // arguments passed are:

     //

     //   - the version number for this interface

     //

     //   - an fd to which the program can write in order to update the

     //     progress bar.  The program can write single-line commands:

     //

     //        progress

     //            fill up the next part of of the progress bar

     //            over seconds.  If is zero, use

     //            set_progress commands to manually control the

     //            progress of this segment of the bar

     //

     //        set_progress

     //             should be between 0.0 and 1.0; sets the

     //            progress bar within the segment defined by the most

     //            recent progress command.

     //

     //        firmware <"hboot"|"radio">

     //            arrange to install the contents of in the

     //            given partition on reboot.

     //

     //            (API v2: may start with "PACKAGE:" to

     //            indicate taking a file from the OTA package.)

     //

     //            (API v3: this command no longer exists.)

     //

     //        ui_print

     //            display on the screen.

     //

     //   - the name of the package zip file.

     //

 

注意看这段注释,它解释了以下代码的行为。结合代码,可知:

1)  将会创建新的进程,执行:/tmp/update_binary

2)  同时,会给该进程传入一些参数,其中最重要的就是一个管道fd,供新进程与原进程通信。

3)  新进程诞生后,原进程就变成了一个服务进程,它提供若干UI更新服务:

a)   progress

b)   set_progress

c)   ui_print

这样,新进程就可以通过老进程的UI系统完成显示任务。而其他功能就靠它自己了。

 

     char**  = (sizeof(char*) * 5);

     [0] = ;

     [1] = (RECOVERY_API_VERSION);   // defined in Android.mk

     [2] = (10);

     ([2], "%d", pipefd[1]);

     [3] = (char*);

     [4] = ;

       = ();

     if ( == 0) {

         (pipefd[0]);

         (, );

         (, "E:Can't run %s (%s)/n", , ());

         (-1);

     }

     (pipefd[1]);

     char [1024];

     * from_child = (pipefd[0], "r");

     while ((, sizeof(), from_child) != ) {

         char*  = (, " /n");

         if ( == ) {

             continue;

         } else if ((, "progress") == 0) {

             char* fraction_s = (, " /n");

             char* seconds_s = (, " /n");

             float fraction = (fraction_s, );

             int  = (seconds_s, , 10);

             (fraction * (1-),

                              );

         } else if ((, "set_progress") == 0) {

             char* fraction_s = (, " /n");

             float fraction = (fraction_s, );

             (fraction);

         } else if ((, "ui_print") == 0) {

             char*  = (, "/n");

             if () {

                 ();

             } else {

                 ("/n");

             }

         } else {

             ("unknown command [%s]/n", );

         }

     }

     (from_child);

     int ;

     (, &, 0);

     if (!() || () != 0) {

         ("Error in %s/n(Status %d)/n", , ());

         return INSTALL_ERROR;

     }

     return INSTALL_SUCCESS;

 }

 

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



Android OTA 升级之五:updater

作者: 宋立新

Email

前言

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

       Updater可以作为学习解释器/编译器的同学一个很好的实例,但是我们只关心产品化相关的内容,所以并不去深究lex/yacc相关的东西。

 

入口函数 main

(from: bootable/recovery/updater/updater.c)

 // Where in the package we expect to find the edify script to execute.

  // (Note it's "updateR-script", not the older "update-script".)

  #define  "META-INF/com/google/android/updater-script"

 

 

这里定义脚本的位置,注释说明本updater支持edify格式的脚本。

 

  int (int , char** ) {

      // Various things log information to stdout or stderr more or less

      // at random.  The log file makes more sense if buffering is

      // turned off so things appear in the right order.

      (, );

      (, );

 

      if ( != 4) {

          (, "unexpected number of arguments (%d)/n", );

          return 1;

      }

 

      char*  = [1];

      if (([0] != '1' && [0] != '2' && [0] != '3') ||

          [1] != '/0') {

          // We support version 1, 2, or 3.

          (, "wrong updater binary API; expected 1, 2, or 3; "

                          "got %s/n",

                  [1]);

          return 2;

      }

 

获取 version 参数。

      // Set up the pipe for sending commands back to the parent process.

 

      int  = ([2]);

      * cmd_pipe = (, "wb");

      (cmd_pipe);

 

 

获取命令管道(用于图形显示等,见前篇)

 

      // Extract the script from the package.

 

      char* package_data = [3];

       za;

      int ;

       = (package_data, &za);

     if ( != 0) {

         (, "failed to open package %s: %s/n",

                 package_data, ());

         return 3;

     }

     const * script_entry = (&za, );

     if (script_entry == ) {

         (, "failed to find %s in %s/n", , package_data);

         return 4;

     }

     char*  = (script_entry->uncompLen+1);

     if (!(&za, script_entry, , script_entry->uncompLen)) {

         (, "failed to read script from package/n");

         return 5;

     }

     [script_entry->uncompLen] = '/0';

 

读入脚本 META-INF/com/google/android/updater-script

 

     // Configure edify's functions.

     ();

     ();

     RegisterDeviceExtensions();

     ();

注册语句处理函数

     // Parse the script.

     * ;

     int  = 0;

     ();

     int  = (&, &);

     if ( != 0 ||  > 0) {

         (, "%d parse errors/n", );

         return 6;

     }

调用yy* 库函数解析脚本。

     // Evaluate the parsed script.

      updater_info;

     updater_info.cmd_pipe = cmd_pipe;

     updater_info.package_zip = &za;

     updater_info. = ();

      ;

     . = &updater_info;

     . = ;

     . = ;

     char*  = (&, );

     if ( == ) {

         if (. == ) {

             (, "script aborted (no error message)/n");

             (cmd_pipe, "ui_print script aborted (no error message)/n");

         } else {

             (, "script aborted: %s/n", .);

             char*  = (., "/n");

             while () {

                 (cmd_pipe, "ui_print %s/n", );

                  = (, "/n");

             }

             (cmd_pipe, "ui_print/n");

         }

         (.);

         return 7;

     } else {

         (, "script result was [%s]/n", );

         ();

     }

解释执行脚本。 核心函数是 。它会调用其他callback函数,而这些callback函数又会调用Evaluate去解析不同的脚本片段。从而实现一个简单的解释器。

     (&za);

     ();

     return 0;

 }

 

还没开始,就结束了。代码非常简单,因为细节隐藏在那些callback函数里。我们看一下。

 void () {     ("ifelse");     ("abort");     ("assert");     ("concat");     ("is_substring");     ("stdout");     ("sleep");     ("less_than_int");     ("greater_than_int"); }

这些语句控制执行流程。

 void () {     ("mount");     ("is_mounted");     ("unmount");     ("format");     ("show_progress");     ("set_progress");     ("delete");     ("delete_recursive");     ("package_extract_dir");     ("package_extract_file");     ("symlink");     ("set_perm");     ("set_perm_recursive");     ("getprop");     ("file_getprop");     ("write_raw_image");     ("apply_patch");     ("apply_patch_check");     ("apply_patch_space");     ("read_file");     ("sha1_check");     ("ui_print");     ("run_program"); }

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




阅读(4853) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~