作者: 宋立新
Email:[email protected]
前言OTA 升级是 Android 系统提供的标准软件升级方式。 它功能强大,提供了完全升级、增量升级模式,可以通过 SD 卡升级,也可以通过网络升级。
这里,我们先研究最简单的情况,通过 SD 卡进行完全升级。
如何执行升级就不多说了,网上有很多资料。(比如,介绍HTC手机如何升级)。我们感兴趣的是它是如何实现的,作为开发者,如何修改它以符合我们的定制化需求。
首先,我们研究一下 ota 升级包的编译过程。
Quick start首先编译出android, 然后执行:
make otapackage
即可获得:out/target/product/{product_name}/ {product_name}-ota-eng.{uid}.zip
将该文件改名为update.zip放到T卡根目录, 即可开始recovery模式下的 OTA 升级。
编译过程研究
主要分两步,第一步, 会准备一个包,其中包含升级需要的内容(原材料),比如,system 目录。
第二步,运行python 脚本 ./build/tools/releasetools/ota_from_target_files,以步骤一准备的ZIP包作为输入,最终生成需要的升级包。
步骤一
编译脚本如下:
(From: build/core/Makefile)
可见往里面添加了很多内容。
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)
核心是一个python脚本: ota_from_target_files, 它以前一步骤生成的ZIP包作为输入,生成可用于OTA升级的zip包。 具体内容我们后文继续分析。
Android OTA 升级之二:脚本 ota_from_target_files
作者: 宋立新
Email:[email protected]
前言前面介绍了ota package 的编译过程,其中最核心的部分就是一个 python 脚本:ota_from_target_files. 现在我们分析这个脚本。
先看一下帮助不带任何参数,先看一下它的帮助:
简单翻译一下:
-b 过时,不再使用。
-k 签名用的密钥
-i 生成增量OTA包时用于定义对比包
-w 是否清除 userdata 分区
-n 是否在升级时不检查时间戳,缺省情况下只能基于老的版本升级。
-e 定义额外运行的脚本
-m 定义采用的脚本格式,目前有两种,amend & edify, 其中amend为较老的格式。对应的,升级时会采用不同的解释器。缺省情况下,ota_from_target_files 会同时生成两个脚本。这提供了最大灵活性。
-p 定义脚本用到的一些可执行文件的路径
-s 定义额外运行的脚本的路径
-x 定义额外运行的脚本可能用到的键/值对
-v 老朋友,冗余模式,让脚本打印出执行的命令
-h 老朋友,这个就不用说了吧。
我们调用如下命令生成我们的升级包:
./build/tools/releasetools/ota_from_target_files /
-m auto /
-p out/host/linux-x86 /
-k build/target/product/security/testkey -n /
out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}
再看内容ota_from_target_files为python 脚本,所以如果懂 python, 会更顺利一点。
文件有1000行。分析过程中,我们只是贴代码片段。 完整文件见:
build/tools/releasetools/ota_from_target_files (from Android 2.2)
入口:main
按照python惯例,单独执行的代码执行从__main__开始:
944 if __name__ == '__main__': 945 try: 946 main(sys.argv[1:]) 947 except common.ExternalError, e: 948 print 949 print " ERROR: %s" % (e,) 950 print 951 sys.exit(1)它调用 main 函数:
签名(如果需要的话),处理完毕。
下面我们看主要功能函数:WriteFullOTAPackage。
2011-08-23 14:56:25| 分类: Android OTA升级 | 标签: |字号大中小 订阅
345 def WriteFullOTAPackage(input_zip, output_zip):
346 if OPTIONS.script_mode == "auto": 347 script = both_generator.BothGenerator(2) 348 elif OPTIONS.script_mode == "amend": 349 script = amend_generator.AmendGenerator() 350 else: 351 # TODO: how to determine this? We don't know what version it will 352 # be installed on top of. For now, we expect the API just won't 353 # change very often. 354 script = edify_generator.EdifyGenerator(2) 首先,我们获得脚本生成器,他们的实现见脚本:edify_generator.py 等。 356 metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip), 357 "pre-device": GetBuildProp("ro.product.device", input_zip), 358 "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip), 359 } 获得一些环境变量,来自android 环境变量。 Google 一下即知其义。 361 device_specific = common.DeviceSpecificParams( 362 input_zip=input_zip, 363 input_version=GetRecoveryAPIVersion(input_zip), 364 output_zip=output_zip, 365 script=script, 366 input_tmp=OPTIONS.input_tmp, 367 metadata=metadata) 设备相关参数,不深究。 369 if not OPTIONS.omit_prereq: 370 ts = GetBuildProp("ro.build.date.utc", input_zip) 371 script.AssertOlderBuild(ts) 如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于升级老的系统。 373 AppendAssertions(script, input_zip) 如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于同一设备,即目标设备的 ro.product.device必须跟update.zip中的相同。
374 device_specific.FullOTA_Assertions() Callback, 用于调用设备相关代码。调用时机为即将开始升级。类似还有:FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。 376 script.ShowProgress(0.5, 0) 在升级脚本中加入显示进度的语句, 参数一表示底下的操作(到下一条同类语句或者到末尾)将暂用的时间在总体时间的比例。参数二用于控制显示的速度。比如,50 则表示底下的操作估计50秒内完成,要求进度条显示线程用50秒显示这一部分的进度。0 表示不自动更新,手动控制(使用SetProgress) 378 if OPTIONS.wipe_user_data: 379 script.FormatPartition("userdata") 如果需要,在脚本中增加语句,擦除 userdata 分区。 381 script.FormatPartition("system") 在脚本中增加语句,擦除 system分区。 382 script.Mount("MTD", "system", "/system") 在脚本中增加语句,安装 system分区到 /system 目录。 383 script.UnpackPackageDir("recovery", "/system") 384 script.UnpackPackageDir("system", "/system")在脚本中增加语句,将recovery以及system中的内容拷贝到 /system目录。其中recovery 目录包含一个patch 以及应用该patch 的脚本。 386 symlinks = CopySystemFiles(input_zip, output_zip) 387 script.MakeSymlinks(symlinks) 386 行从输入 ZIP 包 /system 拷贝文件到输出 ZIP 包 /system。由于这个过程不支持链接文件,所以它将这些文件返回。 于 387 行做继续处理。该行建立这些link 文件。所有的link文件都指向 toolbox 389 boot_img = File("boot.img", common.BuildBootableImage( 390 os.path.join(OPTIONS.input_tmp, "BOOT"))) 391 recovery_img = File("recovery.img", common.BuildBootableImage( 392 os.path.join(OPTIONS.input_tmp, "RECOVERY"))) 393 MakeRecoveryPatch(output_zip, recovery_img, boot_img) 这个复杂,MakeRecoveryPatch 做了两件事:1.在输出 ZIP包中生成一个patch: recovery/recovery-from-boot.p(boot.img和 recovery.img的patch), 它最后会位于:system/recovery-from-boot.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)
将前面获取的metadata 写入输出包的文件中: META-INF/com/android/metadata
至此,我们就得到了一个update.zip包。可以开始升级了。
思考1) 虽然提供了更新recovery分区的机制,但是没有看到触发该更新的语句。所以,缺省的情况是不会更新recovery分区的。大概是为了安全的原因吧。 但是,有时确实需要更新recovery 分区(比如,设备的硬件配置、分区表等发生改变),这该如何操作呢?
Android OTA 升级之三:生成recovery.img
作者: 宋立新
Email:[email protected]
前言得到了ota升级包后,我们就可以用它来升级系统了。Android 手机开机后,会先运行 bootloader。 Bootloader 会根据某些判定条件(比如按某个特殊键)决定是否进入 recovery 模式。Recovery 模式会装载 recovery 分区, 该分区包含recovery.img。recovery.img 包含了标准内核(和boot.img中的内核相同)以及recovery 根文件系统。下面我们看一下它是如何生成的。
recovery.img生成过程 L630-L637 依赖关系
(From: build/core/Makefile)
630 $(INSTALLED_RECOVERYIMAGE_TARGET): $(MKBOOTFS) $(MKBOOTIMG) $(MINIGZIP) / 631 $(INSTALLED_RAMDISK_TARGET) / 632 $(INSTALLED_BOOTIMAGE_TARGET) / 633 $(recovery_binary) / 634 $(recovery_initrc) $(recovery_kernel) / 635 $(INSTALLED_2NDBOOTLOADER_TARGET) / 636 $(recovery_build_prop) $(recovery_resource_deps) / 637 $(RECOVERY_INSTALL_OTA_KEYS)
INSTALLED_RECOVERYIMAGE_TARGET 为我们的编译目标:
584 INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img
它依赖很多其它目标:
1.MKBOOTFS, MINIGZIP, MKBOOTIMG,PC端工具软件:(From build/core/config.mk) 265 MKBOOTFS := $(HOST_OUT_EXECUTABLES)/mkbootfs$(HOST_EXECUTABLE_SUFFIX) 266 MINIGZIP := $(HOST_OUT_EXECUTABLES)/minigzip$(HOST_EXECUTABLE_SUFFIX) 267 MKBOOTIMG := $(HOST_OUT_EXECUTABLES)/mkbootimg$(HOST_EXECUTABLE_SUFFIX)
2.INSTALLED_RAMDISK_TARGET,标准根文件系统 ramdisk.img:
326 BUILT_RAMDISK_TARGET := $(PRODUCT_OUT)/ramdisk.img 328 # We just build this directly to the install location. 329 INSTALLED_RAMDISK_TARGET := $(BUILT_RAMDISK_TARGET) 3.INSTALLED_BOOTIMAGE_TARGET, 即boot.img,标准内核及标准根文件系统: 362 INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img
4. recovery_binary, Recovery可执行程序,源码位于:bootable/recovery
590 recovery_binary := $(call intermediates-dir-for,EXECUTABLES,recovery)/recovery
5. recovery_initrc,recovery模式的init.rc, 位于 bootable/recovery/etc/init.rc
586 recovery_initrc := $(call include-path-for, recovery)/etc/init.rc
6. recovery_kernel, recovery 模式的kernel, 同标准内核
587 recovery_kernel := $(INSTALLED_KERNEL_TARGET) # same as a non-recovery system7.INSTALLED_2NDBOOTLOADER_TARGET,我们不用。
8. recovery_build_prop, recovery 模式的build.prop, 同标准模式。 589 recovery_build_prop := $(INSTALLED_BUILD_PROP_TARGET)
9. recovery_resource_deps, recovery 模式使用的res, 位于:recovery/custom/{product_name}/res, 以及设备自定义部分(我们没用到)
591 recovery_resources_common := $(call include-path-for, recovery)/custom/$(TARGET_PRODUCT)/res 592 recovery_resources_private := $(strip $(wildcard $(TARGET_DEVICE_DIR)/recovery/res)) 593 recovery_resource_deps := $(shell find $(recovery_resources_common) 594 $(recovery_resources_private) -type f)10. RECOVERY_INSTALL_OTA_KEYS, ota 密钥:
618 # Generate a file containing the keys that will be read by the 619 # recovery binary. 620 RECOVERY_INSTALL_OTA_KEYS := / 621 $(call intermediates-dir-for,PACKAGING,ota_keys)/keys L638-L655 准备内容 638 @echo ----- Making recovery image ------ 639 rm -rf $(TARGET_RECOVERY_OUT) 640 mkdir -p $(TARGET_RECOVERY_OUT) 641 mkdir -p $(TARGET_RECOVERY_ROOT_OUT) 642 mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/etc 643 mkdir -p $(TARGET_RECOVERY_ROOT_OUT)/tmp
准备recovery目录:out/target/product/{product_name}/recovery 及其子目录:
./root
./root/etc
./root/tmp
644 echo Copying baseline ramdisk... 645 cp -R $(TARGET_ROOT_OUT) $(TARGET_RECOVERY_OUT) 646 echo Modifying ramdisk contents... 647 rm -rf $(TARGET_RECOVERY_ROOT_OUT)/res
从标准根文件系统拷贝所有文件, 删除其res 目录。
648 cp -f $(recovery_initrc) $(TARGET_RECOVERY_ROOT_OUT)/ 649 cp -f $(recovery_binary) $(TARGET_RECOVERY_ROOT_OUT)/sbin/ 拷贝recovery 模式的核心文件 init.rc 及 recovery 650 cp -rf $(recovery_resources_common) $(TARGET_RECOVERY_ROOT_OUT)/ 651 $(foreach item,$(recovery_resources_private), / 652 cp -rf $(item) $(TARGET_RECOVERY_ROOT_OUT)/) 653 cp $(RECOVERY_INSTALL_OTA_KEYS) $(TARGET_RECOVERY_ROOT_OUT)/res/keys 拷贝资源文件及密钥文件。 654 cat $(INSTALLED_DEFAULT_PROP_TARGET) $(recovery_build_prop) / 655 > $(TARGET_RECOVERY_ROOT_OUT)/default.prop 生成属性文件 default.prop, 它包含了标准根文件系统的default.prop(out/target/product/{product_name}/root/default.prop)以及system分区的build.prop (out/target/product/{product_name}/system/build.prop) L656-L661 最终生成recovery.img 656 $(MKBOOTFS) $(TARGET_RECOVERY_ROOT_OUT) | $(MINIGZIP) > $(recovery_ramdisk) 压缩recovery根文件系统 657 build/quacomm/mkimage $(PRODUCT_OUT)/ramdisk-recovery.img RECOVERY > $(PRODUCT_OUT)/ramdisk_recovery.img 加一个标识头(RECOVERY) 658 mv $(PRODUCT_OUT)/ramdisk_recovery.img $(PRODUCT_OUT)/ramdisk-recovery.img 659 $(MKBOOTIMG) $(INTERNAL_RECOVERYIMAGE_ARGS) --output $@ 660 @echo ----- Made recovery image -------- $@ 661 $(hide) $(call assert-max-image-size,$@,$(BOARD_RECOVERYIMAGE_PARTITION_SIZE),raw)
和内核一起,生成recovery.img
附:Recovery 根文件系统目录结构
$ tree
.
├── advanced_meta_init.rc
├── data
├── default.prop
├── dev
├── etc
├── init
├── init.factory.rc
├── init.goldfish.rc
├── init.quacomm.rc
├── init.rc
├── meta_init.rc
├── proc
├── res
│ ├── images
│ │ ├── icon_error.png
│ │ ├── icon_installing.png
│ │ ├── indeterminate1.png
│ │ ├── indeterminate2.png
│ │ ├── indeterminate3.png
│ │ ├── indeterminate4.png
│ │ ├── indeterminate5.png
│ │ ├── indeterminate6.png
│ │ ├── progress_empty.png
│ │ └── progress_fill.png
│ └── keys
├── sbin
│ ├── adbd
│ ├── advanced_meta_init
│ ├── meta_init
│ ├── meta_tst
│ └── recovery
├── sys
├── system
└── tmp
Android OTA 升级之四:进入根文件系统
作者: 宋立新
Email:[email protected]
前言从bootloader 进入Recovery 模式后,首先也是运行Linux内核,该内核跟普通模式没有区别(减轻了BSP开发者的任务)。区别从执行文件系统开始。 Recovery 模式的细节就隐藏在其根文件系统中。
下面,我们就看看进入Recovery 根文件系统都干些啥。
init.rc
和正常启动一样,内核进入文件系统会执行/init, init 的配置文件就是 /init.rc, 前面文章讲过,这个文件来自:bootable/recovery/etc/init.rc,下面,我们看看它的内容。
1 2 on init 3 export PATH /sbin 4 export ANDROID_ROOT /system 5 export ANDROID_DATA /data 6 export EXTERNAL_STORAGE /sdcard 7 8 symlink /system/etc /etc 9 10 mkdir /sdcard 11 mkdir /system 12 mkdir /data 13 mkdir /cache 14 mount /tmp /tmp tmpfs 15 16 on boot 17 18 ifup lo 19 hostname localhost 20 domainname localdomain 21 22 class_start default 23 24 25 service recovery /sbin/recovery 26 27 service adbd /sbin/adbd recovery 28 disabled 29 30 on property:persist.service.adb.enable=1 31 start adbd 32 33 on property:persist.service.adb.enable=0 34 stop adbd
可以看到,它很非常简单:
1) 设置几个环境变量。备用。
2) 建立 etc 链接。
3) 造几个目录。备用。
4) Mount /tmp 目录为内存文件系统 tmpfs,后面会用到。
5) Trival 设置,不必关心。
6) 启动 recovery主程序。
7) 如果是eng模式(此时persist.service.adb.enable),启动adb
当然,init主程序还会装载属性配置文件 /default.prop, 它包含了很多系统属性设置,比如,ro.build.*, 等等。
很明显,这里最重要的就是recovery主程序,下面,我们分析它。
先看一段注释Recovery.c 中,作者写了一段注释,对我们理解recovery的实现很有帮助,下面看一下:(我就不翻译了)
89 /*90 * The recovery tool communicates with the main system through /cache files.91 * /cache/recovery/command - INPUT - command line for tool, one arg per line92 * /cache/recovery/log - OUTPUT - combined log file from recovery run(s)93 * /cache/recovery/intent - OUTPUT - intent that was passed in94 *95 * The arguments which may be supplied in the recovery.command file:96 * --send_intent=anystring - write the text out to recovery.intent97 * --update_package=root:path - verify install an OTA package file98 * --wipe_data - erase user data (and cache), then reboot99 * --wipe_cache - wipe cache (but not user data), then reboot 100 * --set_encrypted_filesystem=on|off - enables / diasables encrypted fs 101 * 102 * After completing, we remove /cache/recovery/command and reboot. 103 * Arguments may also be supplied in the bootloader control block (BCB). 104 * These important scenarios must be safely restartable at any point: 105 * 106 * FACTORY RESET 107 * 1. user selects "factory reset" 108 * 2. main system writes "--wipe_data" to /cache/recovery/command 109 * 3. main system reboots into recovery 110 * 4. get_args() writes BCB with "boot-recovery" and "--wipe_data" 111 * -- after this, rebooting will restart the erase -- 112 * 5. erase_root() reformats /data 113 * 6. erase_root() reformats /cache 114 * 7. finish_recovery() erases BCB 115 * -- after this, rebooting will restart the main system -- 116 * 8. main() calls reboot() to boot main system 117 * 118 * OTA INSTALL 119 * 1. main system downloads OTA package to /cache/some-filename.zip 120 * 2. main system writes "--update_package=CACHE:some-filename.zip" 121 * 3. main system reboots into recovery 122 * 4. get_args() writes BCB with "boot-recovery" and "--update_package=..." 123 * -- after this, rebooting will attempt to reinstall the update -- 124 * 5. install_package() attempts to install the update 125 * NOTE: the package install must itself be restartable from any point 126 * 6. finish_recovery() erases BCB 127 * -- after this, rebooting will (try to) restart the main system -- 128 * 7. ** if install failed ** 129 * 7a. prompt_and_wait() shows an error icon and waits for the user 130 * 7b; the user reboots (pulling the battery, etc) into the main system 131 * 8. main() calls maybe_install_firmware_update() 132 * ** if the update contained radio/hboot firmware **: 133 * 8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache" 134 * -- after this, rebooting will reformat cache & restart main system -- 135 * 8b. m_i_f_u() writes firmware image into raw cache partition 136 * 8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache" 137 * -- after this, rebooting will attempt to reinstall firmware -- 138 * 8d. bootloader tries to flash firmware 139 * 8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache") 140 * -- after this, rebooting will reformat cache & restart main system -- 141 * 8f. erase_root() reformats /cache 142 * 8g. finish_recovery() erases BCB 143 * -- after this, rebooting will (try to) restart the main system -- 144 * 9. main() calls reboot() to boot main system 145 * 146 * ENCRYPTED FILE SYSTEMS ENABLE/DISABLE 147 * 1. user selects "enable encrypted file systems" 148 * 2. main system writes "--set_encrypted_filesystem=on|off" to 149 * /cache/recovery/command 150 * 3. main system reboots into recovery 151 * 4. get_args() writes BCB with "boot-recovery" and 152 * "--set_encrypted_filesystems=on|off" 153 * -- after this, rebooting will restart the transition -- 154 * 5. read_encrypted_fs_info() retrieves encrypted file systems settings from /data 155 * Settings include: property to specify the Encrypted FS istatus and 156 * FS encryption key if enabled (not yet implemented) 157 * 6. erase_root() reformats /data 158 * 7. erase_root() reformats /cache 159 * 8. restore_encrypted_fs_info() writes required encrypted file systems settings to /data 160 * Settings include: property to specify the Encrypted FS status and 161 * FS encryption key if enabled (not yet implemented) 162 * 9. finish_recovery() erases BCB 163 * -- after this, rebooting will restart the main system -- 164 * 10. main() calls reboot() to boot main system 165 */recovery 主程序 559 int 560 main(int argc, char **argv) 561 { 562 time_t start = time(NULL); 563 564 // If these fail, there's not really anywhere to complain... 565 freopen(TEMPORARY_LOG_FILE, "a", stdout); setbuf(stdout, NULL); 566 freopen(TEMPORARY_LOG_FILE, "a", stderr); setbuf(stderr, NULL); 567 fprintf(stderr, "Starting recovery on %s", ctime(&start)); 568
将标准输出和标准错误输出重定位到"/tmp/recovery.log",如果是eng模式,就可以通过adb pull /tmp/recovery.log, 看到当前的log信息,这为我们提供了有效的调试手段。后面还会看到,recovery模式运行完毕后,会将其拷贝到cache分区,以便后续分析。
569 ui_init(); Recovery 使用了一个简单的基于framebuffer的ui系统,叫miniui,这里,进行了简单的初始化(主要是图形部分以及事件部分),并启动了一个 event 线程用于响应用户按键。 570 get_args(&argc, &argv);从misc 分区以及 CACHE:recovery/command 文件中读入参数,写入到argc, argv ,并且,如果有必要,回写入misc分区。这样,如果recovery没有操作成功(比如,升级还没有结束,就拔电池),系统会一直进入recovery模式。提醒用户继续升级,直到成功。
572 int previous_runs = 0; 573 const char *send_intent = NULL; 574 const char *update_package = NULL; 575 int wipe_data = 0, wipe_cache = 0; 576 577 int arg; 578 while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) { 579 switch (arg) { 580 case 'p': previous_runs = atoi(optarg); break; 581 case 's': send_intent = optarg; break; 582 case 'u': update_package = optarg; break; 583 case 'w': wipe_data = wipe_cache = 1; break; 584 case 'c': wipe_cache = 1; break; 585 case '?': 586 LOGE("Invalid command argument/n"); 587 continue; 588 } 589 } 590 解析参数,p: previous_runs没有用到,其它含义见前面注释。 591 device_recovery_start(); 这个函数没干什么。看名字,它給设备制造商提供了一个调用机会,可写入设备相关初始化代码。 592 593 fprintf(stderr, "Command:"); 594 for (arg = 0; arg < argc; arg++) { 595 fprintf(stderr, " /"%s/"", argv[arg]); 596 } 597 fprintf(stderr, "/n/n"); 598 打印出命令,比如,正常启动进入recovery模式,会打印:Command: "/sbin/recovery" 599 property_list(print_property, NULL); 600 fprintf(stderr, "/n"); 601 打印出所有的系统属性(from default.prop)到log文件。 602 int status = INSTALL_SUCCESS; 603 604 if (update_package != NULL) { 605 status = install_package(update_package); 606 if (status != INSTALL_SUCCESS) ui_print("Installation aborted./n"); 607 } else if (wipe_data) { 608 if (device_wipe_data()) status = INSTALL_ERROR; 609 if (erase_root("DATA:")) status = INSTALL_ERROR; 610 if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR; 611 if (status != INSTALL_SUCCESS) ui_print("Data wipe failed./n"); 612 } else if (wipe_cache) { 613 if (wipe_cache && erase_root("CACHE:")) status = INSTALL_ERROR; 614 if (status != INSTALL_SUCCESS) ui_print("Cache wipe failed./n"); 615 } else { 616 status = INSTALL_ERROR; // No command specified 617 } 根据用户提供参数,调用各项功能,比如,安装一个升级包,擦除cache分区, 擦除user data分区,install_package比较复杂,后面专门分析,其它都很简单。忽略。 618 619 if (status != INSTALL_SUCCESS) ui_set_background(BACKGROUND_ICON_ERROR); 622 if (status != INSTALL_SUCCESS) prompt_and_wait(); 如果前面已经做了某项操作并且成功,则进入重启流程。否则,等待用户选择具体操作。 而用户可选操作为: reboot, 安装update.zip,除cache分区, 擦除user data分区,如前所述,只有安装package 比较复杂,其它简单。 623 624 // Otherwise, get ready to boot the main system... 625 finish_recovery(send_intent); 它的功能如下:1)将前面定义的intent字符串写入(如果有的话):CACHE:recovery/command2)将 /tmp/recovery.log 复制到 "CACHE:recovery/log";3)清空 misc 分区,这样重启就不会进入recovery模式4)删除command 文件:CACHE:recovery/command; 626 ui_print("Rebooting.../n"); 627 sync(); 628 reboot(RB_AUTOBOOT); 629 return EXIT_SUCCESS; 630 }重启。
下面我们分析核心函数 install_package
install_package 289 int 290 install_package(const char *root_path) 291 { 292 ui_set_background(BACKGROUND_ICON_INSTALLING); 294 ui_print("Finding update package.../n"); 295 LOGI("Finding update package.../n"); 296 ui_show_indeterminate_progress(); 297 LOGI("Update location: %s/n", root_path); 298 更新 UI 显示 299 if (ensure_root_path_mounted(root_path) != 0) { 300 LOGE("Can't mount %s/n", root_path); 301 reset_mark_block(); 302 return INSTALL_CORRUPT; 303 } 304 确保升级包所在分区已经mount,通常为 cache 分区或者 SD 分区 305 char path[PATH_MAX] = ""; 306 if (translate_root_path(root_path, path, sizeof(path)) == NULL) { 307 LOGE("Bad path %s/n", root_path); 308 reset_mark_block(); 309 return INSTALL_CORRUPT; 310 } 将根分区转化为具体分区信息.这些信息来自:全局数组:g_roots 313 ui_print("Opening update package.../n"); 314 LOGI("Opening update package.../n"); 315 LOGI("Update file path: %s/n", path); 316 317 int numKeys; 318 RSAPublicKey* loadedKeys = load_keys(PUBLIC_KEYS_FILE, &numKeys); 319 if (loadedKeys == NULL) { 320 LOGE("Failed to load keys/n"); 321 reset_mark_block(); 322 return INSTALL_CORRUPT; 323 } 324 LOGI("%d key(s) loaded from %s/n", numKeys, PUBLIC_KEYS_FILE); 从/res/keys中装载公钥。 326 // Give verification half the progress bar... 328 ui_print("Verifying update package.../n"); 329 LOGI("Verifying update package.../n"); 330 ui_show_progress( 331 VERIFICATION_PROGRESS_FRACTION, 332 VERIFICATION_PROGRESS_TIME); 333 334 int err; 335 err = verify_file(path, loadedKeys, numKeys); 336 free(loadedKeys); 337 LOGI("verify_file returned %d/n", err); 338 if (err != VERIFY_SUCCESS) { 339 LOGE("signature verification failed/n"); 340 reset_mark_block(); 341 return INSTALL_CORRUPT; 342 } 根据公钥验证升级包verify_file的注释说的很明白: // Look for an RSA signature embedded in the .ZIP file comment given // the path to the zip. Verify it matches one of the given public // keys. 344 /* Try to open the package. 345 */ 346 ZipArchive zip; 347 err = mzOpenZipArchive(path, &zip); 348 if (err != 0) { 349 LOGE("Can't open %s/n(%s)/n", path, err != -1 ? strerror(err) : "bad"); 350 reset_mark_block(); 351 return INSTALL_CORRUPT; 352 } 打开升级包,将相关信息存到ZuoArchive数据机构中,便于后面处理。 354 /* Verify and install the contents of the package. 355 */ 356 int status = handle_update_package(path, &zip); 处理函数,我们后面继续分析。 357 mzCloseZipArchive(&zip); 358 return status; 359 } 关闭zip包,结束处理。 handle_update_package 204 static int 205 handle_update_package(const char *path, ZipArchive *zip) 206 { 207 // Update should take the rest of the progress bar. 208 ui_print("Installing update.../n"); 209 210 int result = try_update_binary(path, zip); 211 register_package_root(NULL, NULL); // Unregister package root 212 return result; 213 }
它主要调用函数try_update_binary完成功能。
try_update_binary84 // 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:[email protected]
前言可以说,前面分析的OTA升级的各部分代码都是在搭一个舞台,而主角现在终于登场,它就是updater. Google的代码架构设计非常好,各部分尽量松耦合。前面介绍升级脚本时,可知有两种类型的脚本,amend & edify. 他们各自对应一个updater. 这里,我们主要关注新的edify的updater.
Updater可以作为学习解释器/编译器的同学一个很好的实例,但是我们只关心产品化相关的内容,所以并不去深究lex/yacc相关的东西。
入口函数 main
(from: bootable/recovery/updater/updater.c)
62 // Where in the package we expect to find the edify script to execute.
63 // (Note it's "updateR-script", not the older "update-script".)
64 #define SCRIPT_NAME "META-INF/com/google/android/updater-script"
65
这里定义脚本的位置,注释说明本updater支持edify格式的脚本。
66 int main(int argc, char** argv) {
67 // Various things log information to stdout or stderr more or less
68 // at random. The log file makes more sense if buffering is
69 // turned off so things appear in the right order.
70 setbuf(stdout, NULL);
71 setbuf(stderr, NULL);
72
73 if (argc != 4) {
74 fprintf(stderr, "unexpected number of arguments (%d)/n", argc);
75 return 1;
76 }
77
78 char* version = argv[1];
79 if ((version[0] != '1' && version[0] != '2' && version[0] != '3') ||
80 version[1] != '/0') {
81 // We support version 1, 2, or 3.
82 fprintf(stderr, "wrong updater binary API; expected 1, 2, or 3; "
83 "got %s/n",
84 argv[1]);
85 return 2;
86 }
87
获取 version 参数。
88 // Set up the pipe for sending commands back to the parent process.
89
90 int fd = atoi(argv[2]);
91 FILE* cmd_pipe = fdopen(fd, "wb");
92 setlinebuf(cmd_pipe);
93
获取命令管道(用于图形显示等,见前篇)
94 // Extract the script from the package.
95
96 char* package_data = argv[3];
97 ZipArchive za;
98 int err;
99 err = mzOpenZipArchive(package_data, &za);
100 if (err != 0) {
101 fprintf(stderr, "failed to open package %s: %s/n",
102 package_data, strerror(err));
103 return 3;
104 }
105
106 const ZipEntry* script_entry = mzFindZipEntry(&za, SCRIPT_NAME);
107 if (script_entry == NULL) {
108 fprintf(stderr, "failed to find %s in %s/n", SCRIPT_NAME, package_data);
109 return 4;
110 }
111
112 char* script = malloc(script_entry->uncompLen+1);
113 if (!mzReadZipEntry(&za, script_entry, script, script_entry->uncompLen)) {
114 fprintf(stderr, "failed to read script from package/n");
115 return 5;
116 }
117 script[script_entry->uncompLen] = '/0';
118
读入脚本 META-INF/com/google/android/updater-script
119 // Configure edify's functions.
120
121 RegisterBuiltins();
122 RegisterInstallFunctions();
123 RegisterDeviceExtensions();
124 FinishRegistration();
125
注册语句处理函数
126 // Parse the script.
127
128 Expr* root;
129 int error_count = 0;
130 yy_scan_string(script);
131 int error = yyparse(&root, &error_count);
132 if (error != 0 || error_count > 0) {
133 fprintf(stderr, "%d parse errors/n", error_count);
134 return 6;
135 }
136
调用yy* 库函数解析脚本。
137 // Evaluate the parsed script.
138
139 UpdaterInfo updater_info;
140 updater_info.cmd_pipe = cmd_pipe;
141 updater_info.package_zip = &za;
142 updater_info.version = atoi(version);
143
144 State state;
145 state.cookie = &updater_info;
146 state.script = script;
147 state.errmsg = NULL;
148
149 char* result = Evaluate(&state, root);
150 if (result == NULL) {
151 if (state.errmsg == NULL) {
152 fprintf(stderr, "script aborted (no error message)/n");
153 fprintf(cmd_pipe, "ui_print script aborted (no error message)/n");
154 } else {
155 fprintf(stderr, "script aborted: %s/n", state.errmsg);
156 char* line = strtok(state.errmsg, "/n");
157 while (line) {
158 fprintf(cmd_pipe, "ui_print %s/n", line);
159 line = strtok(NULL, "/n");
160 }
161 fprintf(cmd_pipe, "ui_print/n");
162 }
163 free(state.errmsg);
164 return 7;
165 } else {
166 fprintf(stderr, "script result was [%s]/n", result);
167 free(result);
168 }
解释执行脚本。 核心函数是 Evaluate。它会调用其他callback函数,而这些callback函数又会调用Evaluate去解析不同的脚本片段。从而实现一个简单的解释器。
169
170 mzCloseZipArchive(&za);
171 free(script);
172
173 return 0;
174 }
还没开始,就结束了。代码非常简单,因为细节隐藏在那些callback函数里。我们看一下。
RegisterBuiltins 415 void RegisterBuiltins() { 416 RegisterFunction("ifelse", IfElseFn); 417 RegisterFunction("abort", AbortFn); 418 RegisterFunction("assert", AssertFn); 419 RegisterFunction("concat", ConcatFn); 420 RegisterFunction("is_substring", SubstringFn); 421 RegisterFunction("stdout", StdoutFn); 422 RegisterFunction("sleep", SleepFn); 423 424 RegisterFunction("less_than_int", LessThanIntFn); 425 RegisterFunction("greater_than_int", GreaterThanIntFn); 426 }这些语句控制执行流程。
RegisterInstallFunctions 1036 1037 void RegisterInstallFunctions() { 1038 RegisterFunction("mount", MountFn); 1039 RegisterFunction("is_mounted", IsMountedFn); 1040 RegisterFunction("unmount", UnmountFn); 1041 RegisterFunction("format", FormatFn); 1042 RegisterFunction("show_progress", ShowProgressFn); 1043 RegisterFunction("set_progress", SetProgressFn); 1044 RegisterFunction("delete", DeleteFn); 1045 RegisterFunction("delete_recursive", DeleteFn); 1046 RegisterFunction("package_extract_dir", PackageExtractDirFn); 1047 RegisterFunction("package_extract_file", PackageExtractFileFn); 1048 RegisterFunction("symlink", SymlinkFn); 1049 RegisterFunction("set_perm", SetPermFn); 1050 RegisterFunction("set_perm_recursive", SetPermFn); 1051 1052 RegisterFunction("getprop", GetPropFn); 1053 RegisterFunction("file_getprop", FileGetPropFn); 1054 RegisterFunction("write_raw_image", WriteRawImageFn); 1055 1056 RegisterFunction("apply_patch", ApplyPatchFn); 1057 RegisterFunction("apply_patch_check", ApplyPatchCheckFn); 1058 RegisterFunction("apply_patch_space", ApplyPatchSpaceFn); 1059 1060 RegisterFunction("read_file", ReadFileFn); 1061 RegisterFunction("sha1_check", Sha1CheckFn); 1062 1063 RegisterFunction("ui_print", UIPrintFn); 1064 1065 RegisterFunction("run_program", RunProgramFn); 1066 }这些语句执行各种功能。基本上,我们只需要知道用法就可以了。值得注意的是,run_program原语允许我们去执行自定义程序,这应该足够满足我们的个性化需求了。