Android OTA 升級


前言
       OTA 升級是 Android 系統提供的標準軟件升級方式。 它功能強大,提供了徹底升級、增量升級模式,能夠經過 SD 卡升級,也能夠經過網絡升級。
       這裏,咱們先研究最簡單的狀況,經過 SD 卡進行徹底升級。
       如何執行升級就很少說了,網上有不少資料。(好比,介紹HTC手機如何升級)。咱們感興趣的是它是如何實現的,做爲開發者,如何修改它以符合咱們的定製化需求。
       首先,咱們研究一下 ota 升級包的編譯過程。
Quick start
       首先編譯出android, 而後執行:
make otapackage
    便可得到:out/target/product/{product_name}/ {product_name}-ota-eng.{uid}.zip
    將該文件更名爲update.zip放到T卡根目錄, 便可開始recovery模式下的 OTA 升級。
編譯過程研究
 
主要分兩步,第一步, 會準備一個包,其中包含升級須要的內容(原材料),好比,system 目錄。
第二步,運行python 腳本 ./build/tools/releasetools/ota_from_target_files,以步驟一準備的ZIP包做爲輸入,最終生成須要的升級包。
 
步驟一
編譯腳本以下:
(From: build/core/Makefile)
 
 
1073 # Depending on the various images guarantees that the underlying  
1074 # directories are up-to-date.  
1075 $(BUILT_TARGET_FILES_PACKAGE): /  
1076                 $(INSTALLED_BOOTIMAGE_TARGET) /  
1077                 $(INSTALLED_RADIOIMAGE_TARGET) /  
1078                 $(INSTALLED_RECOVERYIMAGE_TARGET) /  
1079                 $(INSTALLED_FACTORYIMAGE_TARGET) /  
1080                 $(INSTALLED_SYSTEMIMAGE) /  
1081                 $(INSTALLED_USERDATAIMAGE_TARGET) /  
1082                 $(INSTALLED_SECROIMAGE_TARGET) /  
1083                 $(INSTALLED_ANDROID_INFO_TXT_TARGET) /  
1084                 $(built_ota_tools) /  
1085                 $(APKCERTS_FILE) /  
1086                 $(HOST_OUT_EXECUTABLES)/fs_config /  
1087                 | $(ACP)  
1088         @echo "Package target files: $@"  
1089         $(hide) rm -rf $@ $(zip_root)  
1090         $(hide) mkdir -p $(dir $@) $(zip_root)  
1091         @# Components of the recovery image  
1092         $(hide) mkdir -p $(zip_root)/RECOVERY  
1093         $(hide) $(call package_files-copy-root, /  
1094                 $(TARGET_RECOVERY_ROOT_OUT),$(zip_root)/RECOVERY/RAMDISK)  
1095 ifdef INSTALLED_KERNEL_TARGET  
1096         $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/RECOVERY/kernel  
1097         $(hide) $(ACP) $(recovery_ramdisk) $(zip_root)/RECOVERY/ramdisk  
1098 endif  
1099 ifdef INSTALLED_2NDBOOTLOADER_TARGET  
1100         $(hide) $(ACP) /  
1101                 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/RECOVERY/second  
1102 endif  
1103 ifdef BOARD_KERNEL_CMDLINE  
1104         $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/RECOVERY/cmdline  
1105 endif  
1106 ifdef BOARD_KERNEL_BASE  
1107         $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/RECOVERY/base  
1108 endif  
1109         @# Components of the factory image  
1110         $(hide) mkdir -p $(zip_root)/FACTORY  
1111         $(hide) $(call package_files-copy-root, /  
1112                 $(TARGET_FACTORY_ROOT_OUT),$(zip_root)/FACTORY/RAMDISK)  
1113 ifdef INSTALLED_KERNEL_TARGET  
1114         $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/FACTORY/kernel  
1115 endif  
1116 ifdef INSTALLED_2NDBOOTLOADER_TARGET  
1117         $(hide) $(ACP) /  
1118                 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/FACTORY/second  
1119 endif  
1120 ifdef BOARD_KERNEL_CMDLINE  
1121         $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/FACTORY/cmdline  
1122 endif  
1123 ifdef BOARD_KERNEL_BASE  
1124         $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/FACTORY/base  
1125 endif  
1126         @# Components of the boot image  
1127         $(hide) mkdir -p $(zip_root)/BOOT  
1128         $(hide) $(call package_files-copy-root, /  
1129                 $(TARGET_ROOT_OUT),$(zip_root)/BOOT/RAMDISK)  
1130 ifdef INSTALLED_KERNEL_TARGET  
1131         $(hide) $(ACP) $(INSTALLED_KERNEL_TARGET) $(zip_root)/BOOT/kernel  
1132         $(hide) $(ACP) $(INSTALLED_RAMDISK_TARGET) $(zip_root)/BOOT/ramdisk  
1133 endif  
1134 ifdef INSTALLED_2NDBOOTLOADER_TARGET  
1135         $(hide) $(ACP) /  
1136                 $(INSTALLED_2NDBOOTLOADER_TARGET) $(zip_root)/BOOT/second  
1137 endif  
1138 ifdef BOARD_KERNEL_CMDLINE  
1139         $(hide) echo "$(BOARD_KERNEL_CMDLINE)" > $(zip_root)/BOOT/cmdline  
1140 endif  
1141 ifdef BOARD_KERNEL_BASE  
1142         $(hide) echo "$(BOARD_KERNEL_BASE)" > $(zip_root)/BOOT/base  
1143 endif  
1144         $(hide) $(foreach t,$(INSTALLED_RADIOIMAGE_TARGET),/  
1145                     mkdir -p $(zip_root)/RADIO; /  
1146                     $(ACP) $(t) $(zip_root)/RADIO/$(notdir $(t));)  
1147         @# Contents of the system image  
1148         $(hide) $(call package_files-copy-root, /  
1149                 $(SYSTEMIMAGE_SOURCE_DIR),$(zip_root)/SYSTEM)  
1150         @# Contents of the data image  
1151         $(hide) $(call package_files-copy-root, /  
1152                 $(TARGET_OUT_DATA),$(zip_root)/DATA)  
1153         @# Extra contents of the OTA package  
1154         $(hide) mkdir -p $(zip_root)/OTA/bin  
1155         $(hide) $(ACP) $(INSTALLED_ANDROID_INFO_TXT_TARGET) $(zip_root)/OTA/  
1156         $(hide) $(ACP) $(PRIVATE_OTA_TOOLS) $(zip_root)/OTA/bin/  
1157         @# Files that do not end up in any images, but are necessary to  
1158         @# build them.  
1159         $(hide) mkdir -p $(zip_root)/META  
1160         $(hide) $(ACP) $(APKCERTS_FILE) $(zip_root)/META/apkcerts.txt  
1161         $(hide) echo "$(PRODUCT_OTA_PUBLIC_KEYS)" > $(zip_root)/META/otakeys.txt  
1162         $(hide) echo "$(PRIVATE_RECOVERY_API_VERSION)" > $(zip_root)/META/recovery-api-version.txt  
1163         $(hide) echo "blocksize $(BOARD_FLASH_BLOCK_SIZE)" > $(zip_root)/META/imagesizes.txt  
1164         $(hide) echo "boot $(call image-size-from-data-size,$(BOARD_BOOTIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt  
1165         $(hide) echo "recovery $(call image-size-from-data-size,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt  
1166         $(hide) echo "system $(call image-size-from-data-size,$(BOARD_SYSTEMIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt  
1167         $(hide) echo "secro $(call image-size-from-data-size,$(BOARD_SECROIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt  
1168         $(hide) echo "userdata $(call image-size-from-data-size,$(BOARD_USERDATAIMAGE_PARTITION_SIZE))" >> $(zip_root)/META/imagesizes.txt  
1169         $(hide) echo "$(tool_extensions)" > $(zip_root)/META/tool-extensions.txt  
1170         @# Zip everything up, preserving symlinks  
1171         $(hide) (cd $(zip_root) && zip -qry ../$(notdir $@) .)  
1172         @# Run fs_config on all the system files in the zip, and save the output  
1173         $(hide) zipinfo -1 $@ | awk -F/ 'BEGIN { OFS="/" } /^SYSTEM/// {$$1 = "system"; print}' | $(HOST_OUT_EXECUTABLES)/fs_config > $(zip_root)/META/filesystem_config.txt  
1174         $(hide) (cd $(zip_root) && zip -q ../$(notdir $@) META/filesystem_config.txt)  
 
可見往裏面添加了不少內容。
L1089-1090 , 造一個目錄。
L1091-1108,填充 RECOVERY 子目錄的內容。用於生成recovery.img。包括:kernel 的image, recovery 根文件系統的 image, recovery 根文件系統的內容:RECOVERY$ tree -L 2├── kernel├── ramdisk└── RAMDISK    ├── advanced_meta_init.rc    ├── data    ├── default.prop    ├── dev    ├── etc    ├── init    ├── init.factory.rc    ├── init.goldfish.rc    ├── init.mt6516.rc    ├── init.rc    ├── meta_init.rc    ├── proc    ├── res    ├── sbin    ├── sys    ├── system    └── tmpL1109-1125, 填充 FACTORY 子目錄的內容, 沒有用到,包括:kernel 的imageL1126-1143, 填充 BOOT子目錄的內容,用於生成boot.img。和 RECOVERY目錄相似,包括:kernel 的image,根文件系統的 image,根文件系統的內容:BOOT$ tree -L 2.├── kernel├── ramdisk└── RAMDISK    ├── advanced_meta_init.rc    ├── data    ├── default.prop    ├── dev    ├── init    ├── init.factory.rc    ├── init.goldfish.rc    ├── init.mt6516.rc    ├── init.rc    ├── meta_init.rc    ├── proc    ├── res -> /system/res    ├── sbin    ├── sys    └── system L1144-1146, 填充 RADIO子目錄的內容, 沒有用到。L1147-1149, 填充 SYSTEM子目錄的內容。 這是升級的主要內容。L1150-1152, 填充 DATA子目錄的內容。缺省沒有用到。L1153-1156, 填充 OTA/bin子目錄的內容,這是OTA升級本身使用的程序。後面會遇到。OTA/bin$ tree.├── applypatch├── applypatch_static├── check_prereq└── updaterL1159-1169, 填充 META子目錄的內容,這裏包含了OTA腳本須要的一些附加信息。L1170-1171,將全部內容打包。供下一階段使用。L1173-1174,生成 META/filesystem_config.txt 並將其加入到 zip 包中。該文件保存了 system 目錄下各目錄、文件的權限及 owner.$ head META/filesystem_config.txtsystem 0 0 755system/usr 0 0 755system/usr/srec 0 0 755system/usr/srec/config 0 0 755system/usr/srec/config/en.us 0 0 755system/usr/srec/config/en.us/grammars 0 0 755system/usr/srec/config/en.us/grammars/phone_type_choice.g2g 0 0 644system/usr/srec/config/en.us/grammars/VoiceDialer.g2g 0 0 644system/usr/srec/config/en.us/grammars/boolean.g2g 0 0 644system/usr/srec/config/en.us/g2p 0 0 755 這裏,目錄由 zipinfo –l 提供, 而權限則由 fs_config 設定。此程序的源碼位於:build/tools/fs_config, 其中fs_config 包含了一個頭文件:54 #include "private/android_filesystem_config.h"這個文件(system/core/include/private/android_filesystem_config.h)以hardcoding 的方式設定了 system 下各目錄、文件的權限、屬主。好比:152     { 00440, AID_ROOT,      AID_SHELL,     "system/etc/init.goldfish.rc" },153     { 00550, AID_ROOT,      AID_SHELL,     "system/etc/init.goldfish.sh" },154     { 00440, AID_ROOT,      AID_SHELL,     "system/etc/init.trout.rc" },155     { 00550, AID_ROOT,      AID_SHELL,     "system/etc/init.ril" }, 若是須要升級其它內容,好比 bootloader, 則能夠在這裏加入。  步驟二
編譯腳本以下:
(From: build/core/Makefile)
1186 name := $(TARGET_PRODUCT)  
1187 ifeq ($(TARGET_BUILD_TYPE),debug)  
1188   name := $(name)_debug  
1189 endif  
1190 name := $(name)-ota-$(FILE_NAME_TAG)  
1191   
1192 INTERNAL_OTA_PACKAGE_TARGET := $(PRODUCT_OUT)/$(name).zip  
1193   
1194 $(INTERNAL_OTA_PACKAGE_TARGET): KEY_CERT_PAIR := $(DEFAULT_KEY_CERT_PAIR)  
1195   
1196 ifeq ($(TARGET_OTA_SCRIPT_MODE),)  
1197 # default to "auto"  
1198 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode := auto  
1199 else  
1200 $(INTERNAL_OTA_PACKAGE_TARGET): scriptmode := $(TARGET_OTA_SCRIPT_MODE)  
1201 endif  
1202   
1203 $(INTERNAL_OTA_PACKAGE_TARGET): $(BUILT_TARGET_FILES_PACKAGE) $(OTATOOLS)  
1204         @echo "Package OTA: $@"  
1205         $(hide) ./build/tools/releasetools/ota_from_target_files /  
1206            -m $(scriptmode) /  
1207            -p $(HOST_OUT) /  
1208            -k $(KEY_CERT_PAIR) /  
1209            $(BUILT_TARGET_FILES_PACKAGE) $@  
 
核心是一個python腳本: ota_from_target_files, 它之前一步驟生成的ZIP包做爲輸入,生成可用於OTA升級的zip包。 具體內容咱們後文繼續分析。


Android OTA 升級之二:腳本 ota_from_target_files
做者: 宋立新


Email:zjujoe@yahoo.com


前言
       前面介紹了ota package 的編譯過程,其中最核心的部分就是一個 python 腳本:ota_from_target_files. 如今咱們分析這個腳本。
先看一下幫助
不帶任何參數,先看一下它的幫助:
$ ./ota_from_target_files   
  
Given a target-files zipfile, produces an OTA package that installs  
  
that build.  An incremental OTA is produced if -i is given, otherwise  
  
a full OTA is produced.  
  
   
  
Usage:  ota_from_target_files [flags] input_target_files output_ota_package  
  
  -b  (--board_config)  <file>  
  
      Deprecated.  
  
  -k  (--package_key)  <key>  
  
      Key to use to sign the package (default is  
  
      "build/target/product/security/testkey").  
  
  -i  (--incremental_from)  <file>  
  
      Generate an incremental OTA using the given target-files zip as  
  
      the starting build.  
  
  -w  (--wipe_user_data)  
  
      Generate an OTA package that will wipe the user data partition  
  
      when installed.  
  
  -n  (--no_prereq)  
  
      Omit the timestamp prereq check normally included at the top of  
  
      the build scripts (used for developer OTA packages which  
  
      legitimately need to go back and forth).  
  
  -e  (--extra_script)  <file>  
  
      Insert the contents of file at the end of the update script.  
  
  -m  (--script_mode)  <mode>  
  
      Specify 'amend' or 'edify' scripts, or 'auto' to pick  
  
      automatically (this is the default).  
  
  -p  (--path)  <dir>  
  
      Prepend <dir>/bin to the list of places to search for binaries  
  
      run by this script, and expect to find jars in <dir>/framework.  
  
  -s  (--device_specific) <file>  
  
      Path to the python module containing device-specific  
  
      releasetools code.  
  
  -x  (--extra)  <key=value>  
  
      Add a key/value pair to the 'extras' dict, which device-specific  
  
      extension code may look at.  
  
  -v  (--verbose)  
  
      Show command lines being executed.  
  
  -h  (--help)  
  
      Display this usage message and exit.  
  
簡單翻譯一下:
-b 過期,再也不使用。
-k 簽名用的密鑰
-i 生成增量OTA包時用於定義對比包
-w 是否清除 userdata 分區
-n 是否在升級時不檢查時間戳,缺省狀況下只能基於老的版本升級。
-e 定義額外運行的腳本
-m 定義採用的腳本格式,目前有兩種,amend & edify, 其中amend爲較老的格式。對應的,升級時會採用不一樣的解釋器。缺省狀況下,ota_from_target_files 會同時生成兩個腳本。這提供了最大靈活性。
-p 定義腳本用到的一些可執行文件的路徑
-s 定義額外運行的腳本的路徑
-x 定義額外運行的腳本可能用到的鍵/值對
-v 老朋友,冗餘模式,讓腳本打印出執行的命令
-h 老朋友,這個就不用說了吧。
咱們調用以下命令生成咱們的升級包:
 
./build/tools/releasetools/ota_from_target_files /
  -m auto /
  -p out/host/linux-x86 /
  -k build/target/product/security/testkey -n /
out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}
再看內容
ota_from_target_files爲python 腳本,因此若是懂 python, 會更順利一點。
文件有1000行。分析過程當中,咱們只是貼代碼片斷。 完整文件見:
build/tools/releasetools/ota_from_target_files (from Android 2.2)
 
入口:main
按照python慣例,單獨執行的代碼執行從__main__開始:
944 if __name__ == '__main__':945   try:946     main(sys.argv[1:])947   except common.ExternalError, e:948     print949     print "   ERROR: %s" % (e,)950     print951     sys.exit(1)
 它調用 main 函數:
 
844 def main(argv):  
845   
846   def option_handler(o, a):  
847     if o in ("-b", "--board_config"):  
848       pass   # deprecated  
849     elif o in ("-k", "--package_key"):  
850       OPTIONS.package_key = a  
851     elif o in ("-i", "--incremental_from"):  
852       OPTIONS.incremental_source = a  
853     elif o in ("-w", "--wipe_user_data"):  
854       OPTIONS.wipe_user_data = True  
855     elif o in ("-n", "--no_prereq"):  
856       OPTIONS.omit_prereq = True  
857     elif o in ("-e", "--extra_script"):  
858       OPTIONS.extra_script = a  
859     elif o in ("-m", "--script_mode"):  
860       OPTIONS.script_mode = a  
861     elif o in ("--worker_threads"):  
862       OPTIONS.worker_threads = int(a)  
863     else:  
864       return False  
865     return True  
866   
867   args = common.ParseOptions(argv, __doc__,  
868                              extra_opts="b:k:i:d:wne:m:",  
869                              extra_long_opts=["board_config=",  
870                                               "package_key=",  
871                                               "incremental_from=",  
872                                               "wipe_user_data",  
873                                               "no_prereq",  
874                                               "extra_script=",  
875                                               "script_mode=",  
876                                               "worker_threads="],  
877                              extra_option_handler=option_handler)  
878   
879   if len(args) != 2:  
880     common.Usage(__doc__)  
881     sys.exit(1)  
 
將用戶設定的 Option 存入 OPTIONS 變量中。它是一個Python Class, 咱們將其理解爲一個C Struct 便可。 883   if OPTIONS.script_mode not in ("amend", "edify", "auto"):884     raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,)) Script_mode 只能是amend/edify/auto之一, auto 目前是選擇二者都支持。能夠理解是爲了向前兼容,(早期 Android 使用 amend) 886   if OPTIONS.extra_script is not None:887     OPTIONS.extra_script = open(OPTIONS.extra_script).read() 讀入 額外腳本的內容。(若是有) 889   print "unzipping target target-files..."890   OPTIONS.input_tmp = common.UnzipTemp(args[0]) 解開輸入包。
892   if OPTIONS.device_specific is None:  
893     # look for the device-specific tools extension location in the input  
894     try:  
895       f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))  
896       ds = f.read().strip()  
897       f.close()  
898       if ds:  
899         ds = os.path.normpath(ds)  
900         print "using device-specific extensions in", ds  
901         OPTIONS.device_specific = ds  
902     except IOError, e:  
903       if e.errno == errno.ENOENT:  
904         # nothing specified in the file  
905         pass  
906       else:  
907         raise  
 處理 device-specific extensions, 沒用到。 909   common.LoadMaxSizes()910   if not OPTIONS.max_image_size:911     print912     print "  WARNING:  Failed to load max image sizes; will not enforce"913     print "  image size limits."914     print 讀入設定image大小的參數,沒用到。 916   OPTIONS.target_tmp = OPTIONS.input_tmp917   input_zip = zipfile.ZipFile(args[0], "r")918   if OPTIONS.package_key:919     temp_zip_file = tempfile.NamedTemporaryFile()920     output_zip = zipfile.ZipFile(temp_zip_file, "w",921                                  compression=zipfile.ZIP_DEFLATED)922   else:923     output_zip = zipfile.ZipFile(args[1], "w",924                  compression=zipfile.ZIP_DEFLATED) 設定輸出文件,若是要簽名(our case),則還須要一個臨時輸出文件。 926   if OPTIONS.incremental_source is None:927     WriteFullOTAPackage(input_zip, output_zip)928   else:929     print "unzipping source target-files..."930     OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)931     source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")932     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip) 根據參數,調用增量和非增量建立 ZIP 建立函數,咱們採用非增量模式。 934   output_zip.close()935   if OPTIONS.package_key:936     SignOutput(temp_zip_file.name, args[1])937     temp_zip_file.close()939   common.Cleanup()941   print "done."
 簽名(若是須要的話),處理完畢。
 
下面咱們看主要功能函數:WriteFullOTAPackage。


二.腳本ota_from_target_files(第二部分)  
2011-08-23 14:56:25|  分類: Android OTA升級 |  標籤: |字號大中小 訂閱


主功能:WriteFullOTAPackage
 345 def WriteFullOTAPackage(input_zip, output_zip):
346   if OPTIONS.script_mode == "auto":347     script = both_generator.BothGenerator(2)348   elif OPTIONS.script_mode == "amend":349     script = amend_generator.AmendGenerator()350   else:351     # TODO: how to determine this?  We don't know what version it will352     # be installed on top of.  For now, we expect the API just won't353     # change very often.354     script = edify_generator.EdifyGenerator(2) 首先,咱們得到腳本生成器,他們的實現見腳本:edify_generator.py 等。 356   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),357               "pre-device": GetBuildProp("ro.product.device", input_zip),358               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),359               } 得到一些環境變量,來自android 環境變量。 Google 一下即知其義。 361   device_specific = common.DeviceSpecificParams(362       input_zip=input_zip,363       input_version=GetRecoveryAPIVersion(input_zip),364       output_zip=output_zip,365       script=script,366       input_tmp=OPTIONS.input_tmp,367       metadata=metadata) 設備相關參數,不深究。 369   if not OPTIONS.omit_prereq:370     ts = GetBuildProp("ro.build.date.utc", input_zip)371     script.AssertOlderBuild(ts) 若是須要,在腳本中增長一個Assert語句,要求update zip包只能用於升級老的系統。 373   AppendAssertions(script, input_zip) 若是須要,在腳本中增長一個Assert語句,要求update zip包只能用於同一設備,即目標設備的 ro.product.device必須跟update.zip中的相同。
 


