Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1372341
  • 博文数量: 478
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4833
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-28 11:12
文章分类

全部博文(478)

文章存档

2019年(1)

2018年(27)

2017年(21)

2016年(171)

2015年(258)

我的朋友

分类: Android平台

2015-08-18 09:49:52

http://blog.csdn.net/myarrow/article/details/8111212
 

Android OTA 升级(二): 脚本 ota_from_target_files

分类: Android OTA 1787人阅读 评论(0) 收藏 举报

1. ota_from_target_files简介

         前面介绍了ota package 的编译过程,其中最核心的部分就是一个 python 脚本:ota_from_target_files. 现在我们分析这个脚本。不带任何参数,先看一下它的帮助:

[python] view plaincopy
  1. $ ./ota_from_target_files     
  2.     
  3. Given a target-files zipfile, produces an OTA package that installs    
  4. that build.  An incremental OTA is produced if -i is given, otherwise    
  5. a full OTA is produced.    
  6.     
  7. Usage:  ota_from_target_files [flags] input_target_files output_ota_package    
  8.   -b  (--board_config)  <file>    
  9.       Deprecated.    
  10.     
  11.   -k  (--package_key)  <key>    
  12.       Key to use to sign the package (default is    
  13.       "build/target/product/security/testkey").    
  14.     
  15.   -i  (--incremental_from)  <file>    
  16.       Generate an incremental OTA using the given target-files zip as    
  17.       the starting build.    
  18.     
  19.   -w  (--wipe_user_data)    
  20.       Generate an OTA package that will wipe the user data partition    
  21.       when installed.    
  22.     
  23.   -n  (--no_prereq)    
  24.       Omit the timestamp prereq check normally included at the top of    
  25.       the build scripts (used for developer OTA packages which    
  26.       legitimately need to go back and forth).    
  27.     
  28.   -e  (--extra_script)  <file>    
  29.       Insert the contents of file at the end of the update script.    
  30.     
  31.   -m  (--script_mode)  <mode>    
  32.       Specify 'amend' or 'edify' scripts, or 'auto' to pick    
  33.       automatically (this is the default).    
  34.     
  35.   -p  (--path)  <dir>    
  36.       Prepend <dir>/bin to the list of places to search for binaries    
  37.       run by this script, and expect to find jars in <dir>/framework.    
  38.     
  39.   -s  (--device_specific) <file>    
  40.       Path to the python module containing device-specific    
  41.       releasetools code.    
  42.     
  43.   -x  (--extra)  <key=value>    
  44.       Add a key/value pair to the 'extras' dict, which device-specific    
  45.       extension code may look at.    
  46.     
  47.   -v  (--verbose)    
  48.       Show command lines being executed.    
  49.     
  50.   -h  (--help)    
  51.       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 老朋友,这个就不用说了吧。
我们调用如下命令生成我们的升级包: 

[python] view plaincopy
  1. ./build/tools/releasetools/ota_from_target_files /  
  2.   -m auto /  
  3.   -p out/host/linux-x86 /  
  4.   -k build/target/product/security/testkey -n /  
  5. out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}  

2. 文件内容

ota_from_target_files为python 脚本,所以如果懂 python, 会更顺利一点。文件有1000行。分析过程中,我们只是贴代码片段。 完整文件见:

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

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

[python] view plaincopy
  1. 944 if __name__ == '__main__':  
  2. 945   try:  
  3. 946     main(sys.argv[1:])  
  4. 947   except common.ExternalError, e:  
  5. 948     print  
  6. 949     print "   ERROR: %s" % (e,)  
  7. 950     print  
  8. 951     sys.exit(1)  


它调用main函数:

[python] view plaincopy
  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 即可。

[python] view plaincopy
  1. 883   if OPTIONS.script_mode not in ("amend""edify""auto"):  
  2. 884     raise ValueError('unknown script mode "%s"' % (OPTIONS.script_mode,))  


 Script_mode 只能是amend/edify/auto之一, auto 目前是选择两者都支持。
可以理解是为了向前兼容,(早期 Android 使用 amend)

[python] view plaincopy
  1. 886   if OPTIONS.extra_script is not None:  
  2. 887     OPTIONS.extra_script = open(OPTIONS.extra_script).read()  

读入 额外脚本的内容。(如果有)

