您的位置:首页 > 移动开发 > Android开发

Android OTA 升级(二): 脚本 ota_from_target_files

2014-12-19 17:38 1046 查看
转自 http://blog.csdn.net/myarrow/article/details/8111212
1. ota_from_target_files简介

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

[python] view
plaincopy

$ ./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 老朋友,这个就不用说了吧。

我们调用如下命令生成我们的升级包:

[python] view
plaincopy

./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__开始:

[python] view
plaincopy

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

[python] view
plaincopy

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 即可。

[python] view
plaincopy

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)

[python] view
plaincopy

886 if OPTIONS.extra_script is not None:

887 OPTIONS.extra_script = open(OPTIONS.extra_script).read()

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

[python] view
plaincopy

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, 没用到。

[python] view
plaincopy

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大小的参数,没用到。

[python] view
plaincopy

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),则还需要一个临时输出文件。

[python] view
plaincopy

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 创建函数,我们采用非增量模式。

[python] view
plaincopy

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功能介绍





[python] view
plaincopy

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 等。

[python] view
plaincopy

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 一下即知其义。

[python] view
plaincopy

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)

设备相关参数,不深究。

[python] view
plaincopy

369 if not OPTIONS.omit_prereq:

370 ts = GetBuildProp("ro.build.date.utc", input_zip)

371 script.AssertOlderBuild(ts)

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

[python] view
plaincopy

373 AppendAssertions(script, input_zip)

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

[python] view
plaincopy

374 device_specific.FullOTA_Assertions()

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

FullOTA_InstallEnd IncrementalOTA_Assertions IncrementalOTA_VerifyEnd。 不深究。

[python] view
plaincopy

376 script.ShowProgress(0.5, 0)

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

[python] view
plaincopy

378 if OPTIONS.wipe_user_data:

379 script.FormatPartition("userdata")

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

[python] view
plaincopy

381 script.FormatPartition("system")

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

[python] view
plaincopy

382 script.Mount("MTD", "system", "/system")

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

[python] view
plaincopy

383 script.UnpackPackageDir("recovery", "/system")

384 script.UnpackPackageDir("system", "/system")

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

[python] view
plaincopy

386 symlinks = CopySystemFiles(input_zip, output_zip)

387 script.MakeSymlinks(symlinks)

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

[python] view
plaincopy

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.

该脚本的内容为:

[python] view
plaincopy

#!/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

[python] view
plaincopy

395 Item.GetMetadata(input_zip)

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

[python] view
plaincopy

396 Item.Get("system").SetPermissions(script)

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

[python] view
plaincopy

398 common.CheckSize(boot_img.data, "boot.img")

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

[python] view
plaincopy

399 common.ZipWriteStr(output_zip, "boot.img", boot_img.data)

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



[python] view
plaincopy

400 script.ShowProgress(0.2, 0)

402 script.ShowProgress(0.2, 10)

更行进度条。

[python] view
plaincopy

403 script.WriteRawImage("boot", "boot.img")

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

[python] view
plaincopy

405 script.ShowProgress(0.1, 0)

更行进度条。

[python] view
plaincopy

406 device_specific.FullOTA_InstallEnd()

Callback, 同前。

[python] view
plaincopy

408 if OPTIONS.extra_script is not None:

409 script.AppendExtra(OPTIONS.extra_script)

如果有额外脚本,加入。

[python] view
plaincopy

411 script.UnmountAll()

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

[python] view
plaincopy

412 script.AddToZip(input_zip, output_zip)

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



[plain] view
plaincopy

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

[plain] view
plaincopy

413 WriteMetadata(metadata, output_zip)

将前面获取的metadata 写入输出包的文件中: META-INF/com/android/metadata

至此,我们就得到了一个update.zip包。可以开始升级了。

疑问:

1) 虽然提供了更新recovery分区的机制,但是没有看到触发该更新的语句。所以,缺省的情况是不会更新recovery分区的。大概是为了安全的原因吧。 但是,有时确实需要更新recovery 分区(比如,设备的硬件配置、分区表等发生改变),这该如何操作呢?
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: