• update_engine-FilesystemVerifierAction和PostinstallRunnerAction


    在介绍完了DownloadAction之后,还剩下FilesystemVerifierAction和PostinstallRunnerAction,下面开始对其进行分析。

    FilesystemVerifierAction

    在数据下载完成后,在DownloadAction中会切换到FilesystemVerifierAction

    void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
      if (writer_) {
       ........
      // Write the path to the output pipe if we're successful.
      if (code == ErrorCode::kSuccess && HasOutputPipe())
        SetOutputObject(install_plan_);
      processor_->ActionComplete(this, code);
    }

    最后的ActionComplete会开始执行FilesystemVerifierAction。

    src/system/update_engine/payload_consumer/filesystem_verifer_action.cc

     1 void FilesystemVerifierAction::PerformAction() {
     2   // Will tell the ActionProcessor we've failed if we return.
     3   ScopedActionCompleter abort_action_completer(processor_, this);
     4 
     5   if (!HasInputObject()) {
     6     LOG(ERROR) << "FilesystemVerifierAction missing input object.";
     7     return;
     8   }
     9   install_plan_ = GetInputObject();   //获取上一个Action传过来的install_plan_
    10 
    11   if (install_plan_.partitions.empty()) {
    12     LOG(INFO) << "No partitions to verify.";
    13     if (HasOutputPipe())
    14       SetOutputObject(install_plan_);
    15     abort_action_completer.set_code(ErrorCode::kSuccess);
    16     return;
    17   }
    18 
    19   StartPartitionHashing();      //开始计算分区的hash
    20   abort_action_completer.set_should_complete(false);
    21 }

      接着看StartPartitionHashing

     1 void FilesystemVerifierAction::StartPartitionHashing() {
     2   if (partition_index_ == install_plan_.partitions.size()) {       //判断是否验证到了最后一个分区
     3     Cleanup(ErrorCode::kSuccess);
     4     return;
     5   }
     6   InstallPlan::Partition& partition =
     7       install_plan_.partitions[partition_index_];
     8 
     9   string part_path;         
    10   switch (verifier_step_) {                    //默认值是KVerifyTargetHash
    11     case VerifierStep::kVerifySourceHash:
    12       part_path = partition.source_path;
    13       remaining_size_ = partition.source_size;
    14       break;
    15     case VerifierStep::kVerifyTargetHash:
    16       part_path = partition.target_path;         //分区的路径
    17       remaining_size_ = partition.target_size;   //大小
    18       break;
    19   }
    20   LOG(INFO) << "Hashing partition " << partition_index_ << " ("
    21             << partition.name << ") on device " << part_path;
    22   if (part_path.empty())
    23     return Cleanup(ErrorCode::kFilesystemVerifierError);
    24 
    25   brillo::ErrorPtr error;
    26   src_stream_ = brillo::FileStream::Open(             //打开对应的分区文件
    27       base::FilePath(part_path),
    28       brillo::Stream::AccessMode::READ,
    29       brillo::FileStream::Disposition::OPEN_EXISTING,
    30       &error);
    31 
    32   if (!src_stream_) {
    33     LOG(ERROR) << "Unable to open " << part_path << " for reading";
    34     return Cleanup(ErrorCode::kFilesystemVerifierError);
    35   }
    36 
    37   buffer_.resize(kReadFileBufferSize);   //重置缓存区的大小
    38   read_done_ = false;                    //未被读取完成
    39   hasher_.reset(new HashCalculator());   //设置HashCalculator
    40 
    41   // Start the first read.
    42   ScheduleRead();               //开始读取
    43 }

     首先判断是否验证的分区的所有hash,如果验证完成了,调用CleanUp做最后的工作。

    CleanUp

     1 void FilesystemVerifierAction::Cleanup(ErrorCode code) {
     2   src_stream_.reset();
     3   // This memory is not used anymore.
     4   buffer_.clear();
     5 
     6   if (cancelled_)
     7     return;
     8   if (code == ErrorCode::kSuccess && HasOutputPipe())
     9     SetOutputObject(install_plan_);
    10   processor_->ActionComplete(this, code);
    11 }

    可以看到主要就是清空缓存区,设置install_plan_,切换到下一个Action。如果没有验证完成,就获取要验证的分区路径和大小,这个大小只是要验证的大小,不一定是分区的真正大小。对于镜像文件而言1G的大小能被安装在2G的分区上。接下来调用ScheduleRead()开始进行验证。

    ScheduleRead()

     1 void FilesystemVerifierAction::ScheduleRead() {
     2   size_t bytes_to_read = std::min(static_cast(buffer_.size()), 
     3                                   remaining_size_);  //获取要读取数据的大小
     4   if (!bytes_to_read) {   //读取完成
     5     OnReadDoneCallback(0);
     6     return;
     7   }
     8 
     9   bool read_async_ok = src_stream_->ReadAsync(
    10     buffer_.data(),
    11     bytes_to_read,
    12     base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
    13                base::Unretained(this)),
    14     base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
    15                base::Unretained(this)),
    16     nullptr);  //开始读取
    17 
    18   if (!read_async_ok) {
    19     LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
    20     Cleanup(ErrorCode::kError);
    21   }
    22 }

    获取读取数据的真实大小,开始读取数据。

     1 void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
     2   if (bytes_read == 0) {        //读取完成
     3     read_done_ = true;
     4   } else {
     5     remaining_size_ -= bytes_read;  
     6     CHECK(!read_done_);                                     
     7     if (!hasher_->Update(buffer_.data(), bytes_read)) {   //计算hash
     8       LOG(ERROR) << "Unable to update the hash.";
     9       Cleanup(ErrorCode::kError);
    10       return;
    11     }
    12   }
    13 
    14   // We either terminate the current partition or have more data to read.
    15   if (cancelled_)
    16     return Cleanup(ErrorCode::kError);
    17 
    18   if (read_done_ || remaining_size_ == 0) {
    19     if (remaining_size_ != 0) {
    20       LOG(ERROR) << "Failed to read the remaining " << remaining_size_
    21                  << " bytes from partition "
    22                  << install_plan_.partitions[partition_index_].name;
    23       return Cleanup(ErrorCode::kFilesystemVerifierError);
    24     }
    25     return FinishPartitionHashing();   //计算完成后
    26   }
    27   ScheduleRead();   //如果没有计算完成,继续计读取计算
    28 }

    在这个方法中会对读取的数据进行hash计算,每次计算其实都是基于前一次的计算结果来进行的,不然就会有太对的数据加载到内存中,导致内存不足。当计算完成后

     1 void FilesystemVerifierAction::FinishPartitionHashing() {
     2   if (!hasher_->Finalize()) {
     3     LOG(ERROR) << "Unable to finalize the hash.";
     4     return Cleanup(ErrorCode::kError);
     5   }
     6   InstallPlan::Partition& partition =
     7       install_plan_.partitions[partition_index_];
     8   LOG(INFO) << "Hash of " << partition.name << ": "
     9             << Base64Encode(hasher_->raw_hash()); 
    10 
    11   switch (verifier_step_) {
    12     case VerifierStep::kVerifyTargetHash:
    13       if (partition.target_hash != hasher_->raw_hash()) {   //对保存的targethash和计算得到的hash进行一个比较
    14         LOG(ERROR) << "New '" << partition.name
    15                    << "' partition verification failed.";
    16         if (partition.source_hash.empty()) {
    17           // No need to verify source if it is a full payload.
    18           return Cleanup(ErrorCode::kNewRootfsVerificationError);
    19         }
    20         // If we have not verified source partition yet, now that the target
    21         // partition does not match, and it's not a full payload, we need to
    22         // switch to kVerifySourceHash step to check if it's because the source
    23         // partition does not match either.
    24         verifier_step_ = VerifierStep::kVerifySourceHash;  //计算source hash
    25       } else {
    26         partition_index_++;   //计算下一个分区
    27       }
    28       break;
    29     case VerifierStep::kVerifySourceHash:
    30       if (partition.source_hash != hasher_->raw_hash()) {  //保存的source hash和计算得到的也不相同
    31         LOG(ERROR) << "Old '" << partition.name
    32                    << "' partition verification failed.";
    33         LOG(ERROR) << "This is a server-side error due to mismatched delta"
    34                    << " update image!";
    35         LOG(ERROR) << "The delta I've been given contains a " << partition.name
    36                    << " delta update that must be applied over a "
    37                    << partition.name << " with a specific checksum, but the "
    38                    << partition.name
    39                    << " we're starting with doesn't have that checksum! This"
    40                       " means that the delta I've been given doesn't match my"
    41                       " existing system. The "
    42                    << partition.name << " partition I have has hash: "
    43                    << Base64Encode(hasher_->raw_hash())
    44                    << " but the update expected me to have "
    45                    << Base64Encode(partition.source_hash) << " .";
    46         LOG(INFO) << "To get the checksum of the " << partition.name
    47                   << " partition run this command: dd if="
    48                   << partition.source_path
    49                   << " bs=1M count=" << partition.source_size
    50                   << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 "
    51                      "-binary | openssl base64";
    52         LOG(INFO) << "To get the checksum of partitions in a bin file, "
    53                   << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
    54         return Cleanup(ErrorCode::kDownloadStateInitializationError);
    55       }
    56       // The action will skip kVerifySourceHash step if target partition hash
    57       // matches, if we are in this step, it means target hash does not match,
    58       // and now that the source partition hash matches, we should set the error
    59       // code to reflect the error in target partition.
    60       // We only need to verify the source partition which the target hash does
    61       // not match, the rest of the partitions don't matter.
    62       return Cleanup(ErrorCode::kNewRootfsVerificationError);
    63   }
    64   // Start hashing the next partition, if any.
    65   hasher_.reset();   //重置hash计算器
    66   buffer_.clear();  //清空缓存
    67   src_stream_->CloseBlocking(nullptr);
    68   StartPartitionHashing(); //接着计算
    69 }

     可见当一个分区的hash被计算出来的时候就会根据保存好的进行比较,如果target的hash不一致就会转向比较该分区的source hash,其实比较source hash主要就是为了确定错误的类型,只要target hash不一致,无论source hash是否一致都不会继续下一个分区的计算了。就这样一直到最后一个分区验证完后,执行最后一个Action,PostinstallRunnerAction。

    PostinstallRunnerAction

    PostinstallRunnerAction执行每个分区更新完后的postinstall script。但是在高通平台的,android8.0上无论是全包还是差分包升级并没有实质性的postinstall script。在PostinstallRunnerAction中仅仅是将target_slot标记为active状态。目前只分析于执行相关的代码。

    src/system/update_engine/payload_consumer/postinstall_runner_action.cc

     1 void PostinstallRunnerAction::PerformAction() {
     2   CHECK(HasInputObject());
     3   install_plan_ = GetInputObject();   //获取install_plan_
     4 
     5   if (install_plan_.powerwash_required) {    //是否需要进行数据的擦除
     6     if (hardware_->SchedulePowerwash()) {
     7       powerwash_scheduled_ = true;
     8     } else {
     9       return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
    10     }
    11   }
    12 
    13   // Initialize all the partition weights.
    14   partition_weight_.resize(install_plan_.partitions.size());  //初始化每个分区的权重
    15   total_weight_ = 0;
    16   for (size_t i = 0; i < install_plan_.partitions.size(); ++i) {
    17     // TODO(deymo): This code sets the weight to all the postinstall commands,
    18     // but we could remember how long they took in the past and use those
    19     // values.
    20     partition_weight_[i] = install_plan_.partitions[i].run_postinstall;
    21     total_weight_ += partition_weight_[i];  //计算总的权重
    22   }
    23   accumulated_weight_ = 0;
    24   ReportProgress(0);                      //更新进度
    25 
    26   PerformPartitionPostinstall();          //开始真正的流程
    27 }

    来看PerformPartitionPostinstall()

     1 void PostinstallRunnerAction::PerformPartitionPostinstall() {
     2   if (install_plan_.download_url.empty()) {
     3     LOG(INFO) << "Skipping post-install during rollback";
     4     return CompletePostinstall(ErrorCode::kSuccess);
     5   }
     6 
     7   // Skip all the partitions that don't have a post-install step.
     8   while (current_partition_ < install_plan_.partitions.size() &&
     9          !install_plan_.partitions[current_partition_].run_postinstall) {   //run_postinstall为false
    10     VLOG(1) << "Skipping post-install on partition "
    11             << install_plan_.partitions[current_partition_].name;
    12     current_partition_++;
    13   }
    14   if (current_partition_ == install_plan_.partitions.size())
    15     return CompletePostinstall(ErrorCode::kSuccess);
    16   ...................
    17   ...................
    18   ...................
    19 }

    在当前分析中run_postinstall为false,会跳过post-install。之后会直接执行CompletePostinstall(ErrorCode::kSuccess)

     1 void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
     2   // We only attempt to mark the new slot as active if all the postinstall
     3   // steps succeeded.
     4   if (error_code == ErrorCode::kSuccess &&
     5       !boot_control_->SetActiveBootSlot(install_plan_.target_slot)) {   //设置target_slot为active
     6     error_code = ErrorCode::kPostinstallRunnerError;
     7   }
     8 
     9   ScopedActionCompleter completer(processor_, this);
    10   completer.set_code(error_code);
    11 
    12   if (error_code != ErrorCode::kSuccess) {
    13     LOG(ERROR) << "Postinstall action failed.";
    14 
    15     // Undo any changes done to trigger Powerwash.
    16     if (powerwash_scheduled_)
    17       hardware_->CancelPowerwash();
    18 
    19     return;
    20   }
    21 
    22   LOG(INFO) << "All post-install commands succeeded";
    23   if (HasOutputPipe()) {                      //设置输出的install_plan
    24     SetOutputObject(install_plan_);
    25   }
    26 }

    最终将target_slot设置为active在重启之后就会从target_slot开始启动了。

    分析到这里就算是对update_engine的核心过程有了个大概的了解,除了对升级的知识点的认识,还体会到了它的架构。不足之处就是还有很多的细节未涉及。

  • 相关阅读:
    专注于绘画,不受限制!尝试Growly Draw for Mac的快速绘画应用
    Ascend C保姆级教程:我的第一份Ascend C代码
    如何用Angular和NativeScript开发IOS程序?
    stm32定时器输入捕获模式
    【详细】Java网络通信 TCP、UDP、InetAddress
    职场生涯亮红灯要注意这些
    如何看待PyTorch 2.0?
    【图像格式篇】可以从网络加载点9图的吗?
    【精】alibaba-sentinel 管理控制台 啥都没有 ,一片空白解决。
    JavaAPI---replace
  • 原文地址:https://blog.csdn.net/longtian635241/article/details/134512637