[python] view plaincopy
  1. 889   print "unzipping target target-files..."  
  2. 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, 没用到。

  • [python] view plaincopy
    1. 909   common.LoadMaxSizes()  
    2. 910   if not OPTIONS.max_image_size:  
    3. 911     print  
    4. 912     print "  WARNING:  Failed to load max image sizes; will not enforce"  
    5. 913     print "  image size limits."  
    6. 914     print  


     

    读入设定image大小的参数,没用到。
  • [python] view plaincopy
    1. 916   OPTIONS.target_tmp = OPTIONS.input_tmp  
    2. 917   input_zip = zipfile.ZipFile(args[0], "r")  
    3. 918   if OPTIONS.package_key:  
    4. 919     temp_zip_file = tempfile.NamedTemporaryFile()  
    5. 920     output_zip = zipfile.ZipFile(temp_zip_file, "w",  
    6. 921                                  compression=zipfile.ZIP_DEFLATED)  
    7. 922   else:  
    8. 923     output_zip = zipfile.ZipFile(args[1], "w",  
    9. 924                  compression=zipfile.ZIP_DEFLATED)  


    设定输出文件,如果要签名(our case),则还需要一个临时输出文件。

    [python] view plaincopy
    1. 926   if OPTIONS.incremental_source is None:  
    2. 927     WriteFullOTAPackage(input_zip, output_zip)  
    3. 928   else:  
    4. 929     print "unzipping source target-files..."  
    5. 930     OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)  
    6. 931     source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")  
    7. 932     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)  



    根据参数,调用增量和非增量创建 ZIP 创建函数,我们采用非增量模式。

    [python] view plaincopy
    1. 934   output_zip.close()  
    2. 935   if OPTIONS.package_key:  
    3. 936     SignOutput(temp_zip_file.name, args[1])  
    4. 937     temp_zip_file.close()  
    5. 939   common.Cleanup()  
    6. 941   print "done."  



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

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

     

    3. WriteFullOTAPackage功能介绍

     

    [python] view plaincopy
    1. 345 def WriteFullOTAPackage(input_zip, output_zip):  
    2. 346   if OPTIONS.script_mode == "auto":  
    3. 347     script = both_generator.BothGenerator(2)  
    4. 348   elif OPTIONS.script_mode == "amend":  
    5. 349     script = amend_generator.AmendGenerator()  
    6. 350   else:  
    7. 351     # TODO: how to determine this?  We don't know what version it will  
    8. 352     # be installed on top of.  For now, we expect the API just won't  
    9. 353     # change very often.  
    10. 354     script = edify_generator.EdifyGenerator(2)  

     

     首先,我们获得脚本生成器,他们的实现见脚本:edify_generator.py 等。

    [python] view plaincopy
    1. 356   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),  
    2. 357               "pre-device": GetBuildProp("ro.product.device", input_zip),  
    3. 358               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),  
    4. 359               }  


     获得一些环境变量,来自android 环境变量。 Google 一下即知其义。

    [python] view plaincopy
    1. 361   device_specific = common.DeviceSpecificParams(  
    2. 362       input_zip=input_zip,  
    3. 363       input_version=GetRecoveryAPIVersion(input_zip),  
    4. 364       output_zip=output_zip,  
    5. 365       script=script,  
    6. 366       input_tmp=OPTIONS.input_tmp,  
    7. 367       metadata=metadata)  


    设备相关参数,不深究。

    [python] view plaincopy
    1. 369   if not OPTIONS.omit_prereq:  
    2. 370     ts = GetBuildProp("ro.build.date.utc", input_zip)  
    3. 371     script.AssertOlderBuild(ts)  

    如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于升级老的系统。

    [python] view plaincopy
    1. 373   AppendAssertions(script, input_zip)  


     如果需要,在脚本中增加一个Assert语句,要求update zip包只能用于同一设备,即目标设备的 ro.product.device必须跟update.zip中的相同。

    [python] view plaincopy
    1. 374   device_specific.FullOTA_Assertions()  


    Callback, 用于调用设备相关代码。调用时机为即将开始升级。类似还有:
    FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。

    [python] view plaincopy
    1. 376   script.ShowProgress(0.50)  

       在升级脚本中加入显示进度的语句, 参数一表示底下的操作(到下一条同类语句或者到末尾)将暂用的时间在总体时间的比例。参数二用于控制显示的速度。比如,50 则表示底下的操作估计50秒内完成,要求进度条显示线程用50秒显示这一部分的进度。0 表示不自动更新,手动控制(使用SetProgress)

    [python] view plaincopy
    1. 378   if OPTIONS.wipe_user_data:  
    2. 379     script.FormatPartition("userdata")  

    如果需要,在脚本中增加语句,擦除 userdata 分区。

    [python] view plaincopy
    1. 381   script.FormatPartition("system")  

     在脚本中增加语句,擦除 system分区。

    [python] view plaincopy
    1. 382   script.Mount("MTD""system""/system")  

     在脚本中增加语句,安装 system分区到 /system 目录。

    [python] view plaincopy
    1. 383   script.UnpackPackageDir("recovery""/system")  
    2. 384   script.UnpackPackageDir("system""/system")  

       在脚本中增加语句,将recovery以及system中的内容拷贝到 /system目录。其中recovery 目录包含一个patch 以及应用该patch 的脚本。

  • [python] view plaincopy
    1. 386   symlinks = CopySystemFiles(input_zip, output_zip)  
    2. 387   script.MakeSymlinks(symlinks)  

       386 行从输入 ZIP 包 /system 拷贝文件到输出 ZIP 包 /system。由于这个过程不支持链接文件,所以它将这些文件返回。 于 387 行做继续处理。该行建立这些link 文件。所有的link文件都指向 toolbox

    [python] view plaincopy
    1. 389   boot_img = File("boot.img", common.BuildBootableImage(  
    2. 390       os.path.join(OPTIONS.input_tmp, "BOOT")))  
    3. 391   recovery_img = File("recovery.img", common.BuildBootableImage(  
    4. 392       os.path.join(OPTIONS.input_tmp, "RECOVERY")))  
    5. 393   MakeRecoveryPatch(output_zip, recovery_img, boot_img)  

         这个复杂,MakeRecoveryPatch 做了两件事:
        1.在输出 ZIP包中生成一个patch: recovery/recovery-from-boot.p(boot.img和 recovery.img的patch), 它最后会位于:system/recovery-from-boot.p
        2.在输出 ZIP包中生成一个脚本:recovery/etc/install-recovery.sh , 它最后会位于system/etc/install-recovery.sh.
           该脚本的内容为:

    [python] view plaincopy
    1. #!/system/bin/sh  
    2. if ! applypatch -c MTD:recovery:2048:6a167ffb86a4a16cb993473ce0726a3067163fc1; then  
    3.   log -t recovery "Installing new recovery image"  
    4.   applypatch MTD:boot:2324480:9a72a20a9c2f958ba586a840ed773cf8f5244183 MTD:recovery f6c2a70c5f2b02b6a49c9f5c5507a45a42e2d389 2564096 9a72a20a9c2f958ba586a840ed773cf8f5244183:/system/recovery-from-boot.p  
    5. else  
    6.   log -t recovery "Recovery image already installed"  
    7. fi  
    [python] view plaincopy
    1. 395   Item.GetMetadata(input_zip)  

    从 META/filesystem_config.txt 中获得 system 目录下的各文件权限信息。

    [python] view plaincopy
    1. 396   Item.Get("system").SetPermissions(script)  

    在脚本中增加语句,设置 system 目录下文件的权限及属主等。 

    [python] view plaincopy
    1. 398   common.CheckSize(boot_img.data, "boot.img")  

     检查 boot.img 文件大小是否超标.

    [python] view plaincopy
    1. 399   common.ZipWriteStr(output_zip, "boot.img", boot_img.data)  

    将boot.img 放到输出 ZIP 包中。

     


    [python] view plaincopy
    1. 400   script.ShowProgress(0.20)  
    2. 402   script.ShowProgress(0.210)  




     更行进度条。

    [python] view plaincopy
    1. 403   script.WriteRawImage("boot""boot.img")  

    在脚本中增加语句,将 boot.img 写到 boot 分区。

    [python] view plaincopy
    1. 405   script.ShowProgress(0.10)  

     更行进度条。

    [python] view plaincopy
    1. 406   device_specific.FullOTA_InstallEnd()  

     Callback, 同前。

    [python] view plaincopy
    1. 408   if OPTIONS.extra_script is not None:  
    2. 409     script.AppendExtra(OPTIONS.extra_script)  

    如果有额外脚本,加入。

    [python] view plaincopy
    1. 411   script.UnmountAll()  

     在脚本中增加语句,umount 所有分区。

    [python] view plaincopy
    1. 412   script.AddToZip(input_zip, output_zip)  

    1)将前面生成的脚本输出到:META-INF/com/google/android/updater-script (对于edify)

     

    [plain] view plaincopy
    1. assert(getprop("ro.product.device") == "thedevicename" ||            
    2.        getprop("ro.build.product") == "theproductname");      
    3. show_progress(0.500000, 0);      
    4. format("MTD", "system");      
    5. mount("MTD", "system", "/system");    
    6. package_extract_dir("recovery", "/system");   
    7. package_extract_dir("system", "/system");      
    8. symlink("dumpstate", "/system/bin/dumpcrash");      
    9. symlink("toolbox", "/system/bin/cat", "/system/bin/chmod",              
    10.         "/system/bin/chown", "/system/bin/cmp", "/system/bin/date",              
    11.         "/system/bin/dd", "/system/bin/df", "/system/bin/dmesg",              
    12.         "/system/bin/fb2bmp", "/system/bin/getevent", "/system/bin/getprop",              
    13.         "/system/bin/hd", "/system/bin/id", "/system/bin/ifconfig",              
    14.         "/system/bin/iftop", "/system/bin/insmod", "/system/bin/ioctl",              
    15.         "/system/bin/kill", "/system/bin/ln", "/system/bin/log",              
    16.         "/system/bin/ls", "/system/bin/lsmod", "/system/bin/mkdir",              
    17.         "/system/bin/mount", "/system/bin/mv", "/system/bin/netstat",              
    18.         "/system/bin/newfs_msdos", "/system/bin/notify", "/system/bin/printenv",              
    19.         "/system/bin/ps", "/system/bin/reboot", "/system/bin/renice",              
    20.         "/system/bin/rm", "/system/bin/rmdir", "/system/bin/rmmod",              
    21.         "/system/bin/route", "/system/bin/schedtop", "/system/bin/sendevent",             
    22.         "/system/bin/setconsole", "/system/bin/setprop", "/system/bin/sleep",             
    23.         "/system/bin/smd", "/system/bin/start", "/system/bin/stop",              
    24.         "/system/bin/sync", "/system/bin/top", "/system/bin/umount",              
    25.         "/system/bin/vmstat", "/system/bin/watchprops",              
    26.         "/system/bin/wipe");      
    27. set_perm_recursive(0, 0, 0755, 0644, "/system");      
    28. set_perm_recursive(0, 2000, 0755, 0755, "/system/bin");      
    29. set_perm(0, 3003, 02755, "/system/bin/netcfg");      
    30. set_perm(0, 3004, 02755, "/system/bin/ping");      
    31. set_perm_recursive(1002, 1002, 0755, 0440, "/system/etc/bluez");      
    32. set_perm(0, 0, 0755, "/system/etc/bluez");      
    33. set_perm(1002, 1002, 0440, "/system/etc/dbus.conf");      
    34. set_perm(1014, 2000, 0550, "/system/etc/dhcpcd/dhcpcd-run-hooks");      
    35. set_perm(0, 2000, 0550, "/system/etc/init.goldfish.sh");      
    36. set_perm(0, 0, 0544, "/system/etc/install-recovery.sh");      
    37. set_perm_recursive(0, 0, 0755, 0555, "/system/etc/ppp");      
    38. set_perm_recursive(0, 2000, 0755, 0755, "/system/xbin");      
    39. show_progress(0.200000, 0);    show_progress(0.200000, 10);      
    40. assert(package_extract_file("boot.img", "/tmp/boot.img"),           
    41. write_raw_image("/tmp/boot.img", "boot"),             
    42. delete("/tmp/boot.img"));      
    43. show_progress(0.100000, 0);      
    44. unmount("/system");    

     

     

    2)将升级程序:OTA/bin/updater 从输入ZIP包中拷贝到输出ZIP包中的:META-INF/com/google/android/update-binary

    [plain] view plaincopy
    1. 413   WriteMetadata(metadata, output_zip)  

    将前面获取的metadata 写入输出包的文件中: META-INF/com/android/metadata
    至此,我们就得到了一个update.zip包。可以开始升级了。
    疑问:

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


     

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