374   device_specific.FullOTA_Assertions() Callback, 用於調用設備相關代碼。調用時機爲即將開始升級。相似還有:FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。 376   script.ShowProgress(0.5, 0) 在升級腳本中加入顯示進度的語句, 參數一表示底下的操做(到下一條同類語句或者到末尾)將暫用的時間在整體時間的比例。參數二用於控制顯示的速度。好比,50 則表示底下的操做估計50秒內完成,要求進度條顯示線程用50秒顯示這一部分的進度。0 表示不自動更新,手動控制(使用SetProgress) 378   if OPTIONS.wipe_user_data:379     script.FormatPartition("userdata") 若是須要,在腳本中增長語句,擦除 userdata 分區。 381   script.FormatPartition("system") 在腳本中增長語句,擦除 system分區。 382   script.Mount("MTD", "system", "/system") 在腳本中增長語句,安裝 system分區到 /system 目錄。383   script.UnpackPackageDir("recovery", "/system")384   script.UnpackPackageDir("system", "/system")在腳本中增長語句,將recovery以及system中的內容拷貝到 /system目錄。其中recovery 目錄包含一個patch 以及應用該patch 的腳本。 386   symlinks = CopySystemFiles(input_zip, output_zip)387   script.MakeSymlinks(symlinks) 386 行從輸入 ZIP 包 /system 拷貝文件到輸出 ZIP 包 /system。因爲這個過程不支持連接文件,因此它將這些文件返回。 於 387 行作繼續處理。該行創建這些link 文件。全部的link文件都指向 toolbox 389   boot_img = File("boot.img", common.BuildBootableImage(390       os.path.join(OPTIONS.input_tmp, "BOOT")))391   recovery_img = File("recovery.img", common.BuildBootableImage(392       os.path.join(OPTIONS.input_tmp, "RECOVERY")))393   MakeRecoveryPatch(output_zip, recovery_img, boot_img) 這個複雜,MakeRecoveryPatch 作了兩件事:1.在輸出 ZIP包中生成一個patch: recovery/recovery-from-boot.p(boot.img和 recovery.img的patch), 它最後會位於:system/recovery-from-boot.p2.在輸出 ZIP包中生成一個腳本:recovery/etc/install-recovery.sh , 它最後會位於system/etc/install-recovery.sh.該腳本的內容爲:#!/system/bin/shif ! applypatch -c MTD:recovery:2048:6a167ffb86a4a16cb993473ce0726a3067163fc1; then  log -t recovery "Installing new recovery image"  applypatch MTD:boot:2324480:9a72a20a9c2f958ba586a840ed773cf8f5244183 MTD:recovery f6c2a70c5f2b02b6a49c9f5c5507a45a42e2d389 2564096 9a72a20a9c2f958ba586a840ed773cf8f5244183:/system/recovery-from-boot.pelse  log -t recovery "Recovery image already installed"fi 395   Item.GetMetadata(input_zip) 從 META/filesystem_config.txt 中得到 system 目錄下的各文件權限信息。 396   Item.Get("system").SetPermissions(script) 在腳本中增長語句,設置 system 目錄下文件的權限及屬主等。 398   common.CheckSize(boot_img.data, "boot.img") 檢查 boot.img 文件大小是否超標. 399   common.ZipWriteStr(output_zip, "boot.img", boot_img.data) 將boot.img 放到輸出 ZIP 包中。 400   script.ShowProgress(0.2, 0)402   script.ShowProgress(0.2, 10) 更行進度條。 403   script.WriteRawImage("boot", "boot.img") 在腳本中增長語句,將 boot.img 寫到 boot 分區。 405   script.ShowProgress(0.1, 0) 更行進度條。 406   device_specific.FullOTA_InstallEnd() Callback, 同前。 408   if OPTIONS.extra_script is not None:409     script.AppendExtra(OPTIONS.extra_script) 若是有額外腳本,加入。 411   script.UnmountAll() 在腳本中增長語句,umount 全部分區。 412   script.AddToZip(input_zip, output_zip) 1)將前面生成的腳本輸出到:META-INF/com/google/android/updater-script (對於edify) 
assert(getprop("ro.product.device") == "thedevicename" ||  
  
       getprop("ro.build.product") == "theproductname");  
  
show_progress(0.500000, 0);  
  
format("MTD", "system");  
  
mount("MTD", "system", "/system");  
  
package_extract_dir("recovery", "/system");  
  
package_extract_dir("system", "/system");  
  
symlink("dumpstate", "/system/bin/dumpcrash");  
  
symlink("toolbox", "/system/bin/cat", "/system/bin/chmod",  
  
        "/system/bin/chown", "/system/bin/cmp", "/system/bin/date",  
  
        "/system/bin/dd", "/system/bin/df", "/system/bin/dmesg",  
  
        "/system/bin/fb2bmp", "/system/bin/getevent", "/system/bin/getprop",  
  
        "/system/bin/hd", "/system/bin/id", "/system/bin/ifconfig",  
  
        "/system/bin/iftop", "/system/bin/insmod", "/system/bin/ioctl",  
  
        "/system/bin/kill", "/system/bin/ln", "/system/bin/log",  
  
        "/system/bin/ls", "/system/bin/lsmod", "/system/bin/mkdir",  
  
        "/system/bin/mount", "/system/bin/mv", "/system/bin/netstat",  
  
        "/system/bin/newfs_msdos", "/system/bin/notify", "/system/bin/printenv",  
  
        "/system/bin/ps", "/system/bin/reboot", "/system/bin/renice",  
  
        "/system/bin/rm", "/system/bin/rmdir", "/system/bin/rmmod",  
  
        "/system/bin/route", "/system/bin/schedtop", "/system/bin/sendevent",  
  
        "/system/bin/setconsole", "/system/bin/setprop", "/system/bin/sleep",  
  
        "/system/bin/smd", "/system/bin/start", "/system/bin/stop",  
  
        "/system/bin/sync", "/system/bin/top", "/system/bin/umount",  
  
        "/system/bin/vmstat", "/system/bin/watchprops",  
  
        "/system/bin/wipe");  
  
set_perm_recursive(0, 0, 0755, 0644, "/system");  
  
set_perm_recursive(0, 2000, 0755, 0755, "/system/bin");  
  
set_perm(0, 3003, 02755, "/system/bin/netcfg");  
  
set_perm(0, 3004, 02755, "/system/bin/ping");  
  
set_perm_recursive(1002, 1002, 0755, 0440, "/system/etc/bluez");  
  
set_perm(0, 0, 0755, "/system/etc/bluez");  
  
set_perm(1002, 1002, 0440, "/system/etc/dbus.conf");  
  
set_perm(1014, 2000, 0550, "/system/etc/dhcpcd/dhcpcd-run-hooks");  
  
set_perm(0, 2000, 0550, "/system/etc/init.goldfish.sh");  
  
set_perm(0, 0, 0544, "/system/etc/install-recovery.sh");  
  
set_perm_recursive(0, 0, 0755, 0555, "/system/etc/ppp");  
  
set_perm_recursive(0, 2000, 0755, 0755, "/system/xbin");  
  
show_progress(0.200000, 0);  
  
show_progress(0.200000, 10);  
  
assert(package_extract_file("boot.img", "/tmp/boot.img"),  
  
       write_raw_image("/tmp/boot.img", "boot"),  
  
       delete("/tmp/boot.img"));  
  
show_progress(0.100000, 0);  
  
unmount("/system");  
 2)將升級程序:OTA/bin/updater 從輸入ZIP包中拷貝到輸出ZIP包中的:META-INF/com/google/android/update-binary 413   WriteMetadata(metadata, output_zip)
將前面獲取的metadata 寫入輸出包的文件中: META-INF/com/android/metadata
至此,咱們就獲得了一個update.zip包。能夠開始升級了。
思考
1) 雖然提供了更新recovery分區的機制,可是沒有看到觸發該更新的語句。因此,缺省的狀況是不會更新recovery分區的。大概是爲了安全的緣由吧。 可是,有時確實須要更新recovery 分區(好比,設備的硬件配置、分區表等發生改變),這該如何操做呢?


Android OTA 升級之三:生成recovery.img
做者: 宋立新


Email:zjujoe@yahoo.com


前言
       獲得了ota升級包後,咱們就能夠用它來升級系統了。Android 手機開機後,會先運行 bootloader。 Bootloader 會根據某些斷定條件(好比按某個特殊鍵)決定是否進入 recovery 模式。Recovery 模式會裝載 recovery 分區, 該分區包含recovery.img。recovery.img 包含了標準內核(和boot.img中的內核相同)以及recovery 根文件系統。下面咱們看一下它是如何生成的。
 
recovery.img生成過程 L630-L637 依賴關係
(From: build/core/Makefile)
 
630 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) /631                 $(INSTALLED_RAMDISK_TARGET) /632                 $(INSTALLED_BOOTIMAGE_TARGET) /633                 $(recovery_binary) /634                 $(recovery_initrc) $(recovery_kernel) /635                 $(INSTALLED_2NDBOOTLOADER_TARGET) /636                 $(recovery_build_prop) $(recovery_resource_deps) /637                 $(RECOVERY_INSTALL_OTA_KEYS)
 
INSTALLED_RECOVERYIMAGE_TARGET 爲咱們的編譯目標:
584 INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
 
它依賴不少其它目標:
1.MKBOOTFS, MINIGZIP, MKBOOTIMG,PC端工具軟件:(From build/core/config.mk)265 MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX)266 MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX)267 MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)
 
2.INSTALLED_RAMDISK_TARGET,標準根文件系統 ramdisk.img:
326 BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img328 # We just build this directly to the install location.329 INSTALLED_RAMDISK_TARGET := $(BUILT_RAMDISK_TARGET) 3.INSTALLED_BOOTIMAGE_TARGET, 即boot.img,標準內核及標準根文件系統:362 INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
 
4. recovery_binary, Recovery可執行程序,源碼位於:bootable/recovery
590 recovery_binary := $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery
 
5. recovery_initrc,recovery模式的init.rc, 位於 bootable/recovery/etc/init.rc
586 recovery_initrc := $(call include-path-for, recovery)/etc/init.rc
 
6. recovery_kernel, recovery 模式的kernel, 同標準內核
587 recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system
 
7.INSTALLED_2NDBOOTLOADER_TARGET,咱們不用。
 
8. recovery_build_prop, recovery 模式的build.prop, 同標準模式。589 recovery_build_prop := $(INSTALLED_BUILD_PROP_TARGET)
 
9. recovery_resource_deps, recovery 模式使用的res, 位於:recovery/custom/{product_name}/res, 以及設備自定義部分(咱們沒用到)
591 recovery_resources_common := $(call include-path-for, recovery)/custom/$(TARGET_PRODUCT)/res592 recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res))593 recovery_resource_deps := $(shell find $(recovery_resources_common)594   $(recovery_resources_private) -type f) 
10.  RECOVERY_INSTALL_OTA_KEYS, ota 密鑰:
618 # Generate a file containing the keys that will be read by the619 # recovery binary.620 RECOVERY_INSTALL_OTA_KEYS := /621         $(call intermediates-dir-for,PACKAGING,ota_keys)/keys L638-L655 準備內容638         @echo ----- Making recovery image ------639         rm -rf $(TARGET_RECOVERY_OUT)640         mkdir -p $(TARGET_RECOVERY_OUT)641         mkdir -p $(TARGET_RECOVERY_ROOT_OUT)642         mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc643         mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp
 
準備recovery目錄:out/target/product/{product_name}/recovery 及其子目錄:
./root
./root/etc
./root/tmp
 
644         echo Copying baseline ramdisk...645         cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT)646         echo Modifying ramdisk contents...647         rm -rf $(TARGET_RECOVERY_ROOT_OUT)/res
 
從標準根文件系統拷貝全部文件, 刪除其res 目錄。
 648         cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/649         cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/ 拷貝recovery 模式的核心文件 init.rc 及 recovery 650         cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/651         $(foreach item,$(recovery_resources_private), /652           cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/)653         cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 拷貝資源文件及密鑰文件。  654         cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) /655                 > $(TARGET_RECOVERY_ROOT_OUT)/default.prop 生成屬性文件 default.prop, 它包含了標準根文件系統的default.prop(out/target/product/{product_name}/root/default.prop)以及system分區的build.prop (out/target/product/{product_name}/system/build.prop)  L656-L661 最終生成recovery.img656         $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk) 壓縮recovery根文件系統 657         build/quacomm/mkimage $(PRODUCT_OUT)/ramdisk-recovery.img RECOVERY > $(PRODUCT_OUT)/ramdisk_recovery.img 加一個標識頭(RECOVERY) 658         mv $(PRODUCT_OUT)/ramdisk_recovery.img $(PRODUCT_OUT)/ramdisk-recovery.img659         $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $@660         @echo ----- Made recovery image -------- $@661         $(hide) $(call assert-max-image-size,$@,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw)   和內核一塊兒,生成recovery.img   附:Recovery 根文件系統目錄結構   $ tree . ├── advanced_meta_init.rc ├── data ├── default.prop ├── dev ├── etc ├── init ├── init.factory.rc ├── init.goldfish.rc ├── init.quacomm.rc ├── init.rc ├── meta_init.rc ├── proc ├── res │   ├── images │   │   ├── icon_error.png │   │   ├── icon_installing.png │   │   ├── indeterminate1.png │   │   ├── indeterminate2.png │   │   ├── indeterminate3.png │   │   ├── indeterminate4.png │   │   ├── indeterminate5.png │   │   ├── indeterminate6.png │   │   ├── progress_empty.png │   │   └── progress_fill.png │   └── keys ├── sbin │   ├── adbd │   ├── advanced_meta_init │   ├── meta_init │   ├── meta_tst │   └── recovery ├── sys ├── system └── tmp Android OTA 升級之四:進入根文件系統 做者: 宋立新 Email:zjujoe@yahoo.com 前言        從bootloader 進入Recovery 模式後,首先也是運行Linux內核,該內核跟普通模式沒有區別(減輕了BSP開發者的任務)。區別從執行文件系統開始。 Recovery 模式的細節就隱藏在其根文件系統中。        下面,咱們就看看進入Recovery 根文件系統都幹些啥。   init.rc        和正常啓動同樣,內核進入文件系統會執行/init, init 的配置文件就是 /init.rc, 前面文章講過,這個文件來自:bootable/recovery/etc/init.rc,下面,咱們看看它的內容。     1  2 on init  3     export PATH /sbin  4     export ANDROID_ROOT /system  5     export ANDROID_DATA /data  6     export EXTERNAL_STORAGE /sdcard  7  8     symlink /system/etc /etc  9 10     mkdir /sdcard11     mkdir /system12     mkdir /data13     mkdir /cache14     mount /tmp /tmp tmpfs15 16 on boot17 18     ifup lo19     hostname localhost20     domainname localdomain21 22     class_start default23 24 25 service recovery /sbin/recovery26 27 service adbd /sbin/adbd recovery28     disabled29 30 on property:persist.service.adb.enable=131     start adbd32 33 on property:persist.service.adb.enable=034     stop adbd   能夠看到,它很很是簡單: 1)   設置幾個環境變量。備用。 2)   創建 etc 連接。 3)   造幾個目錄。備用。 4)   Mount /tmp 目錄爲內存文件系統 tmpfs,後面會用到。 5)   Trival 設置,沒必要關心。 6)   啓動 recovery主程序。 7)   若是是eng模式(此時persist.service.adb.enable),啓動adb 固然,init主程序還會裝載屬性配置文件 /default.prop, 它包含了不少系統屬性設置,好比,ro.build.*, 等等。   很明顯,這裏最重要的就是recovery主程序,下面,咱們分析它。 先看一段註釋 Recovery.c 中,做者寫了一段註釋,對咱們理解recovery的實現頗有幫助,下面看一下:(我就不翻譯了) 89 /*90  * The recovery tool communicates with the main system through /cache files.91  *   /cache/recovery/command - INPUT - command line for tool, one arg per line92  *   /cache/recovery/log - OUTPUT - combined log file from recovery run(s)93  *   /cache/recovery/intent - OUTPUT - intent that was passed in94  *95  * The arguments which may be supplied in the recovery.command file:96  *   --send_intent=anystring - write the text out to recovery.intent97  *   --update_package=root:path - verify install an OTA package file98  *   --wipe_data - erase user data (and cache), then reboot99  *   --wipe_cache - wipe cache (but not user data), then reboot100  *   --set_encrypted_filesystem=on|off - enables / diasables encrypted fs101  *102  * After completing, we remove /cache/recovery/command and reboot.103  * Arguments may also be supplied in the bootloader control block (BCB).104  * These important scenarios must be safely restartable at any point:105  *106  * FACTORY RESET107  * 1. user selects "factory reset"108  * 2. main system writes "--wipe_data" to /cache/recovery/command109  * 3. main system reboots into recovery110  * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data"111  *    -- after this, rebooting will restart the erase --112  * 5. erase_root() reformats /data113  * 6. erase_root() reformats /cache114  * 7. finish_recovery() erases BCB115  *    -- after this, rebooting will restart the main system --116  * 8. main() calls reboot() to boot main system117  *118  * OTA INSTALL119  * 1. main system downloads OTA package to /cache/some-filename.zip120  * 2. main system writes "--update_package=CACHE:some-filename.zip"121  * 3. main system reboots into recovery122  * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..."123  *    -- after this, rebooting will attempt to reinstall the update --124  * 5. install_package() attempts to install the update125  *    NOTE: the package install must itself be restartable from any point126  * 6. finish_recovery() erases BCB127  *    -- after this, rebooting will (try to) restart the main system --128  * 7. ** if install failed **129  *    7a. prompt_and_wait() shows an error icon and waits for the user130  *    7b; the user reboots (pulling the battery, etc) into the main system131  * 8. main() calls maybe_install_firmware_update()132  *    ** if the update contained radio/hboot firmware **:133  *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"134  *        -- after this, rebooting will reformat cache & restart main system --135  *    8b. m_i_f_u() writes firmware image into raw cache partition136  *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"137  *        -- after this, rebooting will attempt to reinstall firmware --138  *    8d. bootloader tries to flash firmware139  *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")140  *        -- after this, rebooting will reformat cache & restart main system --141  *    8f. erase_root() reformats /cache142  *    8g. finish_recovery() erases BCB143  *        -- after this, rebooting will (try to) restart the main system --144  * 9. main() calls reboot() to boot main system145  *146  * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE147  * 1. user selects "enable encrypted file systems"148  * 2. main system writes "--set_encrypted_filesystem=on|off" to149  *    /cache/recovery/command150  * 3. main system reboots into recovery151  * 4. get_args() writes BCB with "boot-recovery" and152  *    "--set_encrypted_filesystems=on|off"153  *    -- after this, rebooting will restart the transition --154  * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data155  *    Settings include: property to specify the Encrypted FS istatus and156  *    FS encryption key if enabled (not yet implemented)157  * 6. erase_root() reformats /data158  * 7. erase_root() reformats /cache159  * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data160  *    Settings include: property to specify the Encrypted FS status and161  *    FS encryption key if enabled (not yet implemented)162  * 9. finish_recovery() erases BCB163  *    -- after this, rebooting will restart the main system --164  * 10. main() calls reboot() to boot main system165  */   recovery 主程序559 int560 main(int argc, char **argv)561 {562     time_t start = time(NULL);563564     // If these fail, there's not really anywhere to complain...565     freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL);566     freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL);567     fprintf(stderr, "Starting recovery on %s", ctime(&start));568  將標準輸出和標準錯誤輸出重定位到"/tmp/recovery.log",若是是eng模式,就能夠經過adb pull /tmp/recovery.log, 看到當前的log信息,這爲咱們提供了有效的調試手段。後面還會看到,recovery模式運行完畢後,會將其拷貝到cache分區,以便後續分析。  569     ui_init(); Recovery 使用了一個簡單的基於framebuffer的ui系統,叫miniui,這裏,進行了簡單的初始化(主要是圖形部分以及事件部分),並啓動了一個 event 線程用於響應用戶按鍵。 570     get_args(&argc, &argv); 從misc 分區以及 CACHE:recovery/command 文件中讀入參數,寫入到argc, argv ,而且,若是有必要,回寫入misc分區。這樣,若是recovery沒有操做成功(好比,升級尚未結束,就拔電池),系統會一直進入recovery模式。提醒用戶繼續升級,直到成功。  572     int previous_runs = 0;573     const char *send_intent = NULL;574     const char *update_package = NULL;575     int wipe_data = 0, wipe_cache = 0;576577     int arg;578     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {579         switch (arg) {580         case 'p': previous_runs = atoi(optarg); break;581         case 's': send_intent = optarg; break;582         case 'u': update_package = optarg; break;583         case 'w': wipe_data = wipe_cache = 1; break;584         case 'c': wipe_cache = 1; break;585         case '?':586             LOGE("Invalid command argument/n");587             continue;588         }589     }590解析參數,p: previous_runs沒有用到,其它含義見前面註釋。 591     device_recovery_start(); 這個函數沒幹什麼。看名字,它給設備製造商提供了一個調用機會,可寫入設備相關初始化代碼。592593     fprintf(stderr, "Command:");594     for (arg = 0; arg < argc; arg++) {595         fprintf(stderr, " /"%s/"", argv[arg]);596     }597     fprintf(stderr, "/n/n");598打印出命令,好比,正常啓動進入recovery模式,會打印:Command: "/sbin/recovery"599     property_list(print_property, NULL);600     fprintf(stderr, "/n");601打印出全部的系統屬性(from default.prop)到log文件。 602     int status = INSTALL_SUCCESS;603604     if (update_package != NULL) {605         status = install_package(update_package);606         if (status != INSTALL_SUCCESS) ui_print("Installation aborted./n");607     } else if (wipe_data) {608         if (device_wipe_data()) status = INSTALL_ERROR;609         if (erase_root("DATA:")) status = INSTALL_ERROR;610         if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;611         if (status != INSTALL_SUCCESS) ui_print("Data wipe failed./n");612     } else if (wipe_cache) {613         if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR;614         if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed./n");615     } else {616         status = INSTALL_ERROR;  // No command specified617     } 根據用戶提供參數,調用各項功能,好比,安裝一個升級包,擦除cache分區, 擦除user data分區,install_package比較複雜,後面專門分析,其它都很簡單。忽略。 618619     if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR);622     if (status != INSTALL_SUCCESS) prompt_and_wait(); 若是前面已經作了某項操做而且成功,則進入重啓流程。不然,等待用戶選擇具體操做。而用戶可選操做爲: reboot, 安裝update.zip,除cache分區, 擦除user data分區,如前所述,只有安裝package 比較複雜,其它簡單。 623624     // Otherwise, get ready to boot the main system...625     finish_recovery(send_intent); 它的功能以下:1)將前面定義的intent字符串寫入(若是有的話):CACHE:recovery/command2)將 /tmp/recovery.log 複製到 "CACHE:recovery/log";3)清空 misc 分區,這樣重啓就不會進入recovery模式4)刪除command 文件:CACHE:recovery/command; 626     ui_print("Rebooting.../n");627     sync();628     reboot(RB_AUTOBOOT);629     return EXIT_SUCCESS;630 }  重啓。 下面咱們分析核心函數 install_package   install_package289 int290 install_package(const char *root_path)291 {292     ui_set_background(BACKGROUND_ICON_INSTALLING);294     ui_print("Finding update package.../n");295     LOGI("Finding update package.../n");296     ui_show_indeterminate_progress();297     LOGI("Update location: %s/n", root_path);298更新 UI 顯示299     if (ensure_root_path_mounted(root_path) != 0) {300         LOGE("Can't mount %s/n", root_path);301         reset_mark_block();302         return INSTALL_CORRUPT;303     }304 確保升級包所在分區已經mount,一般爲 cache 分區或者 SD 分區 305     char path[PATH_MAX] = "";306     if (translate_root_path(root_path, path, sizeof(path)) == NULL) {307         LOGE("Bad path %s/n", root_path);308         reset_mark_block();309         return INSTALL_CORRUPT;310     } 將根分區轉化爲具體分區信息.這些信息來自:全局數組:g_roots 313     ui_print("Opening update package.../n");314     LOGI("Opening update package.../n");315     LOGI("Update file path: %s/n", path);316317     int numKeys;318     RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys);319     if (loadedKeys == NULL) {320         LOGE("Failed to load keys/n");321         reset_mark_block();322         return INSTALL_CORRUPT;323     }324     LOGI("%d key(s) loaded from %s/n", numKeys, PUBLIC_KEYS_FILE); 從/res/keys中裝載公鑰。 326     // Give verification half the progress bar...328     ui_print("Verifying update package.../n");329     LOGI("Verifying update package.../n");330     ui_show_progress(331             VERIFICATION_PROGRESS_FRACTION,332             VERIFICATION_PROGRESS_TIME);333334     int err;335     err = verify_file(path, loadedKeys, numKeys);336     free(loadedKeys);337     LOGI("verify_file returned %d/n", err);338     if (err != VERIFY_SUCCESS) {339         LOGE("signature verification failed/n");340         reset_mark_block();341         return INSTALL_CORRUPT;342     } 根據公鑰驗證升級包verify_file的註釋說的很明白:       // Look for an RSA signature embedded in the .ZIP file comment given       // the path to the zip.  Verify it matches one of the given public       // keys. 344     /* Try to open the package.345      */346     ZipArchive zip;347     err = mzOpenZipArchive(path, &zip);348     if (err != 0) {349         LOGE("Can't open %s/n(%s)/n", path, err != -1 ? strerror(err) : "bad");350         reset_mark_block();351         return INSTALL_CORRUPT;352     } 打開升級包,將相關信息存到ZuoArchive數據機構中,便於後面處理。 354     /* Verify and install the contents of the package.355      */356     int status = handle_update_package(path, &zip); 處理函數,咱們後面繼續分析。 357     mzCloseZipArchive(&zip);358     return status;359 } 關閉zip包,結束處理。 handle_update_package204 static int205 handle_update_package(const char *path, ZipArchive *zip)206 {207     // Update should take the rest of the progress bar.208     ui_print("Installing update.../n");209210     int result = try_update_binary(path, zip);211     register_package_root(NULL, NULL);  // Unregister package root212     return result;213 }  它主要調用函數try_update_binary完成功能。 try_update_binary 84 // If the package contains an update binary, extract it and run it.  85 static int  86 try_update_binary(const char *path, ZipArchive *zip) {  87     const ZipEntry* binary_entry =  88             mzFindZipEntry(zip, ASSUMED_UPDATE_BINARY_NAME);  89     if (binary_entry == NULL) {  90         return INSTALL_CORRUPT;  91     }  92  93     char* binary = "/tmp/update_binary";  94     unlink(binary);  95     int fd = creat(binary, 0755);  96     if (fd < 0) {  97         LOGE("Can't make %s/n", binary);  98         return 1;  99     } 100     bool ok = mzExtractZipEntryToFile(zip, binary_entry, fd); 101     close(fd); 102 103     if (!ok) { 104         LOGE("Can't copy %s/n", ASSUMED_UPDATE_BINARY_NAME); 105         return 1; 106     }  將升級包內文件META-INF/com/google/android/update-binary 複製爲/tmp/update_binary   108     int pipefd[2]; 109     pipe(pipefd); 110 111     // When executing the update binary contained in the package, the 112     // arguments passed are: 113     // 114     //   - the version number for this interface 115     // 116     //   - an fd to which the program can write in order to update the 117     //     progress bar.  The program can write single-line commands: 118     // 119     //        progress <frac> <secs> 120     //            fill up the next <frac> part of of the progress bar 121     //            over <secs> seconds.  If <secs> is zero, use 122     //            set_progress commands to manually control the 123     //            progress of this segment of the bar 124     // 125     //        set_progress <frac> 126     //            <frac> should be between 0.0 and 1.0; sets the 127     //            progress bar within the segment defined by the most 128     //            recent progress command. 129     // 130     //        firmware <"hboot"|"radio"> <filename> 131     //            arrange to install the contents of <filename> in the 132     //            given partition on reboot. 133     // 134     //            (API v2: <filename> may start with "PACKAGE:" to 135     //            indicate taking a file from the OTA package.) 136     // 137     //            (API v3: this command no longer exists.) 138     // 139     //        ui_print <string> 140     //            display <string> on the screen. 141     // 142     //   - the name of the package zip file. 143     // 144   注意看這段註釋,它解釋瞭如下代碼的行爲。結合代碼,可知: 1)  將會建立新的進程,執行:/tmp/update_binary 2)  同時,會給該進程傳入一些參數,其中最重要的就是一個管道fd,供新進程與原進程通訊。 3)  新進程誕生後,原進程就變成了一個服務進程,它提供若干UI更新服務: a)   progress b)   set_progress c)   ui_print 這樣,新進程就能夠經過老進程的UI系統完成顯示任務。而其餘功能就靠它本身了。   145     char** args = malloc(sizeof(char*) * 5); 146     args[0] = binary; 147     args[1] = EXPAND(RECOVERY_API_VERSION);   // defined in Android.mk 148     args[2] = malloc(10); 149     sprintf(args[2], "%d", pipefd[1]); 150     args[3] = (char*)path; 151     args[4] = NULL; 152 153     pid_t pid = fork(); 154     if (pid == 0) { 155         close(pipefd[0]); 156         execv(binary, args); 157         fprintf(stderr, "E:Can't run %s (%s)/n", binary, strerror(errno)); 158         _exit(-1); 159     } 160     close(pipefd[1]); 161 162     char buffer[1024]; 163     FILE* from_child = fdopen(pipefd[0], "r"); 164     while (fgets(buffer, sizeof(buffer), from_child) != NULL) { 165         char* command = strtok(buffer, " /n"); 166         if (command == NULL) { 167             continue; 168         } else if (strcmp(command, "progress") == 0) { 169             char* fraction_s = strtok(NULL, " /n"); 170             char* seconds_s = strtok(NULL, " /n"); 171 172             float fraction = strtof(fraction_s, NULL); 173             int seconds = strtol(seconds_s, NULL, 10); 174 175             ui_show_progress(fraction * (1-VERIFICATION_PROGRESS_FRACTION), 176                              seconds); 177         } else if (strcmp(command, "set_progress") == 0) { 178             char* fraction_s = strtok(NULL, " /n"); 179             float fraction = strtof(fraction_s, NULL); 180             ui_set_progress(fraction); 181         } else if (strcmp(command, "ui_print") == 0) { 182             char* str = strtok(NULL, "/n"); 183             if (str) { 184                 ui_print(str); 185             } else { 186                 ui_print("/n"); 187             } 188         } else { 189             LOGE("unknown command [%s]/n", command); 190         } 191     } 192     fclose(from_child); 193 194     int status; 195     waitpid(pid, &status, 0); 196     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { 197         LOGE("Error in %s/n(Status %d)/n", path, WEXITSTATUS(status)); 198         return INSTALL_ERROR; 199     } 200 201     return INSTALL_SUCCESS; 202 }   這樣,咱們又回到了升級包中的文件:META-INF/com/google/android/update-binary,下回繼續研究。 Android OTA 升級之五:updater 做者: 宋立新 Email:zjujoe@yahoo.com 前言        能夠說,前面分析的OTA升級的各部分代碼都是在搭一個舞臺,而主角如今終於登場,它就是updater. Google的代碼架構設計很是好,各部分儘可能鬆耦合。前面介紹升級腳本時,可知有兩種類型的腳本,amend & edify. 他們各自對應一個updater. 這裏,咱們主要關注新的edify的updater.        Updater能夠做爲學習解釋器/編譯器的同窗一個很好的實例,可是咱們只關心產品化相關的內容,因此並不去深究lex/yacc相關的東西。   入口函數 main (from: bootable/recovery/updater/updater.c) 62 // Where in the package we expect to find the edify script to execute.  63 // (Note it's "updateR-script", not the older "update-script".)  64 #define SCRIPT_NAME "META-INF/com/google/android/updater-script"  65   這裏定義腳本的位置,註釋說明本updater支持edify格式的腳本。    66 int main(int argc, char** argv) {  67     // Various things log information to stdout or stderr more or less  68     // at random.  The log file makes more sense if buffering is  69     // turned off so things appear in the right order.  70     setbuf(stdout, NULL);  71     setbuf(stderr, NULL);  72  73     if (argc != 4) {  74         fprintf(stderr, "unexpected number of arguments (%d)/n", argc);  75         return 1;  76     }  77  78     char* version = argv[1];  79     if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||  80         version[1] != '/0') {  81         // We support version 1, 2, or 3.  82         fprintf(stderr, "wrong updater binary API; expected 1, 2, or 3; "  83                         "got %s/n",  84                 argv[1]);  85         return 2;  86     }  87 獲取 version 參數。  88     // Set up the pipe for sending commands back to the parent process.  89  90     int fd = atoi(argv[2]);  91     FILE* cmd_pipe = fdopen(fd, "wb");  92     setlinebuf(cmd_pipe);  93   獲取命令管道(用於圖形顯示等,見前篇)    94     // Extract the script from the package.  95  96     char* package_data = argv[3];  97     ZipArchive za;  98     int err;  99     err = mzOpenZipArchive(package_data, &za); 100     if (err != 0) { 101         fprintf(stderr, "failed to open package %s: %s/n", 102                 package_data, strerror(err)); 103         return 3; 104     } 105 106     const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME); 107     if (script_entry == NULL) { 108         fprintf(stderr, "failed to find %s in %s/n", SCRIPT_NAME, package_data); 109         return 4; 110     } 111 112     char* script = malloc(script_entry->uncompLen+1); 113     if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) { 114         fprintf(stderr, "failed to read script from package/n"); 115         return 5; 116     } 117     script[script_entry->uncompLen] = '/0'; 118   讀入腳本 META-INF/com/google/android/updater-script   119     // Configure edify's functions. 120 121     RegisterBuiltins(); 122     RegisterInstallFunctions(); 123     RegisterDeviceExtensions(); 124     FinishRegistration(); 125 註冊語句處理函數 126     // Parse the script. 127 128     Expr* root; 129     int error_count = 0; 130     yy_scan_string(script); 131     int error = yyparse(&root, &error_count); 132     if (error != 0 || error_count > 0) { 133         fprintf(stderr, "%d parse errors/n", error_count); 134         return 6; 135     } 136 調用yy* 庫函數解析腳本。 137     // Evaluate the parsed script. 138 139     UpdaterInfo updater_info; 140     updater_info.cmd_pipe = cmd_pipe; 141     updater_info.package_zip = &za; 142     updater_info.version = atoi(version); 143 144     State state; 145     state.cookie = &updater_info; 146     state.script = script; 147     state.errmsg = NULL; 148 149     char* result = Evaluate(&state, root); 150     if (result == NULL) { 151         if (state.errmsg == NULL) { 152             fprintf(stderr, "script aborted (no error message)/n"); 153             fprintf(cmd_pipe, "ui_print script aborted (no error message)/n"); 154         } else { 155             fprintf(stderr, "script aborted: %s/n", state.errmsg); 156             char* line = strtok(state.errmsg, "/n"); 157             while (line) { 158                 fprintf(cmd_pipe, "ui_print %s/n", line); 159                 line = strtok(NULL, "/n"); 160             } 161             fprintf(cmd_pipe, "ui_print/n"); 162         } 163         free(state.errmsg); 164         return 7; 165     } else { 166         fprintf(stderr, "script result was [%s]/n", result); 167         free(result); 168     } 解釋執行腳本。 核心函數是 Evaluate。它會調用其餘callback函數,而這些callback函數又會調用Evaluate去解析不一樣的腳本片斷。從而實現一個簡單的解釋器。 169 170     mzCloseZipArchive(&za); 171     free(script); 172 173     return 0; 174 }   還沒開始,就結束了。代碼很是簡單,由於細節隱藏在那些callback函數裏。咱們看一下。 RegisterBuiltins415 void RegisterBuiltins() {416     RegisterFunction("ifelse", IfElseFn);417     RegisterFunction("abort", AbortFn);418     RegisterFunction("assert", AssertFn);419     RegisterFunction("concat", ConcatFn);420     RegisterFunction("is_substring", SubstringFn);421     RegisterFunction("stdout", StdoutFn);422     RegisterFunction("sleep", SleepFn);423424     RegisterFunction("less_than_int", LessThanIntFn);425     RegisterFunction("greater_than_int", GreaterThanIntFn);426 } 這些語句控制執行流程。 RegisterInstallFunctions10361037 void RegisterInstallFunctions() {1038     RegisterFunction("mount", MountFn);1039     RegisterFunction("is_mounted", IsMountedFn);1040     RegisterFunction("unmount", UnmountFn);1041     RegisterFunction("format", FormatFn);1042     RegisterFunction("show_progress", ShowProgressFn);1043     RegisterFunction("set_progress", SetProgressFn);1044     RegisterFunction("delete", DeleteFn);1045     RegisterFunction("delete_recursive", DeleteFn);1046     RegisterFunction("package_extract_dir", PackageExtractDirFn);1047     RegisterFunction("package_extract_file", PackageExtractFileFn);1048     RegisterFunction("symlink", SymlinkFn);1049     RegisterFunction("set_perm", SetPermFn);1050     RegisterFunction("set_perm_recursive", SetPermFn);10511052     RegisterFunction("getprop", GetPropFn);1053     RegisterFunction("file_getprop", FileGetPropFn);1054     RegisterFunction("write_raw_image", WriteRawImageFn);10551056     RegisterFunction("apply_patch", ApplyPatchFn);1057     RegisterFunction("apply_patch_check", ApplyPatchCheckFn);1058     RegisterFunction("apply_patch_space", ApplyPatchSpaceFn);10591060     RegisterFunction("read_file", ReadFileFn);1061     RegisterFunction("sha1_check", Sha1CheckFn);10621063     RegisterFunction("ui_print", UIPrintFn);10641065     RegisterFunction("run_program", RunProgramFn);1066 } 這些語句執行各類功能。基本上,咱們只須要知道用法就能夠了。值得注意的是,run_program原語容許咱們去執行自定義程序,這應該足夠知足咱們的個性化需求了。   關閉
相關文章
相關標籤/搜索