Split payload application code into a subdirectory.
This patch splits from the main libupdate_engine code the part that
is strictly used to download and apply a payload into a new static
library, moving the code to subdirectories. The new library is divided
in two subdirectories: common/ and payload_consumer/, and should not
depend on other update_engine files outside those two subdirectories.
The main difference between those two is that the common/ tools are more
generic and not tied to the payload consumer process, but otherwise they
are both compiled together.
There are still dependencies from the new libpayload_consumer library
into the main directory files and DBus generated files. Those will be
addressed in follow up CLs.
Bug: 25197634
Test: FEATURES=test emerge-link update_engine; `mm` on Brillo.
Change-Id: Id8d0204ea573627e6e26ca9ea17b9592ca95bc23
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
new file mode 100644
index 0000000..6768407
--- /dev/null
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -0,0 +1,259 @@
+//
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/filesystem_verifier_action.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <string>
+
+#include <base/bind.h>
+#include <brillo/streams/file_stream.h>
+
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+const off_t kReadFileBufferSize = 128 * 1024;
+} // namespace
+
+FilesystemVerifierAction::FilesystemVerifierAction(
+ const BootControlInterface* boot_control,
+ VerifierMode verifier_mode)
+ : verifier_mode_(verifier_mode),
+ boot_control_(boot_control) {}
+
+void FilesystemVerifierAction::PerformAction() {
+ // Will tell the ActionProcessor we've failed if we return.
+ ScopedActionCompleter abort_action_completer(processor_, this);
+
+ if (!HasInputObject()) {
+ LOG(ERROR) << "FilesystemVerifierAction missing input object.";
+ return;
+ }
+ install_plan_ = GetInputObject();
+
+ // For delta updates (major version 1) we need to populate the source
+ // partition hash if not pre-populated.
+ if (!install_plan_.is_full_update && install_plan_.partitions.empty() &&
+ verifier_mode_ == VerifierMode::kComputeSourceHash) {
+ LOG(INFO) << "Using legacy partition names.";
+ InstallPlan::Partition part;
+ string part_path;
+
+ part.name = kLegacyPartitionNameRoot;
+ if (!boot_control_->GetPartitionDevice(
+ part.name, install_plan_.source_slot, &part_path))
+ return;
+ int block_count = 0, block_size = 0;
+ if (utils::GetFilesystemSize(part_path, &block_count, &block_size)) {
+ part.source_size = static_cast<int64_t>(block_count) * block_size;
+ LOG(INFO) << "Partition " << part.name << " size: " << part.source_size
+ << " bytes (" << block_count << "x" << block_size << ").";
+ }
+ install_plan_.partitions.push_back(part);
+
+ part.name = kLegacyPartitionNameKernel;
+ if (!boot_control_->GetPartitionDevice(
+ part.name, install_plan_.source_slot, &part_path))
+ return;
+ off_t kernel_part_size = utils::FileSize(part_path);
+ if (kernel_part_size < 0)
+ return;
+ LOG(INFO) << "Partition " << part.name << " size: " << kernel_part_size
+ << " bytes.";
+ part.source_size = kernel_part_size;
+ install_plan_.partitions.push_back(part);
+ }
+
+ if (install_plan_.partitions.empty()) {
+ LOG(INFO) << "No partitions to verify.";
+ if (HasOutputPipe())
+ SetOutputObject(install_plan_);
+ abort_action_completer.set_code(ErrorCode::kSuccess);
+ return;
+ }
+
+ StartPartitionHashing();
+ abort_action_completer.set_should_complete(false);
+}
+
+void FilesystemVerifierAction::TerminateProcessing() {
+ cancelled_ = true;
+ Cleanup(ErrorCode::kSuccess); // error code is ignored if canceled_ is true.
+}
+
+bool FilesystemVerifierAction::IsCleanupPending() const {
+ return src_stream_ != nullptr;
+}
+
+void FilesystemVerifierAction::Cleanup(ErrorCode code) {
+ src_stream_.reset();
+ // This memory is not used anymore.
+ buffer_.clear();
+
+ if (cancelled_)
+ return;
+ if (code == ErrorCode::kSuccess && HasOutputPipe())
+ SetOutputObject(install_plan_);
+ processor_->ActionComplete(this, code);
+}
+
+void FilesystemVerifierAction::StartPartitionHashing() {
+ if (partition_index_ == install_plan_.partitions.size()) {
+ Cleanup(ErrorCode::kSuccess);
+ return;
+ }
+ InstallPlan::Partition& partition =
+ install_plan_.partitions[partition_index_];
+
+ string part_path;
+ switch (verifier_mode_) {
+ case VerifierMode::kComputeSourceHash:
+ boot_control_->GetPartitionDevice(
+ partition.name, install_plan_.source_slot, &part_path);
+ remaining_size_ = partition.source_size;
+ break;
+ case VerifierMode::kVerifyTargetHash:
+ boot_control_->GetPartitionDevice(
+ partition.name, install_plan_.target_slot, &part_path);
+ remaining_size_ = partition.target_size;
+ break;
+ }
+ LOG(INFO) << "Hashing partition " << partition_index_ << " ("
+ << partition.name << ") on device " << part_path;
+ if (part_path.empty())
+ return Cleanup(ErrorCode::kFilesystemVerifierError);
+
+ brillo::ErrorPtr error;
+ src_stream_ = brillo::FileStream::Open(
+ base::FilePath(part_path),
+ brillo::Stream::AccessMode::READ,
+ brillo::FileStream::Disposition::OPEN_EXISTING,
+ &error);
+
+ if (!src_stream_) {
+ LOG(ERROR) << "Unable to open " << part_path << " for reading";
+ return Cleanup(ErrorCode::kFilesystemVerifierError);
+ }
+
+ buffer_.resize(kReadFileBufferSize);
+ read_done_ = false;
+ hasher_.reset(new HashCalculator());
+
+ // Start the first read.
+ ScheduleRead();
+}
+
+void FilesystemVerifierAction::ScheduleRead() {
+ size_t bytes_to_read = std::min(static_cast<int64_t>(buffer_.size()),
+ remaining_size_);
+ if (!bytes_to_read) {
+ OnReadDoneCallback(0);
+ return;
+ }
+
+ bool read_async_ok = src_stream_->ReadAsync(
+ buffer_.data(),
+ bytes_to_read,
+ base::Bind(&FilesystemVerifierAction::OnReadDoneCallback,
+ base::Unretained(this)),
+ base::Bind(&FilesystemVerifierAction::OnReadErrorCallback,
+ base::Unretained(this)),
+ nullptr);
+
+ if (!read_async_ok) {
+ LOG(ERROR) << "Unable to schedule an asynchronous read from the stream.";
+ Cleanup(ErrorCode::kError);
+ }
+}
+
+void FilesystemVerifierAction::OnReadDoneCallback(size_t bytes_read) {
+ if (bytes_read == 0) {
+ read_done_ = true;
+ } else {
+ remaining_size_ -= bytes_read;
+ CHECK(!read_done_);
+ if (!hasher_->Update(buffer_.data(), bytes_read)) {
+ LOG(ERROR) << "Unable to update the hash.";
+ Cleanup(ErrorCode::kError);
+ return;
+ }
+ }
+
+ // We either terminate the current partition or have more data to read.
+ if (cancelled_)
+ return Cleanup(ErrorCode::kError);
+
+ if (read_done_ || remaining_size_ == 0) {
+ if (remaining_size_ != 0) {
+ LOG(ERROR) << "Failed to read the remaining " << remaining_size_
+ << " bytes from partition "
+ << install_plan_.partitions[partition_index_].name;
+ return Cleanup(ErrorCode::kFilesystemVerifierError);
+ }
+ return FinishPartitionHashing();
+ }
+ ScheduleRead();
+}
+
+void FilesystemVerifierAction::OnReadErrorCallback(
+ const brillo::Error* error) {
+ // TODO(deymo): Transform the read-error into an specific ErrorCode.
+ LOG(ERROR) << "Asynchronous read failed.";
+ Cleanup(ErrorCode::kError);
+}
+
+void FilesystemVerifierAction::FinishPartitionHashing() {
+ if (!hasher_->Finalize()) {
+ LOG(ERROR) << "Unable to finalize the hash.";
+ return Cleanup(ErrorCode::kError);
+ }
+ InstallPlan::Partition& partition =
+ install_plan_.partitions[partition_index_];
+ LOG(INFO) << "Hash of " << partition.name << ": " << hasher_->hash();
+
+ switch (verifier_mode_) {
+ case VerifierMode::kComputeSourceHash:
+ partition.source_hash = hasher_->raw_hash();
+ break;
+ case VerifierMode::kVerifyTargetHash:
+ if (partition.target_hash != hasher_->raw_hash()) {
+ LOG(ERROR) << "New '" << partition.name
+ << "' partition verification failed.";
+ return Cleanup(ErrorCode::kNewRootfsVerificationError);
+ }
+ break;
+ }
+ // Start hashing the next partition, if any.
+ partition_index_++;
+ hasher_.reset();
+ buffer_.clear();
+ src_stream_->CloseBlocking(nullptr);
+ StartPartitionHashing();
+}
+
+} // namespace chromeos_update_engine