IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android Update Engine分析(十四) 生成 payload 数据 -> 正文阅读

[移动开发]Android Update Engine分析(十四) 生成 payload 数据

Android Update Engine 分析(十四) 生成 payload 数据

相关文章:

Android A/B System 分析系列

Android Update Engine 分析系列

在《Android Update Engine 分析(九)delta_generator 工具的 6 种操作》中提到了,delta_generator 工具提供的 6 种操作,分别是:

  1. 生成 payload 和 metadata 数据的哈希值
  2. 更新 payload 和 metadata 数据的签名
  3. 使用公钥验证 payload 和 metadata 数据的签名
  4. 提取 payload 文件的 property 数据
  5. 对 old image 打 delta 补丁
  6. 生成 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 数据:

# ota_from_target_files 内调用 brillo_update_payload
# 基于 new.zip 和 old.zip 生成 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:

# 将 new.zip 和 old.zip 中的 boot.img 和 system.img 解包出来, 用于生成 payload-unsigned.bin
# brillo_update_payload 内调用 delta_generator
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 的代码有点长,似乎还挺复杂,不过按照功能划分,只做了两件事:

  1. 解析传入参数,设置 payload_config 结构体,用于后期指导如何生成 payload 文件;
  2. 根据 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;
}

提取下上面注释中的重点,如下:

  1. 解析传入参数,设置 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 内部的各项参数
  1. 根据 payload_config 设置, 调用 GenerateUpdatePayloadFile 生成 payload 文件

2.2 payload_config 结构

不过这里最大的问题就是 payload_config 对象的结构多层嵌套,非常复杂。
不过,再复杂的结构,也扛不住画图,然后对照着图来阅读代码。

我这里用一张图总结了 PayloadGenerationConfig 结构的内容:

PayloadGenerationConfig 结构示意图
其实在设置 payload_config 的属性过程中,我们只关心都有哪些成员变量,因此移除成员函数后的结构如下所示:
简化版 PayloadGenerationConfig 结构示意图

文字是一种线性结构,只有你理解了前面的文字,才能继续理解后续的内容。
但图则不同,图会将一个完整的结构呈现给你,有一种全局观,也可以只选择阅读关心的部分。

当代码或数据结构过于复杂时,建议换一种方式,使用图来展示,加深理解。

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;
}

提取下注释中的重点,如下:

  1. 调用 payload.Init 函数设置 manifest_ 结构的 version 以及 old_image_info 和 new_image_info
  2. 生成名为 “CrAU_temp_data.XXXXXX” 的临时文件
  3. 遍历所有 target 分区进行操作
    • 3.1 选择 payload 生成使用的的策略 generator
    • 3.2 使用 generator 的 GenerateOperations 操作分区,生成分区对应的 AnnotatedOperation
    • 3.3 过滤空操作, 例如增量升级时,同样的数据就会生成 Noop Operation
    • 3.4 将所有 AnnotatedOperation 更新到对应的 partition 数据中
  4. 调用 payload.WritePayload 函数将临时文件 “CrAU_temp_data.XXXXXX” 的数据输出到 output_path 文件中

下面是 GenerateUpdatePayloadFile 中调用的一些函数注释

  • PayloadFile::Init 函数

这里使用一个 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;
}

提取这段代码的注释重点, 看看都做了什么操作:

  1. 创建临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX, 用于按顺序存放 AnnotatedOperation
  2. 更新每一个操作对应数据的 data_offset, data_length 和 data_sha256_hash,并确保 data_offset 顺序存放
  3. 遍历分区,更新 manifest_ 结构
    • 3.1 更新分区的 post install 相关内容
    • 3.2 遍历分区的所有操作, 逐个添加到 partition.operations 向量数组中
    • 3.3 更新分区的 old_partition_info 和 new_partition_info
  4. 如果有传递 private_key, 计算 signature 数据的大小,并更新 signature_offset 和 signature_size 信息
  5. 串行化 manifest 结构成 protobuf 格式数据
  6. 更新 metdata_size
  7. 更新 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 数据
  8. 如果有传递 private_key, 计算 metadata 的签名,并写入到 payload 文件中
  9. 遍历所有的 AnnotatedOperation, 每次读取其 1M 数据添加到 payload 文件中
  10. 如果有传递 private_key, 计算 payload 的签名,并写入到 payload 文件中
  11. 遍历所有 AnnotatedOperation, 打印每个操作的信息
  12. 返回 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 函数中,主要做了两件事:

  1. 解析参数,生成 payload_config 结构;
  2. 基于 payload_config 结构中的内容,调用 GenerateUpdatePayloadFile 去实现功能;

payload_config 结构比较复杂,可以配合下图阅读代码:
PayloadGenerationConfig 结构示意图

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 具体操作上大致有以下这些步骤:

  1. 提取临时文件/tmp/CrAU_temp_data.XXXXXX 中的 AnnotatedOperation 数据,根据操作位置的 data_offset 按从小到大顺序存放到临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX 中;
  2. 记录每一个 AnnotatedOperation 的信息,包括 data_offset, data_length 和 data_sha256_hash 存放到分区的 install operation 数组中;

因此,对每一个 operation, 其信息(offset, length 和 hash) 是存放在分区数组中的,但实际操作的数据其实是按起始位置从小到大存放在临时文件 /tmp/CrAU_temp_data.ordered.XXXXXX 中。

  1. 更新 manifest 数据, 并将其按照 protobuf 格式序列化;
  2. 开始往输出文件中写入数据:
    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 文件中;
  3. 顺序写入从 /tmp/CrAU_temp_data.ordered.XXXXXX 文件中提取的 AnnotatedOperation 数据;
  4. 如果有提供签名用的私钥,则计算 payload 文件的签名(计算范围不包含 metadata 和 payload 的签名自身),然后写入到 payload 文件末尾;
  5. 打印输出所有的 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 下一篇的内容

本篇分析了:

  1. 生成 payload 参数,
  2. 简述根据不同的情况选用不同策略的 Generator 对每一个分区的每一个 block 生成相应的 Install Operation,
  3. 然后筛选重组这些 Install Operation 的数据, 将 Install Operation 的信息按分区存放在 manifest 中,
  4. 最后输出 manifest 和 install operation 数据。

但第 2 步中,对于不同策略的 Generator 对每一个分区的每一个 block 到底是如何操作的并没有进一步分析。
下一篇将详细分析 3 种不同策略的 Generator 操作。

4. 其它

洛奇工作中常常会遇到自己不熟悉的问题,这些问题可能并不难,但因为不了解,找不到人帮忙而瞎折腾,往往导致浪费几天甚至更久的时间。

所以我组建了几个微信讨论群(记得微信我说加哪个群,如何加微信见后面),欢迎一起讨论:

  • 一个密码编码学讨论组,主要讨论各种加解密,签名校验等算法,请说明加密码学讨论群。
  • 一个Android OTA的讨论组,请说明加Android OTA群。
  • 一个git和repo的讨论组,请说明加git和repo群。

在工作之余,洛奇尽量写一些对大家有用的东西,如果洛奇的这篇文章让您有所收获,解决了您一直以来未能解决的问题,不妨赞赏一下洛奇,这也是对洛奇付出的最大鼓励。扫下面的二维码赞赏洛奇,金额随意:

收钱码

洛奇自己维护了一个公众号“洛奇看世界”,一个很佛系的公众号,不定期瞎逼逼。公号也提供个人联系方式,一些资源,说不定会有意外的收获,详细内容见公号提示。扫下方二维码关注公众号:

公众号

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-01 20:43:51  更:2022-02-01 20:46:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 13:31:27-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码