build_image.py
build/tools/releasetools/build_image.py: ------------------------------------------------------------------------- in_dir = argv[0] glob_dict_file = argv[1] out_file = argv[2] |
mkuserimg.sh [-s] in_dir, out_file, fs_type mount_point partition_size selinux_fc
mkyaffs2image –f [-c $(BOARD_NAND_PAGE_SIZE)] [-s $(BOARD_NAND_SPARE_SIZE)] in_dir out_file selinux_fc mount_point
common.py
全局函数
- Run(args, **kwargs)
启动进程,返回subprocess.Popen对象。
- LoadInfoDict(zip)
从META/misc_info.txt读取META字典,并返回字典对象(包括”fstab”和"build.prop")。
- LoadBuildProp(zip)
从SYSTEM/build.prop读取构建属性字典,并返回字典对象。
- LoadRecoveryFSTab(zip, fstab_version)
读取并解析RECOVERY/RAMDISK/etc/recovery.fstab,返回分区表字典。
- BuildBootableImage(sourcedir, fs_config_file, info_dict=None)
通过mkbootfs、minigzip、mkbootimg 工具将目录打包成bootable image。
- GetBootableImage(name, prebuilt_name, unpack_dir, tree_subdir, info_dict=None)
获取启动image,boot或者recovery,先检查BOOTABLE_IMAGES/prebuilt_name,如果没有,则通过tree_subdir调用BuildBootableImage构建一个。
- UnzipTemp(filename, pattern=None)
解压zip文件到临时目录,返回临时目录名和zipfile.ZipFile对象。
- GetKeyPasswords(keylist)
- SignFile(input_name, output_name, key, password, align=None, whole_file=False)
通过signapk命令签名zip文件,如果align,通过zipalign命令对齐zip中的文件。
- CheckSize(data, target, info_dict)
- ReadApkCerts(tf_zip)
- ComputeDifferences(diffs)
- GetTypeAndDevice(mount_point, info)
返回分区类型和设备路径,info中包含分区表(info["fstab"])。
- ParseCertificate(data)
class DeviceSpecificParams
处理升级包制作干预机制。
class File
- <data>
- name 文件名(完整路径)
- data 文件内容
- size 文件大小
- sha1 文件内容的hash值
- FromLocalFile(cls, name, diskname)
从本地文件读取内容构建一个File对象。
- WriteToTemp(self)
将文件内容写入临时文件中,返回NamedTemporaryFile对象。
- AddToZip(self, z)
将文件名,文件内容写入zip归档中。
class Difference
- ComputePatch(self)
计算源文件、目标文件的差异,根据扩展名选择不同的diff工具,如imgdiff、bsdiff。*.gz,*.zip,*.apk,*.jar,*.img使用imgdiff。
- GetPatch(self)
img_from_target_files.py
该脚本用来制作用于fastboot update的升级包。
ota-from-target-files.py
该脚本用来制作升级包。
根据输入参数情形,制作的升级包有“完整包”和“差量包”两种。
制作过程的输入文件是target-files结构的zip文件,输出文件是ota-package结构的zip文件。
全局函数
- IsSymlink
- IsRegular
- ClosestFileMatch(src, tgtfiles, existing)
寻找最大相关性文件,匹配的算法是:完整路径一样 > SHA1一样 > 文件名一样,后两种情况需要升级程序支持重命名函数(update_rename_support),并且源文件只能使用一次(不在existing中)。
- CopySystemFiles(input_zip, output_zip=None, substitute=None)
从源zip文件拷贝SYSTEM/*文件到目标zip文件system/*,跳过符号链接, substitute控制覆盖一些文件的内容。该接口返回所有符号链接(内容和名称)。
- SignOutput(temp_zip_name, output_zip_name)
用package_key签名整个zip包。
package_key默认是build/target/product/security/testkey。
- MakeRecoveryPatch(input_tmp, output_zip, recovery_img, boot_img)
通过imgdiff计算recovery_img, boot_img的差异,输出到recovery/recovery-from-boot.p,同时生成升级shell脚本recovery/etc/install-recovery.sh。
- WriteFullOTAPackage(input_zip, output_zip)
生成完整升级包。
- WriteIncrementalOTAPackage(target_zip, source_zip, output_zip)
生成增量升级包。
- WritePolicyConfig(file_context, output_zip)
- WriteMetadata(metadata, output_zip)
- LoadSystemFiles(z)
读取zip文件中的SYSTEM目录下文件,返回字典:name->File对象。
- GetBuildProp(prop, info_dict)
class Item
- <data>
- name
- uid
- gid
- mode
- selabel
- capabilities
- dir
- GetMetadata(cls, input_zip)
从Metadata("META/filesystem_config.txt")中读取每个item的属性。
- CountChildMetadata(self)
根据属性分类统计当前节点和所有子节点的个数,输出一个字典:{(uid, gid, dmode, fmode, selabel, capabilities): count}。同时计算最大分类,对应属性记录在best_subtree中。
- SetPermissions(self, script)
追加“权限修改指令”到升级脚本(由script对象维护)中。
主流程(main)
- 解析输入参数(common.ParseOptions)
- 解压输入zip文件(input_zip,common.UnzipTemp)
- 读入META信息字典(common.LoadInfoDict)
- META/misc_info.txt
- 其他可选的META目录下的文件:mkyaffs2-extra-flags.txt、recovery-api-version.txt、tool-extensions.txt、imagesizes.txt
- 读入recovery分区表(LoadRecoveryFSTab,从RECOVERY/RAMDISK/etc/ recovery.fstab)
- 读入build属性(LoadBuildProp,从SYSTEM/build.prop)
- 如果META字典中有selinux_fc,selinux_fc=“BOOT/RAMDISK/file_contexts”
- 创建输出zip文件(output_zip,zipfile.ZipFile),如果需要签名,使用临时文件中转
- 根据是否有增量源OPTIONS.incremental_source(-i参数指定)
- 写完整升级包(WriteFullOTAPackage)
- 创建增量源文件(source_zip),写增量升级包(WriteIncrementalOTAPackage)
- 如果需要,进行签名(SignOutput)
完整升级包(WriteFullOTAPackage)
- 创建升级脚本生成器edify_generator.EdifyGenerator
- 定义metadata:{post-build: ro.build.fingerprint, pre-device: ro.product.device, post-timestamp: ro.build.date.utc}
- 创建升级包制作干预common.DeviceSpecificParams对象
- 【升级脚本】检查时间(没有--omit_prereq)、版本一致性
- 【升级脚本】格式化/data分区(如果wipe_user_data)
- 如果META字典中有selinux_fc,写入selinux_fc(文件路径)到zip文件(WritePolicyConfig)
- 【升级脚本】格式化并挂载/system分区
- 【升级脚本】解压recovery、system目录到/system
- 从输入zip拷贝文件到输出zip
- 【升级脚本】创建符号链接
- 获取boot.img、recovery.img(common.GetBootableImage)
- 生成recovery image相对boot image增量patch,以及recovery升级脚本
- 解析文件属性定义(Item.GetMetadata)
- 【升级脚本】修改system目录下文件属性
- 写入boot.img到输出zip
- 【升级脚本】解压boot.img到boot分区
- 【升级脚本】卸载分区
- 将升级脚本写入到输出zip(script.AddToZip,META-INF/com/google/android/updater-script)
- 将metadata写入到输出zip(WriteMetadata,META-INF/com/android/metadata)
增量升级包(WriteIncrementalOTAPackage)
- 创建升级脚本生成器edify_generator.EdifyGenerator
- 定义metadata:{post-build: ro.build.fingerprint, pre-device: ro.product.device, post-timestamp: ro.build.date.utc}
- 创建升级包制作干预common.DeviceSpecificParams对象
- 加载源、目标zip文件中的SYSTEM目录文件(LoadSystemFiles)
- 构建源、目标文件对应关系(算法在ClosestFileMatch中),分成“重命名组”、“覆盖组(包括源文件不存在的文件)”,“差异组”,“覆盖组”文件写入到输出zip文件
- 计算所有“差异组”文件的差别
- 写入目标文件(如果差量反而更大移动到“覆盖组”)或者增量文件到输出zip文件
- 【升级脚本】挂载/system分区
- 【升级脚本】检查Fingerprint是否一致
- 从源zip、目标zip中分别获取boot.img、recovery.img(common.GetBootableImage)
- 【升级脚本】校验所有源文件的SHA1(script.PatchCheck)
- 【升级脚本】如果boot有更新,校验boot的SHA1
- 【升级脚本】检验cache剩余空间是否足够(至少largest_source_size)
- 【升级脚本】删除“覆盖组”文件(包括被直接替换的文件)
- 【升级脚本】应用patch(“差异组”,但是system/build.prop延后)
- 【升级脚本】如果boot有更新,应用boot patch升级
- 如果recovery有更新,生成recovery image相对boot image增量patch,以及recovery升级脚本
- 【升级脚本】如果recovery有更新,删除老的recovery增量patch和升级脚本
- 提取源、目标zip中的所有符号链接
- 解析文件属性定义(Item.GetMetadata)
- 【临时升级脚本】修改system目录下文件属性
- 【升级脚本】删除不再需要的符号链接
- 【升级脚本】解压system目录到/system
- 【升级脚本】如果recovery有更新,解压recovery目录到/system
- 【升级脚本】重命名绑定的那些文件
- 【升级脚本】删除并重建目标符号链接
- 【升级脚本】增加【临时升级脚本】
- 【升级脚本】对一些延后处理的文件,应用patch
- 【升级脚本】设置/system/build.prop的属性
- 【升级脚本】卸载分区
- 将升级脚本写入到输出zip(script.AddToZip,META-INF/com/google/android/updater-script)
- 将metadata写入到输出zip(WriteMetadata,META-INF/com/android/metadata)
参考:
- [Android]构建boot.img(一):root目录与ramdisk.img的生成
- [Android]构建boot.img(二):kernel的拷贝与打包
- [Android]构建boot.img(三):boot.img的生成与结构
设计细节
- 文件权限
升级命令set_perm_recursive能够递归设置目录下的所有文件权限,利用它能够提高效率(至少减少了升级脚本的行数)。这里的思想是寻找目录下最常见的权限设置,先通过set_perm_recursive设置,然后处理例外的情形。
在遍历目录时不处理符号链接,其权限都是root、root、777。
在增量升级的时候,权限设置会全部重新做一遍。
- 增量算法
- 构建文件字典(路径->File):source_data、target_data
- 对source_data的文件,
- 构建path:matching_file_cache
- 如果目标文件集中没有路径相同文件,增加file、sha1映射
- 对每个target_data中的文件
- 在matching_file_cache寻找匹配文件,
- 如果找到path匹配,返回之
- 如果update_rename_support为False,返回None
- 如果文件小于1K,返回None
- 如果sha1匹配,并且没有绑定,返回之
- 如果file(文件名)匹配,并且没有重命名绑定,返回之
- 返回None
- 如果找到匹配,但是路径不一样,加入到重命名组renames(字典:源文件路径->目标File),增加重命名绑定
- 如果没有找到匹配,加入到覆盖组:verbatim_targets(二元组:目标文件名,文件大小)
- 如果找到匹配,但是sha1不一样,加入到差异组:diffs(数组:Difference)
- 如果找到匹配,并且sha1一样,直接pass
- 在matching_file_cache寻找匹配文件,
- 计算差异组的差异文件
- 对每个差异组文件
- 如果差异文件大小大于目标文件大小的一定比例(patch_threshold),移动到覆盖组
- 否则,移动到补丁组:patch_list(五元组:源文件名,目标文件File,源文件File,目标文件大小,差异文件大小),更新最大源文件大小:largest_source_size
- 升级顺序
- 补丁组
- 覆盖组
- 重命名组
从目标文件的角度总结一下:
- 如果有path匹配
- 如果sha1不一样,差异组
- 如果差异太大,覆盖组
- 否则补丁组
- 否则,不加入任何组
- 如果sha1不一样,差异组
- 如果有sha1匹配,重命名组
- 如果file匹配,重命名组,同时加入差异组
- 如果差异太大,覆盖组
- 否则补丁组
- 如果没有匹配,覆盖组
加入覆盖组,目标文件输出到zip,加入补丁组,差异文件输出到zip。
如果同时在补丁组和重命名组,先做补丁,再重命名。
- recovery升级
Recovery升级是在正常升级完成之后才执行的,目的是保证至少有一个可用系统存在。
Recovery的升级文件是recovery分区相对与boot分区的补丁文件。通过打补丁的方式升级,因为这两个分区(或者img文件)包含很多相同内容,所以这种方式升级可以减少升级包的大小。
在主系统init.rc中,有启动recovery升级的服务定义:
service flash_recovery /system/etc/install-recovery.sh class main oneshot |
该shell脚本在函数MakeRecoveryPatch中生成,内容为:
#!/system/bin/sh if ! applypatch -c %(recovery_type)s:%(recovery_device)s:%(recovery_size)d:%(recovery_sha1)s; then log -t recovery "Installing new recovery image" applypatch %(bonus_args)s %(boot_type)s:%(boot_device)s:%(boot_size)d:%(boot_sha1)s %(recovery_type)s:%(recovery_device)s %(recovery_sha1)s %(recovery_size)d %(boot_sha1)s:/system/recovery-from-boot.p else log -t recovery "Recovery image already installed" fi |
- 符号链接
符号链接收集在二元数组(链接目标名,链接名)中,创建链接时以链接目标分组,同时创建到同一个目标的多个链接。
- 升级进度
升级进度有两个层次的控制,show_progress(frac, dur)控制进度在dur秒内增加frac。如果dur为0,那么由set_progress(frac)在上一个show_progress划定的块里面更新进度。
全量更新:
ShowProgress(0.5, 0) —— 解压system、recovery
ShowProgress(0.2, 0) —— 设置文件权限
ShowProgress(0.2, 10) —— 更新boot分区
ShowProgress(0.1, 0) —— 其他
增量更新:
ShowProgress(0.1, 0) —— 校验文件sha1,删除文件
SetProgress(so_far / total_verify_size)
ShowProgress(0.8, 0) —— 打补丁
SetProgress(so_far / total_patch_size)
ShowProgress(0.1, 10) —— 创建符号链接,重命名文件
- 签名
密钥来源:
- -k <package_key>(OPTIONS.package_key)
- META/misc_info.txt(OPTIONS.info_dict[“default_system_dev_certificate”])
- “build/target/product/security/testkey”
公钥扩展名: .x509.pem
私钥扩展名: .pk8
签名程序: signapk.jar -w <公钥> <私钥> <输入文件> <输出文件>