update_engine-FilesystemVerifierAction和PostinstallRunnerAction


在介紹完了DownloadAction之后,還剩下FilesystemVerifierAction和PostinstallRunnerAction,下面開始對其進行分析。

FilesystemVerifierAction

在數據下載完成后,在DownloadAction中會切換到FilesystemVerifierAction

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

 最后的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<int64_t>(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的核心過程有了個大概的了解,除了對升級的知識點的認識,還體會到了它的架構。不足之處就是還有很多的細節未涉及。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM