update_engine-FilesystemVerifierAction和PostinstallRunnerAction
Posted on 2019-03-19 14:59 不上班行不行 阅读(2613) 评论(1) 编辑 收藏 举报在介绍完了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的核心过程有了个大概的了解,除了对升级的知识点的认识,还体会到了它的架构。不足之处就是还有很多的细节未涉及。