Android OTA 升級之二:腳本 ota_from_target_files

前言

       前面介紹了ota package 的編譯過程,其中最核心的部分就是一個 python 腳本:ota_from_target_files. 現在我們分析這個腳本。

先看一下幫助

不帶任何參數,先看一下它的幫助:

  1. $ ./ota_from_target_files  
  2.  
  3. Given a target-files zipfile, produces an OTA package that installs 
  4.  
  5. that build.  An incremental OTA is produced if -i is given, otherwise 
  6.  
  7. a full OTA is produced. 
  8.  
  9.   
  10.  
  11. Usage:  ota_from_target_files [flags] input_target_files output_ota_package 
  12.  
  13.   -b  (--board_config)  <file> 
  14.  
  15.       Deprecated. 
  16.  
  17.   -k  (--package_key)  <key> 
  18.  
  19.       Key to use to sign the package (default is 
  20.  
  21.       "build/target/product/security/testkey"). 
  22.  
  23.   -i  (--incremental_from)  <file> 
  24.  
  25.       Generate an incremental OTA using the given target-files zip as 
  26.  
  27.       the starting build. 
  28.  
  29.   -w  (--wipe_user_data) 
  30.  
  31.       Generate an OTA package that will wipe the user data partition 
  32.  
  33.       when installed. 
  34.  
  35.   -n  (--no_prereq) 
  36.  
  37.       Omit the timestamp prereq check normally included at the top of 
  38.  
  39.       the build scripts (used for developer OTA packages which 
  40.  
  41.       legitimately need to go back and forth). 
  42.  
  43.   -e  (--extra_script)  <file> 
  44.  
  45.       Insert the contents of file at the end of the update script. 
  46.  
  47.   -m  (--script_mode)  <mode> 
  48.  
  49.       Specify 'amend' or 'edify' scripts, or 'auto' to pick 
  50.  
  51.       automatically (this is the default). 
  52.  
  53.   -p  (--path)  <dir> 
  54.  
  55.       Prepend <dir>/bin to the list of places to search for binaries 
  56.  
  57.       run by this script, and expect to find jars in <dir>/framework. 
  58.  
  59.   -s  (--device_specific) <file> 
  60.  
  61.       Path to the python module containing device-specific 
  62.  
  63.       releasetools code. 
  64.  
  65.   -x  (--extra)  <key=value> 
  66.  
  67.       Add a key/value pair to the 'extras' dict, which device-specific 
  68.  
  69.       extension code may look at. 
  70.  
  71.   -v  (--verbose) 
  72.  
  73.       Show command lines being executed. 
  74.  
  75.   -h  (--help) 
  76.  
  77.       Display this usage message and exit. 
 

簡單翻譯一下:

-b 過時,不再使用。

-k 簽名用的密鑰

-i 生成增量OTA包時用於定義對比包

-w 是否清除 userdata 分區

-n 是否在升級時檢查時間戳,缺省情況下只能基於老的版本升級。

-e 定義額外運行的腳本

-m 定義採用的腳本格式,目前有兩種,amend & edify, 其中amend爲較老的格式。對應的,升級時會採用不同的解釋器。缺省情況下,ota_from_target_files 會同時生成兩個腳本。這提供了最大靈活性。

-p 定義腳本用到的一些可執行文件的路徑

-s 定義額外運行的腳本的路徑

-x 定義額外運行的腳本可能用到的鍵/值對

-v 老朋友,冗餘模式,讓腳本打印出執行的命令

-h 老朋友,這個就不用說了吧。

我們調用如下命令生成我們的升級包:

./build/tools/releasetools/ota_from_target_files /

  -m auto /

  -p out/host/linux-x86 /

  -k build/target/product/security/testkey -n /

out/target/product/{product-name}/obj/PACKAGING/target_files_intermediates/{product-name}-target_files-eng.{uid}.zip {output_zip}

再看內容

ota_from_target_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 函數:

  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 即可。
 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])
 解開輸入包。
  1. 892 if OPTIONS.device_specific is None:
  2. 893 # look for the device-specific tools extension location in the input
  3. 894 try:
  4. 895 f = open(os.path.join(OPTIONS.input_tmp, "META", "tool-extensions.txt"))
  5. 896 ds = f.read().strip()
  6. 897 f.close()
  7. 898 if ds:
  8. 899 ds = os.path.normpath(ds)
  9. 900 print "using device-specific extensions in", ds
  10. 901 OPTIONS.device_specific = ds
  11. 902 except IOError, e:
  12. 903 if e.errno == errno.ENOENT:
  13. 904 # nothing specified in the file
  14. 905 pass
  15. 906 else:
  16. 907 raise
處理 device-specific extensions, 沒用到。
 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。

主功能: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)
 
  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);
  40. show_progress(0.200000, 10);
  41. assert(package_extract_file("boot.img", "/tmp/boot.img"),
  42. write_raw_image("/tmp/boot.img", "boot"),
  43. delete("/tmp/boot.img"));
  44. show_progress(0.100000, 0);
  45. 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 分區(比如,設備的硬件配置、分區表等發生改變),這該如何操作呢?

發佈了9 篇原創文章 · 獲贊 2 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章