Android OTA 升级(二): 脚本 ota_from_target_files

1. ota_from_target_files简介

         前面介绍了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}

2. 文件内容

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函数:

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     print
     912     print "  WARNING:  Failed to load max image sizes; will not enforce"
     913     print "  image size limits."
     914     print
    


     

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


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

     926   if OPTIONS.incremental_source is None:
     927     WriteFullOTAPackage(input_zip, output_zip)
     928   else:
     929     print "unzipping source target-files..."
     930     OPTIONS.source_tmp = common.UnzipTemp(OPTIONS.incremental_source)
     931     source_zip = zipfile.ZipFile(OPTIONS.incremental_source, "r")
     932     WriteIncrementalOTAPackage(input_zip, source_zip, output_zip)
    



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

     934   output_zip.close()
     935   if OPTIONS.package_key:
     936     SignOutput(temp_zip_file.name, args[1])
     937     temp_zip_file.close()
     939   common.Cleanup()
     941   print "done."
    



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

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

     

    3. WriteFullOTAPackage功能介绍

     

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

     

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

    356   metadata = {"post-build": GetBuildProp("ro.build.fingerprint", input_zip),
    357               "pre-device": GetBuildProp("ro.product.device", input_zip),
    358               "post-timestamp": GetBuildProp("ro.build.date.utc", input_zip),
    359               }
    


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

    361   device_specific = common.DeviceSpecificParams(
    362       input_zip=input_zip,
    363       input_version=GetRecoveryAPIVersion(input_zip),
    364       output_zip=output_zip,
    365       script=script,
    366       input_tmp=OPTIONS.input_tmp,
    367       metadata=metadata)
    
    


    设备相关参数,不深究。

    369   if not OPTIONS.omit_prereq:
    370     ts = GetBuildProp("ro.build.date.utc", input_zip)
    371     script.AssertOlderBuild(ts)

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

     373   AppendAssertions(script, input_zip)


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

    374   device_specific.FullOTA_Assertions()


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

    376   script.ShowProgress(0.5, 0)

       在升级脚本中加入显示进度的语句, 参数一表示底下的操作(到下一条同类语句或者到末尾)将暂用的时间在总体时间的比例。参数二用于控制显示的速度。比如,50 则表示底下的操作估计50秒内完成,要求进度条显示线程用50秒显示这一部分的进度。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.p
        2.在输出 ZIP包中生成一个脚本:recovery/etc/install-recovery.sh , 它最后会位于system/etc/install-recovery.sh.
           该脚本的内容为:

    #!/system/bin/sh
    if ! 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.p
    else
      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 分区(比如,设备的硬件配置、分区表等发生改变),这该如何操作呢?


     


     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     


  •  

  •  

  •  



  •  





  •  







     

     

     

     

     

     

     

     

     

     

     

     

     

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值