相关文章:
Android A/B System 分析系列
Android Update Engine 分析系列
在《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》中提到了,delta_generator 工具提供的 6 种操作,分别是:
- 生成 payload 和 metadata 数据的哈希值
- 更新 payload 和 metadata 数据的签名
- 使用公钥验证 payload 和 metadata 数据的签名
- 提取 payload 文件的 property 数据
- 对 old image 打 delta 补丁
- 生成 payload 数据
生成 payload 数据是 delta_generator 工具最主要的功能,当我准备重新更新这一些列文章时,一开始就想写这个的,不过因为太久没看,不记得 payload 细节了,更无从从细节上分析。所以就先写一些简单的操作,等熟悉了再开始写。经过前面篇的分析了解 payload 结构,现在是时候来解决这个问题了。
本篇主要从相对高一点的层次开始,自上而下分析 payload 数据的生成。不过对于具体到每个 block 数据的处理,这又太过于细节,会留在后续篇章处理。
- 如果在阅读代码时,觉得用于生成 payload 数据比较复杂,建议参考第 2.2 节我总结的 payload_config 结构图
- 如果你代码的逐行注释不感兴趣,只想从宏观步骤上了解 payload 是如何生成的,请跳转到第 3 节,直接看总结
本文涉及的Android代码版本:android‐7.1.1_r23 (NMF27D)
1. 从 ota_from_target_files 脚本 到 delta_generator 工具
1.1 ota_from_target_files 制作升级包命令
命令行调用 ota_from_target_files 来生成 payload 数据:
默认制作全量升级包
$ ./build/tools/releasetools/ota_from_target_files bcm7252ssffdr4-target_files-eng.ygu.zip update.zip
当使用 -i 选项指定基线包时,此时生成增量升级包
$./build/tools/releasetools/ota_from_target_files \
-i dist/old.zip \
dist/new.zip \
update.zip
本篇主要分析较为复杂的增量升级包的制作,有了对增量升级包的理解,了解全量升级包就更容易了。
1.2 brillo_update_payload 制作升级包
A/B 系统上,所有 zip 包的操作都交由 brillo_update_payload 脚本处理,比如生成 payload 数据:
brillo_update_payload generate --payload /tmp/payload-unsigned.bin \
--target_image dist/new.zip \
--source_image dist/old.zip
在 brillo_update_payload 脚本中,进一步调用 delta_generator 工具来生成 payload:
delta_generator -out_file=dist/payload-unsigned.bin \
-partition_names=boot:system \
-new_partitions=/tmp/boot.img.new:/tmp/system.raw.new \
-old_partitions=/tmp/boot.img.old:/tmp/system.raw.old \
--minor_version=3 --major_version=2
因此,熟悉 delta_generator 参数的话,以直接在命令行操作。
2. delta_generator 生成 payload 源码分析
2.1 Main 函数
Main 函数中处理生成 payload.bin 的代码有点长,似乎还挺复杂,不过按照功能划分,只做了两件事:
- 解析传入参数,设置
payload_config 结构体,用于后期指导如何生成 payload 文件; - 根据
payload_config 设置生成 payload 文件;
// 文件: system/update_engine/payload_generator/generate_delta_main.cc
int Main(int argc, char** argv) {
...
/*
* 1. 解析传入参数,设置 payload_config 结构体
*/
// A payload generation was requested. Convert the flags to a
// PayloadGenerationConfig.
PayloadGenerationConfig payload_config;
vector<string> partition_names, old_partitions, new_partitions;
/*
* 1.1 解析 "partition_names" 参数
*/
// 拆分传递的参数 "-partition_names=boot:system" 到 partition_names 中
partition_names =
base::SplitString(FLAGS_partition_names, ":", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
CHECK(!partition_names.empty());
// 检查 major_version 和 new_partitions 参数
// 1. major_version 是否为 1,实际传入为 2
// 2. new_partitions 是否为空
if (FLAGS_major_version == kChromeOSMajorPayloadVersion ||
FLAGS_new_partitions.empty()) {
// 检查 partition_names 参数
// 1. 如果 partition_names 包含的分区数不为 2
// 2. 如果 partition_names 包含的分区不是 "root" 和 "system"
// 则提示错误信息
LOG_IF(FATAL, partition_names.size() != 2)
<< "To support more than 2 partitions, please use the "
<< "--new_partitions flag and major version 2.";
LOG_IF(FATAL, partition_names[0] != kLegacyPartitionNameRoot ||
partition_names[1] != kLegacyPartitionNameKernel)
<< "To support non-default partition name, please use the "
<< "--new_partitions flag and major version 2.";
}
/*
* 1.2 解析 "new_partitions" 参数,设置 payload_config.target.partitions
*/
// 如果传递了 "-new_partitions" 参数
if (!FLAGS_new_partitions.empty()) {
LOG_IF(FATAL, !FLAGS_new_image.empty() || !FLAGS_new_kernel.empty())
<< "--new_image and --new_kernel are deprecated, please use "
<< "--new_partitions for all partitions.";
// 拆分传递的参数 "-new_partitions=/tmp/boot.img.new:/tmp/system.raw.new" 到 new_partitions 中
new_partitions =
base::SplitString(FLAGS_new_partitions, ":", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
CHECK(partition_names.size() == new_partitions.size());
// 如果有传递 "-old_partitions" 参数,则为差分升级,否则为整包升级
payload_config.is_delta = !FLAGS_old_partitions.empty();
LOG_IF(FATAL, !FLAGS_old_image.empty() || !FLAGS_old_kernel.empty())
<< "--old_image and --old_kernel are deprecated, please use "
<< "--old_partitions if you are using --new_partitions.";
} else {
// 如果没有传递 "-new_partitions" 参数, 则使用 "-new_image" 和 "-new_kernel" 参数
new_partitions = {FLAGS_new_image, FLAGS_new_kernel};
LOG(WARNING) << "--new_partitions is empty, using deprecated --new_image "
<< "and --new_kernel flags.";
// 没有传递 "-old_image" 和 "-old_kernel" 参数,则为整包升级
payload_config.is_delta = !FLAGS_old_image.empty() ||
!FLAGS_old_kernel.empty();
LOG_IF(FATAL, !FLAGS_old_partitions.empty())
<< "Please use --new_partitions if you are using --old_partitions.";
}
// 遍历需要操作的所有分区
for (size_t i = 0; i < partition_names.size(); i++) {
LOG_IF(FATAL, partition_names[i].empty())
<< "Partition name can't be empty, see --partition_names.";
// 使用 new_partitions 参数设置 target.partitions
// target.partitions[0].name = boot
// target.partitions[0].path = /tmp/boot.img.new
// 在 target.partitions vector 的最后用 partition_names[i] 初始化一个分区配置信息
payload_config.target.partitions.emplace_back(partition_names[i]);
// 设置最后添加 target 分区的 path 信息
payload_config.target.partitions.back().path = new_partitions[i];
}
/*
* 1.3 解析 "old_partitions" 参数,设置 payload_config.source.partitions
*/
// 判断是否为增量升级
if (payload_config.is_delta) {
if (!FLAGS_old_partitions.empty()) {
// 拆分传递的参数 "-old_partitions=/tmp/boot.img.old:/tmp/system.raw.old" 到 old_partitions 中
old_partitions =
base::SplitString(FLAGS_old_partitions, ":", base::TRIM_WHITESPACE,
base::SPLIT_WANT_ALL);
// 确保 old_partitions 和 new_partitions 的数量一致
CHECK(old_partitions.size() == new_partitions.size());
} else {
old_partitions = {FLAGS_old_image, FLAGS_old_kernel};
LOG(WARNING) << "--old_partitions is empty, using deprecated --old_image "
<< "and --old_kernel flags.";
}
// 使用 old_partitions 参数设置 source.partitions
// source.partitions[0].name = boot
// source.partitions[0].path = /tmp/boot.img.old
for (size_t i = 0; i < partition_names.size(); i++) {
// 在 source.partitions vector 的最后用 partition_names[i] 初始化一个分区配置信息
payload_config.source.partitions.emplace_back(partition_names[i]);
// 设置最后添加 source 分区的 path 信息
payload_config.source.partitions.back().path = old_partitions[i];
}
}
/*
* 1.4 解析 "new_postinstall_config_file" 参数
*/
// 检查是否传递了 "-new_postinstall_config_file" 参数
if (!FLAGS_new_postinstall_config_file.empty()) {
LOG_IF(FATAL, FLAGS_major_version == kChromeOSMajorPayloadVersion)
<< "Postinstall config is only allowed in major version 2 or newer.";
brillo::KeyValueStore store;
// 从 new_postinstall_config_file 文件加载 key=value 键值对
CHECK(store.Load(base::FilePath(FLAGS_new_postinstall_config_file)));
CHECK(payload_config.target.LoadPostInstallConfig(store));
}
/*
* 1.5 设置 payload_config 的 hard_chunk_size, block_size
*/
// 使用 "-chunk_size" 参数和 kBlockSize 更新 payload_config 设置
// Use the default soft_chunk_size defined in the config.
payload_config.hard_chunk_size = FLAGS_chunk_size;
payload_config.block_size = kBlockSize;
// The partition size is never passed to the delta_generator, so we
// need to detect those from the provided files.
// 根据 source 分区镜像文件设置每一个分区对应的 payload_config.source.partitions[0/1].size 参数
if (payload_config.is_delta) {
CHECK(payload_config.source.LoadImageSize());
}
// 根据 target 分区镜像文件设置每一个分区对应的 payload_config.target.partitions[0/1].size 参数
CHECK(payload_config.target.LoadImageSize());
// 确保 payload 输出文件不能为空
CHECK(!FLAGS_out_file.empty());
/*
* 1.5 解析参数,设置 payload_config.target.image_info 和 payload_config.source.image_info
*/
// 根据以下可选参数:
// - new_channel,
// - new_board,
// - new_version,
// - new_key,
// - new_build_channel,
// - new_build_version
// 参数创建 target 对应的 ImageInfo protobuf 结构数据
// Ignore failures. These are optional arguments.
ParseImageInfo(FLAGS_new_channel,
FLAGS_new_board,
FLAGS_new_version,
FLAGS_new_key,
FLAGS_new_build_channel,
FLAGS_new_build_version,
&payload_config.target.image_info);
// 根据以下可选参数:
// - old_channel,
// - old_board,
// - old_version,
// - old_key,
// - old_build_channel,
// - old_build_version
// 参数创建 source 对应的 ImageInfo protobuf 结构数据
// Ignore failures. These are optional arguments.
ParseImageInfo(FLAGS_old_channel,
FLAGS_old_board,
FLAGS_old_version,
FLAGS_old_key,
FLAGS_old_build_channel,
FLAGS_old_build_version,
&payload_config.source.image_info);
// 根据 "-rootfs_partition_size" 参数设置 payload_config
payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size;
// 如果是增量升级,获取相应的 target 和 source 分区文件句柄
if (payload_config.is_delta) {
// Avoid opening the filesystem interface for full payloads.
for (PartitionConfig& part : payload_config.target.partitions)
CHECK(part.OpenFilesystem());
for (PartitionConfig& part : payload_config.source.partitions)
CHECK(part.OpenFilesystem());
}
/*
* 1.6 设置 payload_config.version
*/
// 根据 "-major_version" 参数设置 payload_config, Android N 上的 major_version=2
payload_config.version.major = FLAGS_major_version;
LOG(INFO) << "Using provided major_version=" << FLAGS_major_version;
// 检查 "-minor_version" 参数
// 1. 如果没有传递该参数,则从 update_engine.con 中提取
// 2. 如果有传递,则根据 "-minor_version" 参数设置 payload_config, Android N 上的 minor_version=3
if (FLAGS_minor_version == -1) {
// Autodetect minor_version by looking at the update_engine.conf in the old
// image.
if (payload_config.is_delta) {
payload_config.version.minor = kInPlaceMinorPayloadVersion;
brillo::KeyValueStore store;
uint32_t minor_version;
for (const PartitionConfig& part : payload_config.source.partitions) {
if (part.fs_interface && part.fs_interface->LoadSettings(&store) &&
utils::GetMinorVersion(store, &minor_version)) {
payload_config.version.minor = minor_version;
break;
}
}
} else {
payload_config.version.minor = kFullPayloadMinorVersion;
}
LOG(INFO) << "Auto-detected minor_version=" << payload_config.version.minor;
} else {
payload_config.version.minor = FLAGS_minor_version;
LOG(INFO) << "Using provided minor_version=" << FLAGS_minor_version;
}
// 检查 "-zlib_fingerprint" 参数
// 如果 IsZlibCompatible(zlib_fingerprint) 检查返回 ture, 则设置 imgdiff_allowed = true
// 目前还没有弄清楚 ""-zlib_fingerprint"" 参数的意义和用途
if (!FLAGS_zlib_fingerprint.empty()) {
if (utils::IsZlibCompatible(FLAGS_zlib_fingerprint)) {
payload_config.version.imgdiff_allowed = true;
} else {
LOG(INFO) << "IMGDIFF operation disabled due to fingerprint mismatch.";
}
}
// 输出生成的 payload 的类型:增量升级,全量升级?
if (payload_config.is_delta) {
LOG(INFO) << "Generating delta update";
} else {
LOG(INFO) << "Generating full update";
}
/*
* 1.7 检查验证 payload_config 内部的各项参数
*/
// From this point, all the options have been parsed.
if (!payload_config.Validate()) {
LOG(ERROR) << "Invalid options passed. See errors above.";
return 1;
}
/*
* 2. 根据 payload_config 设置生成 payload 文件
*/
// 根据 payload_config 的内容,生成 payload 文件
uint64_t metadata_size;
if (!GenerateUpdatePayloadFile(payload_config,
FLAGS_out_file,
FLAGS_private_key,
&metadata_size)) {
return 1;
}
// 如果有 "-out_metadata_size_file" 参数,则将 metadata size 输出到这个文件中
if (!FLAGS_out_metadata_size_file.empty()) {
string metadata_size_string = std::to_string(metadata_size);
CHECK(utils::WriteFile(FLAGS_out_metadata_size_file.c_str(),
metadata_size_string.data(),
metadata_size_string.size()));
}
return 0;
}
提取下上面注释中的重点,如下:
- 解析传入参数,设置 payload_config 结构体
- 1.1 解析 “partition_names” 参数
- 1.2 解析 “new_partitions” 参数,设置 payload_config.target.partitions
- 1.3 解析 “old_partitions” 参数,设置 payload_config.source.partitions
- 1.4 解析 “new_postinstall_config_file” 参数
- 1.5 设置 payload_config 的 hard_chunk_size, block_size
- 1.5 设置 payload_config.target.image_info 和 payload_config.source.image_info
- 1.6 设置 payload_config.version
- 1.7 检查验证 payload_config 内部的各项参数
- 根据 payload_config 设置, 调用 GenerateUpdatePayloadFile 生成 payload 文件
2.2 payload_config 结构
不过这里最大的问题就是 payload_config 对象的结构多层嵌套,非常复杂。 不过,再复杂的结构,也扛不住画图,然后对照着图来阅读代码。
我这里用一张图总结了 PayloadGenerationConfig 结构的内容:
其实在设置 payload_config 的属性过程中,我们只关心都有哪些成员变量,因此移除成员函数后的结构如下所示:
文字是一种线性结构,只有你理解了前面的文字,才能继续理解后续的内容。 但图则不同,图会将一个完整的结构呈现给你,有一种全局观,也可以只选择阅读关心的部分。
当代码或数据结构过于复杂时,建议换一种方式,使用图来展示,加深理解。
2.3 GenerateUpdatePayloadFile 函数
从最上层看,GenerateUpdatePayloadFile 函数才是真正干活的那个。
这个函数根据传入的 payload_config 结构的内容,遍历每一个需要操作的分区,选择一种合适的操作策略,生成该分区的 AnnotatedOperation, 按分区组织 AnnotatedOperation, 并将其输出到 payload 文件中。
// 文件: system/update_engine/payload_generator/delta_diff_generator.cc
bool GenerateUpdatePayloadFile(
const PayloadGenerationConfig& config,
const string& output_path,
const string& private_key_path,
uint64_t* metadata_size) {
// 检查 major 和 minor version
if (!config.version.Validate()) {
LOG(ERROR) << "Unsupported major.minor version: " << config.version.major
<< "." << config.version.minor;
return false;
}
/*
* 1. 调用 payload.Init 函数设置 manifest_ 结构的 version 以及 old_image_info 和 new_image_info
*/
// Create empty payload file object.
PayloadFile payload;
TEST_AND_RETURN_FALSE(payload.Init(config));
/*
* 2. 生成名为 "CrAU_temp_data.XXXXXX" 的临时文件
*/
const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
string temp_file_path;
int data_file_fd;
TEST_AND_RETURN_FALSE(
utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
// 执行 unlink 文件删除那件
// 实际上, 文件打开的情况下unlink()并不会立即删除,并且对文件依然可以进行读写操作,在进程结束之后文件会被删除
ScopedPathUnlinker temp_file_unlinker(temp_file_path);
TEST_AND_RETURN_FALSE(data_file_fd >= 0);
{
off_t data_file_size = 0;
// 用 data_file_fd 初始化 data_file_fd_closer, 在进程结束时关闭文件
ScopedFdCloser data_file_fd_closer(&data_file_fd);
// 使用文件描述符和 file size 初始化一个 FileWrite
BlobFileWriter blob_file(data_file_fd, &data_file_size);
// 再次检查增量升级时两个包是否一样大
if (config.is_delta) {
TEST_AND_RETURN_FALSE(config.source.partitions.size() ==
config.target.partitions.size());
}
PartitionConfig empty_part("");
/*
* 3. 遍历所有 target 分区进行操作
*/
for (size_t i = 0; i < config.target.partitions.size(); i++) {
// 全量升级时, old_part 为空
const PartitionConfig& old_part =
config.is_delta ? config.source.partitions[i] : empty_part;
// 打印分区信息,例如:
// Partition name: boot
// Partition size: 19456000
// Block count: 4750
const PartitionConfig& new_part = config.target.partitions[i];
LOG(INFO) << "Partition name: " << new_part.name;
LOG(INFO) << "Partition size: " << new_part.size;
LOG(INFO) << "Block count: " << new_part.size / config.block_size;
/*
* 3.1 选择 payload 生成使用的的策略 generator
* 1. old_part 不为空, 即差分升级的情况:
* 1a. 如果 version.minor = 1(kInPlaceMinorPayloadVersion), 使用 InplaceGenerator
* 1b. 其它情况使用 ABGenerator
* 2. old_part 为空, 即全量升级情况, 使用 FullUpdateGenerator
* 具体的 generator 后面专门分析,这里略过。
*/
// Select payload generation strategy based on the config.
unique_ptr<OperationsGenerator> strategy;
// We don't efficiently support deltas on squashfs. For now, we will
// produce full operations in that case.
if (!old_part.path.empty() &&
!utils::IsSquashfsFilesystem(new_part.path)) {
// Delta update.
if (config.version.minor == kInPlaceMinorPayloadVersion) {
LOG(INFO) << "Using generator InplaceGenerator().";
strategy.reset(new InplaceGenerator());
} else {
LOG(INFO) << "Using generator ABGenerator().";
strategy.reset(new ABGenerator());
}
} else {
LOG(INFO) << "Using generator FullUpdateGenerator().";
strategy.reset(new FullUpdateGenerator());
}
/*
* 3.2 使用 generator 的 GenerateOperations 操作分区,生成分区对应的 AnnotatedOperation
*/
vector<AnnotatedOperation> aops;
// Generate the operations using the strategy we selected above.
TEST_AND_RETURN_FALSE(strategy->GenerateOperations(config,
old_part,
new_part,
&blob_file,
&aops));
/*
* 3.3 过滤空操作, 例如增量升级时,同样的数据就会生成 Noop Operation
*/
// Filter the no-operations. OperationsGenerators should not output this
// kind of operations normally, but this is an extra step to fix that if
// happened.
diff_utils::FilterNoopOperations(&aops);
/*
* 3.4 将所有 AnnotatedOperation 更新到对应的 partition 数据中
*/
TEST_AND_RETURN_FALSE(payload.AddPartition(old_part, new_part, aops));
}
}
/*
* 4. 调用 payload.WritePayload 函数将临时文件 "CrAU_temp_data.XXXXXX" 的数据输出到 output_path 文件中
*/
LOG(INFO) << "Writing payload file...";
// Write payload file to disk.
TEST_AND_RETURN_FALSE(payload.WritePayload(output_path, temp_file_path,
private_key_path, metadata_size));
LOG(INFO) << "All done. Successfully created delta file with "
<< "metadata size = " << *metadata_size;
return true;
}
提取下注释中的重点,如下:
- 调用 payload.Init 函数设置 manifest_ 结构的 version 以及 old_image_info 和 new_image_info
- 生成名为 “CrAU_temp_data.XXXXXX” 的临时文件
- 遍历所有 target 分区进行操作
- 3.1 选择 payload 生成使用的的策略 generator
- 3.2 使用 generator 的 GenerateOperations 操作分区,生成分区对应的 AnnotatedOperation
- 3.3 过滤空操作, 例如增量升级时,同样的数据就会生成 Noop Operation
- 3.4 将所有 AnnotatedOperation 更新到对应的 partition 数据中
- 调用 payload.WritePayload 函数将临时文件 “CrAU_temp_data.XXXXXX” 的数据输出到 output_path 文件中
下面是 GenerateUpdatePayloadFile 中调用的一些函数注释
这里使用一个 PayloadGenerationConfig 结构的对象 config ,去设置 manifest_ 结构的 version 以及 old_image_info 和 new_image_info 成员。
// 文件: system/update_engine/payload_generator/payload_file.cc
bool PayloadFile::Init(const PayloadGenerationConfig& config) {
// 检查 config.version 的 major 和 minor 版本信息
TEST_AND_RETURN_FALSE(config.version.Validate());
// 设置 payload 的 major_version_ 版本
major_version_ = config.version.major;
// 设置 manifest_ 的 minor 版本
manifest_.set_minor_version(config.version.minor);
// 用 config.source.image_info 更新 manifest_ 的 old_image_info()
if (!config.source.ImageInfoIsEmpty())
*(manifest_.mutable_old_image_info()) = config.source.image_info;
// 用 config.target.image_info 更新 manifest_ 的 new_image_info()
if (!config.target.ImageInfoIsEmpty())
*(manifest_.mutable_new_image_info()) = config.target.image_info;
// 设置 manifest_ 的 block_size
manifest_.set_block_size(config.block_size);
return true;
}
diff_utils::FilterNoopOperations 函数
FilterNoopOperations 函数遍历所有操作,如果满足条件 IsNoopOperation , 则移除该操作。
void FilterNoopOperations(vector<AnnotatedOperation>* ops) {
ops->erase(
std::remove_if(
ops->begin(), ops->end(),
[](const AnnotatedOperation& aop){return IsNoopOperation(aop.op);}),
ops->end());
}
如果判断是 Noop Operation 的呢? 这里的条件是如果 op 的 type 为 MOVE, 并且 src_extends 和 dst_extends 一样。 至于 ExpandExtents 到底是什么,不是这里关心的重点,不再往下追溯。
// Returns true if |op| is a no-op operation that doesn't do any useful work
// (e.g., a move operation that copies blocks onto themselves).
bool IsNoopOperation(const InstallOperation& op) {
return (op.type() == InstallOperation::MOVE &&
ExpandExtents(op.src_extents()) == ExpandExtents(op.dst_extents()));
}
PayloadFile::AddPartition 函数
将所有的 AnnotatedOperation 操作和分区绑定在一起,并更新该分区新(new)旧(old)状态的 size 和 hash,这个 size 和 hash 在升级时会检查对比。
bool PayloadFile::AddPartition(const PartitionConfig& old_conf,
const PartitionConfig& new_conf,
const vector<AnnotatedOperation>& aops) {
// 检查是否是 ChomeOS 更新,这里因为是 Android, 略过。
// Check partitions order for Chrome OS
if (major_version_ == kChromeOSMajorPayloadVersion) {
const vector<const char*> part_order = { kLegacyPartitionNameRoot,
kLegacyPartitionNameKernel };
TEST_AND_RETURN_FALSE(part_vec_.size() < part_order.size());
TEST_AND_RETURN_FALSE(new_conf.name == part_order[part_vec_.size()]);
}
// 更新 part 的 name, 并添加所有的 aops 数据
Partition part;
part.name = new_conf.name;
part.aops = aops;
part.postinstall = new_conf.postinstall;
// 更新 old_part 和 new_part 的 size 和 hash 信息
// Initialize the PartitionInfo objects if present.
if (!old_conf.path.empty())
TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(old_conf,
&part.old_info));
TEST_AND_RETURN_FALSE(diff_utils::InitializePartitionInfo(new_conf,
&part.new_info));
part_vec_.push_back(std::move(part));
return true;
}
在升级时,如果现有分区的 size 和 hash,跟 payload 数据中的 old_partition_info.hash 比较。 在代码中,下载 payload 数据以后,解析 manifest 时会调用 VerifySourcePartitions 执行这个操作。
当然,升级完成后,也有可以将更新后新的分区 hash 同 payload 数据中的 new_partition_info.hash 比较。 不过代码中似乎并没有这个逻辑,如果你看到了,麻烦说一下,谢谢!
InitializePartitionInfo 函数
InitializePartitionInfo 函数的实际操作就是更新分区的 size 和 hash 信息。
/*
* 计算 part 文件,更新 info->size 和 info->hash
*/
bool InitializePartitionInfo(const PartitionConfig& part, PartitionInfo* info) {
// 设置 partition size
info->set_size(part.size);
HashCalculator hasher;
// 计算 partition 文件的哈希,例如用于计算 old_part 文件为 /tmp/boot.img.g93Yhs,则设置
// part_info->size=19456000
// part_info->hash=tVjyP4PyAa1TCYisd9NIGr0fdm4VtIrVz+w/Vrjvx5A=
TEST_AND_RETURN_FALSE(hasher.UpdateFile(part.path, part.size) ==
static_cast<off_t>(part.size));
TEST_AND_RETURN_FALSE(hasher.Finalize());
const brillo::Blob& hash = hasher.raw_hash();
info->set_hash(hash.data(), hash.size());
// 打印 size 和 hash 信息, 例如: /tmp/boot.img.g93Yhs: size=19456000 hash=tVjyP4PyAa1TCYisd9NIGr0fdm4VtIrVz+w/Vrjvx5A=
LOG(INFO) << part.path << ": size=" << part.size << " hash=" << hasher.hash();
return true;
}
2.4 PayloadFile::WritePayload 函数
在 GenerateUpdatePayloadFile 函数中,除了遍历分区去生成各个分区对应的 AnnotatedOperation 外,最最重要的就是就是最后一步,如何将这些生成的 AnnotatedOperation 写入到 payload 文件中。
函数 PayloadFile::WritePayload 就是做这个事情,这个函数的代码较长,这里单独分析:
// 文件: system/update_engine/payload_generator/payload_file.cc
bool PayloadFile::WritePayload(const string& payload_file,
const string& data_blobs_path,
const string& private_key_path,
uint64_t* metadata_size_out) {
/*
* 1. 创建临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX, 用于按顺序存放 AnnotatedOperation
*/
// Reorder the data blobs with the manifest_.
string ordered_blobs_path;
TEST_AND_RETURN_FALSE(utils::MakeTempFile(
"CrAU_temp_data.ordered.XXXXXX",
&ordered_blobs_path,
nullptr));
// 确保进程退出是删除临时文件
ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path);
/*
* 2. 更新每一个操作对应数据的 data_offset, data_length 和 data_sha256_hash,并确保 data_offset 顺序存放
*/
// 对之前生成的 data_blobs_path 文件中的 operation 顺序输出到新文件中
// 同时更新其对应的 data_offset, data_length 和 data_sha256_hash
TEST_AND_RETURN_FALSE(ReorderDataBlobs(data_blobs_path, ordered_blobs_path));
// 再次检查所有 install operation 数据是顺序存放的
// 即 op.data_offset 是依次递增的
// Check that install op blobs are in order.
uint64_t next_blob_offset = 0;
for (const auto& part : part_vec_) {
for (const auto& aop : part.aops) {
if (!aop.op.has_data_offset())
continue;
if (aop.op.data_offset() != next_blob_offset) {
LOG(FATAL) << "bad blob offset! " << aop.op.data_offset() << " != "
<< next_blob_offset;
}
next_blob_offset += aop.op.data_length();
}
}
// 清空 manifest_ 中原有的 install_operations, kernel_install_operations 和 partitions 向量数组
// Copy the operations and partition info from the part_vec_ to the manifest.
manifest_.clear_install_operations();
manifest_.clear_kernel_install_operations();
manifest_.clear_partitions();
/*
* 3. 遍历分区,更新 manifest_ 结构
*/
// 遍历所有分区,将分区的 install operations 更新到 manifest_ 结构中
for (const auto& part : part_vec_) {
// 对 major_version 为 2(kBrilloMajorPayloadVersion), 即 Android 的情况
if (major_version_ == kBrilloMajorPayloadVersion) {
/*
* 3.1 更新分区的 post install 相关内容
*/
// manifest_ 结构的 partitions 数组中新增一个名为 part.name 的项
PartitionUpdate* partition = manifest_.add_partitions();
partition->set_partition_name(part.name);
// 使用 part 数据更新 manifest_ 结构的 partition 数据:
// 更新 part.postinstall.run --> partition.run_postinstall
// 更新 part.postinstall.path --> partition.postinstall_path
// 更新 part.postinstall.filesystem_type --> partition.filesystem_type
// 更新 part.postinstall.optional --> partition.postinstall_optional
if (part.postinstall.run) {
partition->set_run_postinstall(true);
if (!part.postinstall.path.empty())
partition->set_postinstall_path(part.postinstall.path);
if (!part.postinstall.filesystem_type.empty())
partition->set_filesystem_type(part.postinstall.filesystem_type);
partition->set_postinstall_optional(part.postinstall.optional);
}
/*
* 3.2 遍历分区的所有操作, 逐个添加到 partition.operations 向量数组中
*/
for (const AnnotatedOperation& aop : part.aops) {
*partition->add_operations() = aop.op;
}
/*
* 3.3 更新分区的 old_partition_info 和 new_partition_info
*/
if (part.old_info.has_size() || part.old_info.has_hash())
*(partition->mutable_old_partition_info()) = part.old_info;
if (part.new_info.has_size() || part.new_info.has_hash())
*(partition->mutable_new_partition_info()) = part.new_info;
} else { // 对于 major_version_ 为 1, 即 ChomeOS 升级的情况,这里暂时不管
// major_version_ == kChromeOSMajorPayloadVersion
if (part.name == kLegacyPartitionNameKernel) {
for (const AnnotatedOperation& aop : part.aops)
*manifest_.add_kernel_install_operations() = aop.op;
if (part.old_info.has_size() || part.old_info.has_hash())
*manifest_.mutable_old_kernel_info() = part.old_info;
if (part.new_info.has_size() || part.new_info.has_hash())
*manifest_.mutable_new_kernel_info() = part.new_info;
} else {
for (const AnnotatedOperation& aop : part.aops)
*manifest_.add_install_operations() = aop.op;
if (part.old_info.has_size() || part.old_info.has_hash())
*manifest_.mutable_old_rootfs_info() = part.old_info;
if (part.new_info.has_size() || part.new_info.has_hash())
*manifest_.mutable_new_rootfs_info() = part.new_info;
}
}
}
/*
* 4. 如果有传递 private_key, 计算 signature 数据的大小,并更新 signature_offset 和 signature_size 信息
*/
// Signatures appear at the end of the blobs. Note the offset in the
// manifest_.
// 如果有传递 private_key,则:
// 1. 使用它对一个字符的 'x' 数据签名,目的是获取签名的长度
// 2. 使用签名长度更新 manifest_ 中的 signatures_offset 和 signatures_size 数据
uint64_t signature_blob_length = 0;
if (!private_key_path.empty()) {
TEST_AND_RETURN_FALSE(
PayloadSigner::SignatureBlobLength(vector<string>(1, private_key_path),
&signature_blob_length));
PayloadSigner::AddSignatureToManifest(
next_blob_offset, signature_blob_length,
major_version_ == kChromeOSMajorPayloadVersion, &manifest_);
}
/*
* 5. 串行化 manifest 结构成 protobuf 格式数据
*/
// 由于 manifest_ 是 protobuf 结构,输出时需要序列化,
// 这里将其序列化到字符串 serialized_manifest 中,方便后面计算大小和输出
// Serialize protobuf
string serialized_manifest;
TEST_AND_RETURN_FALSE(manifest_.AppendToString(&serialized_manifest));
/*
* 6. 更新 metdata_size
*/
// 计算 metadata 的大小
// metadata 包括: 4(magic)+8(file_format_version)+8(manifest_size)
// 另外还有 4 bytes(metadata_signature_size) 待定
uint64_t metadata_size =
sizeof(kDeltaMagic) + 2 * sizeof(uint64_t) + serialized_manifest.size();
// 打开 payload 文件写入头部数据
LOG(INFO) << "Writing final delta file header...";
DirectFileWriter writer;
TEST_AND_RETURN_FALSE_ERRNO(writer.Open(payload_file.c_str(),
O_WRONLY | O_CREAT | O_TRUNC,
0644) == 0);
ScopedFileWriterCloser writer_closer(&writer);
/*
* 7. 更新 payload 的 metadata 数据
*/
/*
* 7.1 写入 4 字节 magic("CrAU")
*/
// Write header
TEST_AND_RETURN_FALSE(writer.Write(kDeltaMagic, sizeof(kDeltaMagic)));
/*
* 7.2 写入 写入 8 字节 major_version
*/
// Write major version number
TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer, major_version_));
/*
* 7.3 写入 8 字节 manifest_size
*/
// Write protobuf length
TEST_AND_RETURN_FALSE(WriteUint64AsBigEndian(&writer,
serialized_manifest.size()));
/*
* 7.4 计算 metadata_signature_size,并写入到文件中
*/
// 对 major_version = 2(kBrilloMajorPayloadVersion),即 Android 升级的情况,
// 写入 metadata_signature_size
// Write metadata signature size.
uint32_t metadata_signature_size = 0;
if (major_version_ == kBrilloMajorPayloadVersion) {
// Metadata signature has the same size as payload signature, because they
// are both the same kind of signature for the same kind of hash.
// 将 metadata_signature_size 存储大端格式的 signature size
uint32_t metadata_signature_size = htobe32(signature_blob_length);
// 写入 metadata_signature_size
TEST_AND_RETURN_FALSE(writer.Write(&metadata_signature_size,
sizeof(metadata_signature_size)));
// 原来的 metadata_size 没有包含 metadata_signature_size 成员的 4 bytes 数据
metadata_size += sizeof(metadata_signature_size);
// 将大端格式的 metadata_signature_size 转换成原来的数据(原来可能是大端也可能是小端,取悦于机器的运行状态)
// Set correct size instead of big endian size.
metadata_signature_size = signature_blob_length;
}
/*
* 7.5. 写入按 protbuf 格式序列化以后的 manifest 数据
*/
// Write protobuf
LOG(INFO) << "Writing final delta file protobuf... "
<< serialized_manifest.size();
TEST_AND_RETURN_FALSE(writer.Write(serialized_manifest.data(),
serialized_manifest.size()));
/*
* 8. 如果有传递 private_key, 计算 metadata 的签名,并写入到 payload 文件中
*/
// 具体操作:
// 如果是 Android 升级,并且传递了 private_key, 则:
// 1. 使用 private_key 对 payload 文件的 metadata 数据签名
// 2. 将 metadata 的签名写入到 payload 文件中
// 如果这里没有传入 private_key,则后续处理时需要在 payload 文件中插入 metdata_signature 数据
// 这里最好的方式是,如果传入 private_key,则计算签名,否则预留一个签名的占位符
// 问题是不同的签名参数,其签名长度不知道,因此不知道应该预留多大的空位给签名数据
// Write metadata signature blob.
if (major_version_ == kBrilloMajorPayloadVersion &&
!private_key_path.empty()) {
brillo::Blob metadata_hash, metadata_signature;
TEST_AND_RETURN_FALSE(HashCalculator::RawHashOfFile(payload_file,
metadata_size,
&metadata_hash));
TEST_AND_RETURN_FALSE(
PayloadSigner::SignHashWithKeys(metadata_hash,
vector<string>(1, private_key_path),
&metadata_signature));
TEST_AND_RETURN_FALSE(writer.Write(metadata_signature.data(),
metadata_signature.size()));
}
/*
* 9. 遍历所有的 AnnotatedOperation, 每次读取其 1M 数据添加到 payload 文件中
*/
// Append the data blobs
LOG(INFO) << "Writing final delta file data blobs...";
int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
ScopedFdCloser blobs_fd_closer(&blobs_fd);
TEST_AND_RETURN_FALSE(blobs_fd >= 0);
for (;;) {
vector<char> buf(1024 * 1024);
ssize_t rc = read(blobs_fd, buf.data(), buf.size());
if (0 == rc) {
// EOF
break;
}
TEST_AND_RETURN_FALSE_ERRNO(rc > 0);
TEST_AND_RETURN_FALSE(writer.Write(buf.data(), rc));
}
/*
* 10. 如果有传递 private_key, 计算 payload 的签名,并写入到 payload 文件中
*/
// 如果传递了 private_key, 则:
// 1. 使用 private_key 对 payload 文件的 payload 数据签名
// 2. 将 payload 数据写入到 payload 文件中
// Write payload signature blob.
if (!private_key_path.empty()) {
LOG(INFO) << "Signing the update...";
brillo::Blob signature_blob;
// 计算 payload 数据的签名
TEST_AND_RETURN_FALSE(PayloadSigner::SignPayload(
payload_file,
vector<string>(1, private_key_path),
metadata_size,
metadata_signature_size,
metadata_size + metadata_signature_size + manifest_.signatures_offset(),
&signature_blob));
// 将 payload 数据的签名写入到 payload 文件中
// 但是在 payload 数据签名的前面,还有个 payload_signature_message_size, 这里没看到更新,是 bug 吗?
TEST_AND_RETURN_FALSE(writer.Write(signature_blob.data(),
signature_blob.size()));
}
/*
* 11. 遍历所有 AnnotatedOperation, 打印每个操作的信息
*/
/*
* 打印一大堆操作信息:
* 0.00% 0 SOURCE_COPY <identical-blocks>:0
* 0.00% 0 SOURCE_COPY <identical-blocks>:0
* ...
* 0.00% 0 SOURCE_COPY <identical-blocks>:0
* 0.00% 48 REPLACE_BZ <system-operation-164>
* 0.00% 48 REPLACE_BZ <system-operation-165>
* ...
* 0.79% 2097152 REPLACE <system-operation-365>
* 0.79% 2097152 REPLACE <system-operation-40>
* 100.00% 263861305 <total>
*/
ReportPayloadUsage(metadata_size);
/*
* 12. 返回 metadata_size
*/
*metadata_size_out = metadata_size;
return true;
}
提取这段代码的注释重点, 看看都做了什么操作:
- 创建临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX, 用于按顺序存放 AnnotatedOperation
- 更新每一个操作对应数据的 data_offset, data_length 和 data_sha256_hash,并确保 data_offset 顺序存放
- 遍历分区,更新 manifest_ 结构
- 3.1 更新分区的 post install 相关内容
- 3.2 遍历分区的所有操作, 逐个添加到 partition.operations 向量数组中
- 3.3 更新分区的 old_partition_info 和 new_partition_info
- 如果有传递 private_key, 计算 signature 数据的大小,并更新 signature_offset 和 signature_size 信息
- 串行化 manifest 结构成 protobuf 格式数据
- 更新 metdata_size
- 更新 payload 的 metadata 数据
- 7.1 写入 4 字节 magic(“CrAU”)
- 7.2 写入 写入 8 字节 major_version
- 7.3 写入 8 字节 manifest_size
- 7.4 计算 metadata_signature_size,并写入到文件中
- 7.5 写入按 protbuf 格式序列化以后的 manifest 数据
- 如果有传递 private_key, 计算 metadata 的签名,并写入到 payload 文件中
- 遍历所有的 AnnotatedOperation, 每次读取其 1M 数据添加到 payload 文件中
- 如果有传递 private_key, 计算 payload 的签名,并写入到 payload 文件中
- 遍历所有 AnnotatedOperation, 打印每个操作的信息
- 返回 metadata_size
PayloadFile::ReorderDataBlobs 函数
bool PayloadFile::ReorderDataBlobs(
const string& data_blobs_path,
const string& new_data_blobs_path) {
// 打开旧文件: /tmp/CrAU_temp_data.XXXXXX
int in_fd = open(data_blobs_path.c_str(), O_RDONLY, 0);
TEST_AND_RETURN_FALSE_ERRNO(in_fd >= 0);
ScopedFdCloser in_fd_closer(&in_fd);
// 打开新文件: /tmp/CrAU_temp_data.ordered.XXXXXX
DirectFileWriter writer;
TEST_AND_RETURN_FALSE(
writer.Open(new_data_blobs_path.c_str(),
O_WRONLY | O_TRUNC | O_CREAT,
0644) == 0);
ScopedFileWriterCloser writer_closer(&writer);
uint64_t out_file_size = 0;
// 外层循环遍历所有的分区
for (auto& part : part_vec_) {
// 内层循环遍历分区内的所有操作
for (AnnotatedOperation& aop : part.aops) {
// 如果 operation 操作有 data_offset 成员,则丢弃该数据
if (!aop.op.has_data_offset())
continue;
// 确保 operation 操作有 data_length() 成员
CHECK(aop.op.has_data_length());
// 用 operation 操作中数据的长度初始化一个 buf 缓冲
brillo::Blob buf(aop.op.data_length());
// pread 函数带偏移量地原子的从文件中读取数据
// 函数原型: ssize_t pread(intfd, void *buf, size_t count, off_t offset);
// 这里从旧文件中 op.data_offset 指定的位置读取长度为 op.data_length 的数据到 buf 缓冲中
ssize_t rc = pread(in_fd, buf.data(), buf.size(), aop.op.data_offset());
TEST_AND_RETURN_FALSE(rc == static_cast<ssize_t>(buf.size()));
// 计算 operation 操作中数据的 sha256 哈希到 op.data_sha256_hash 中
// Add the hash of the data blobs for this operation
TEST_AND_RETURN_FALSE(AddOperationHash(&aop.op, buf));
// 将 out_file_size 更新为 op.data_offset 值
aop.op.set_data_offset(out_file_size);
// 将 buf 中的数据写入到新文件中,数据的 offset 和 size 记录在 op.data_offset 和 op.data_length 中
TEST_AND_RETURN_FALSE(writer.Write(buf.data(), buf.size()));
// 更新下一个 operation 的 offset
out_file_size += buf.size();
}
}
return true;
}
2.5 AnnotatedOperation 结构
看了前面的代码,你有可能会好奇,所有分区最基本的操作都是 InstallOperation, 为什么我们看到的都是 AnnotatedOperation。
从 AnnotatedOperation 的定义可见,这个结构实际上是对 InstallOperation 的封装:
// 文件: system/update_engine/payload_generator/annotated_operation.h
struct AnnotatedOperation {
// The name given to the operation, for logging and debugging purposes only.
// This normally includes the path to the file and the chunk used, if any.
std::string name;
// The InstallOperation, as defined by the protobuf.
InstallOperation op;
// Writes |blob| to the end of |blob_file|. It sets the data_offset and
// data_length in AnnotatedOperation to match the offset and size of |blob|
// in |blob_file|.
bool SetOperationBlob(const brillo::Blob& blob, BlobFileWriter* blob_file);
};
基本上各个地方都操作的都是 AnnotatedOperation 内部封装的 InstallOperation, 在生成 payload.bin 文件的最后,调用 ReportPayloadUsage 函数打印相关信息。
3. payload 生成总结
本文从比较高的层次上分析了生成 payload 文件的源码,这里总结如下:
3.1 命令行层面
命令行使用 ota_from_target_files 工具通过一下命令生成 update.zip 包:
$./build/tools/releasetools/ota_from_target_files \
-i dist/old.zip \
dist/new.zip \
update.zip
在脚本中会解包 old.zip 和 new.zip,这两个包是通过 make otapackage 命令生成的。 解包后会得到 boot.img 和 system.img 的临时文件,然后 delta_generator 操作这些临时文件生成 payload.bin 文件。
delta_generator -out_file=dist/payload-unsigned.bin \
-partition_names=boot:system \
-new_partitions=/tmp/boot.img.new:/tmp/system.raw.new \
-old_partitions=/tmp/boot.img.old:/tmp/system.raw.old \
--minor_version=3 --major_version=2
3.2 delta_generator 源码
在 delta_generator 的 Main 函数中,主要做了两件事:
- 解析参数,生成 payload_config 结构;
- 基于 payload_config 结构中的内容,调用 GenerateUpdatePayloadFile 去实现功能;
payload_config 结构比较复杂,可以配合下图阅读代码:
在 GenerateUpdatePayloadFile 函数中,根据 partition_names 数组,遍历要操作的分区。
在遍历分区一开始,就根据分区和 minor 版本号,选择不同的生成策略来产生数据。
- 如果 minor 版本号是 kInPlaceMinorPayloadVersion, 则选用 InplaceGenerator 策略;
- 如果同时提供了新旧两个分区,则选用 ABGenerator 产生差分增量数据;
- 如果只提供了新分区,但没有旧分区,则选用 FullUpdateGenerator 产生全量数据;
然后调用所用 Generator 的 GenerateOperations 操作,检查分区内部的每一个 block,然后产生一大堆 AnnotatedOperation 数据。AnnotatedOperation 实际上是对 InstallOperation 的简单封装,因此也就是对每一个 block 产生了 InstallOperation 数据,存放在临时文件/tmp/CrAU_temp_data.XXXXXX 中。
然后遍历所有 AnnotatedOperation, 筛选出没有使用的空操作,将剩余的 AnnotatedOperation 数据指针存放在 Partition 数组中。换句话说,只需要找到分区的 aops 数组,并对其遍历就能还原每一个操作。
对 partition_names 数组中的每一个分区都执行遍历生成 AnnotatedOperation 数据。
遍历完成后,调用 payload.WritePayload 函数将生成的数据输出到指定 payload 文件中。
WritePayload 具体操作上大致有以下这些步骤:
- 提取临时文件
/tmp/CrAU_temp_data.XXXXXX 中的 AnnotatedOperation 数据,根据操作位置的 data_offset 按从小到大顺序存放到临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX 中; - 记录每一个 AnnotatedOperation 的信息,包括 data_offset, data_length 和 data_sha256_hash 存放到分区的 install operation 数组中;
因此,对每一个 operation, 其信息(offset, length 和 hash) 是存放在分区数组中的,但实际操作的数据其实是按起始位置从小到大存放在临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX 中。
- 更新 manifest 数据, 并将其按照 protobuf 格式序列化;
- 开始往输出文件中写入数据:
4.1 写入 payload 头部的 header 数据(包括 magic, major_version, manifest_size, metadata_signature_size等) 4.2 写入序列化的 manifest 数据(保罗payload signature offset 和 size, 新旧分区的 image_info,以及按分区组织的 AnnotatedOperation 信息等); 4.3 如果有提供签名用的私钥,则计算前面这部分写入数据的签名,并写入到 payload 文件中; - 顺序写入从
/tmp/CrAU_temp_data.ordered.XXXXXX 文件中提取的 AnnotatedOperation 数据; - 如果有提供签名用的私钥,则计算 payload 文件的签名(计算范围不包含 metadata 和 payload 的签名自身),然后写入到 payload 文件末尾;
- 打印输出所有的 AnnotatedOperation 数据信息;
通过以上操作,就将所有数据组织输出到了 payload.bin 文件中,后续步骤会进一步对 payload.bin 进行操作,包括生成 payload 和 metadata 的哈希并签名, 提取 payload 和 metadata 的 property 数据等。
3.2 delta_generator 生成 payload 的调用关系
下面是 delta_generator 生成 payload 的调用关系:
ota_from_target_files -i dist/old.zip dist/new.zip update.zip
--> delta_generator -out_file=payload.bin -partition_names=boot:system -new_partitions=/tmp/boot.img.new:/tmp/system.raw.new -old_partitions=/tmp/boot.img.old:/tmp/system.raw.old --minor_version=3 --major_version=2
--> GenerateUpdatePayloadFile(config)
--> payload.Init(config)
--> 遍历分区进行操作
--> InplaceGenerator/ABGenerator/FullUpdateGenerator.GenerateOperations(config, old_part, new_part, ...)
--> FilterNoopOperations
--> payload.AddPartition
--> InitializePartitionInfo(old_part)
--> HashCalculator(old_part), 计算 old_part 的哈希
--> InitializePartitionInfo(new_part)
--> HashCalculator(new_part), 计算 new_part 的哈希
--> payload.WritePayload
--> ReorderDataBlobs
--> AddSignatureToManifest
--> manifest_.AppendToString(&serialized_manifest)
--> Write(kDeltaMagic)
--> Write(major_version_)
--> Write(serialized_manifest.size)
--> Write(metadata_signature_size)
--> Write(serialized_manifest.data)
--> HashCalculator::RawHashOfFile(metadata, &metadata_hash)
--> PayloadSigner::SignHashWithKeys(metadata_hash, private_key_path, &metadata_signature)
--> Write(metadata_signature.data)
--> Write(major_version_)
--> Write(serialized_manifest.size)
--> Write(metadata_signature_size)
--> Write(serialized_manifest.data)
--> Write(operation.data)
--> PayloadSigner::SignPayload(payload, &signature_blob)
--> Write(signature_blob.data)
--> ReportPayloadUsage(metadata_size)
上面这里的调用关系部分地方不是原始的函数名和参数的,大致表示下调用的逻辑和意义。
3.3 下一篇的内容
本篇分析了:
- 生成 payload 参数,
- 简述根据不同的情况选用不同策略的 Generator 对每一个分区的每一个 block 生成相应的 Install Operation,
- 然后筛选重组这些 Install Operation 的数据, 将 Install Operation 的信息按分区存放在 manifest 中,
- 最后输出 manifest 和 install operation 数据。
但第 2 步中,对于不同策略的 Generator 对每一个分区的每一个 block 到底是如何操作的并没有进一步分析。 下一篇将详细分析 3 种不同策略的 Generator 操作。
4. 其它
洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。
所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:
- 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
- 一个Android OTA的讨论组,请说明加Android OTA群。
- 一个git和repo的讨论组,请说明加git和repo群。
在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:
洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:
|