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/bzip_extent_writer.cc b/payload_consumer/bzip_extent_writer.cc
new file mode 100644
index 0000000..0fcc8ba
--- /dev/null
+++ b/payload_consumer/bzip_extent_writer.cc
@@ -0,0 +1,91 @@
+//
+// Copyright (C) 2009 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/bzip_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
+}
+
+bool BzipExtentWriter::Init(FileDescriptorPtr fd,
+ const vector<Extent>& extents,
+ uint32_t block_size) {
+ // Init bzip2 stream
+ int rc = BZ2_bzDecompressInit(&stream_,
+ 0, // verbosity. (0 == silent)
+ 0); // 0 = faster algo, more memory
+
+ TEST_AND_RETURN_FALSE(rc == BZ_OK);
+
+ return next_->Init(fd, extents, block_size);
+}
+
+bool BzipExtentWriter::Write(const void* bytes, size_t count) {
+ brillo::Blob output_buffer(kOutputBufferLength);
+
+ // Copy the input data into |input_buffer_| only if |input_buffer_| already
+ // contains unconsumed data. Otherwise, process the data directly from the
+ // source.
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+ const uint8_t* input_end = input + count;
+ if (!input_buffer_.empty()) {
+ input_buffer_.insert(input_buffer_.end(), input, input_end);
+ input = input_buffer_.data();
+ input_end = input + input_buffer_.size();
+ }
+ stream_.next_in = reinterpret_cast<char*>(const_cast<uint8_t*>(input));
+ stream_.avail_in = input_end - input;
+
+ for (;;) {
+ stream_.next_out = reinterpret_cast<char*>(output_buffer.data());
+ stream_.avail_out = output_buffer.size();
+
+ int rc = BZ2_bzDecompress(&stream_);
+ TEST_AND_RETURN_FALSE(rc == BZ_OK || rc == BZ_STREAM_END);
+
+ if (stream_.avail_out == output_buffer.size())
+ break; // got no new bytes
+
+ TEST_AND_RETURN_FALSE(
+ next_->Write(output_buffer.data(),
+ output_buffer.size() - stream_.avail_out));
+
+ if (rc == BZ_STREAM_END)
+ CHECK_EQ(stream_.avail_in, 0u);
+ if (stream_.avail_in == 0)
+ break; // no more input to process
+ }
+
+ // Store unconsumed data (if any) in |input_buffer_|.
+ if (stream_.avail_in || !input_buffer_.empty()) {
+ brillo::Blob new_input_buffer(input_end - stream_.avail_in, input_end);
+ new_input_buffer.swap(input_buffer_);
+ }
+
+ return true;
+}
+
+bool BzipExtentWriter::EndImpl() {
+ TEST_AND_RETURN_FALSE(input_buffer_.empty());
+ TEST_AND_RETURN_FALSE(BZ2_bzDecompressEnd(&stream_) == BZ_OK);
+ return next_->End();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/bzip_extent_writer.h b/payload_consumer/bzip_extent_writer.h
new file mode 100644
index 0000000..0ad542e
--- /dev/null
+++ b/payload_consumer/bzip_extent_writer.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2009 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
+
+#include <bzlib.h>
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+
+// BzipExtentWriter is a concrete ExtentWriter subclass that bzip-decompresses
+// what it's given in Write. It passes the decompressed data to an underlying
+// ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class BzipExtentWriter : public ExtentWriter {
+ public:
+ explicit BzipExtentWriter(std::unique_ptr<ExtentWriter> next)
+ : next_(std::move(next)) {
+ memset(&stream_, 0, sizeof(stream_));
+ }
+ ~BzipExtentWriter() override = default;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override;
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override;
+
+ private:
+ std::unique_ptr<ExtentWriter> next_; // The underlying ExtentWriter.
+ bz_stream stream_; // the libbz2 stream
+ brillo::Blob input_buffer_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_BZIP_EXTENT_WRITER_H_
diff --git a/payload_consumer/bzip_extent_writer_unittest.cc b/payload_consumer/bzip_extent_writer_unittest.cc
new file mode 100644
index 0000000..a52a286
--- /dev/null
+++ b/payload_consumer/bzip_extent_writer_unittest.cc
@@ -0,0 +1,145 @@
+//
+// Copyright (C) 2009 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/bzip_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const char kPathTemplate[] = "./BzipExtentWriterTest-file.XXXXXX";
+const uint32_t kBlockSize = 4096;
+}
+
+class BzipExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+ fd_.reset(new EintrSafeFileDescriptor);
+ int fd = mkstemp(path_);
+ ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+ close(fd);
+ }
+ void TearDown() override {
+ fd_->Close();
+ unlink(path_);
+ }
+ void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+ void TestZeroPad(bool aligned_size);
+
+ FileDescriptorPtr fd_;
+ char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(BzipExtentWriterTest, SimpleTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ // 'echo test | bzip2 | hexdump' yields:
+ static const char test_uncompressed[] = "test\n";
+ static const uint8_t test[] = {
+ 0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, 0xcc, 0xc3,
+ 0x71, 0xd4, 0x00, 0x00, 0x02, 0x41, 0x80, 0x00, 0x10, 0x02, 0x00, 0x0c,
+ 0x00, 0x20, 0x00, 0x21, 0x9a, 0x68, 0x33, 0x4d, 0x19, 0x97, 0x8b, 0xb9,
+ 0x22, 0x9c, 0x28, 0x48, 0x66, 0x61, 0xb8, 0xea, 0x00,
+ };
+
+ BzipExtentWriter bzip_writer(
+ brillo::make_unique_ptr(new DirectExtentWriter()));
+ EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(bzip_writer.Write(test, sizeof(test)));
+ EXPECT_TRUE(bzip_writer.End());
+
+ brillo::Blob buf;
+ EXPECT_TRUE(utils::ReadFile(path_, &buf));
+ EXPECT_EQ(strlen(test_uncompressed), buf.size());
+ EXPECT_EQ(string(buf.begin(), buf.end()), string(test_uncompressed));
+}
+
+TEST_F(BzipExtentWriterTest, ChunkedTest) {
+ const brillo::Blob::size_type kDecompressedLength = 2048 * 1024; // 2 MiB
+ string decompressed_path;
+ ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-decompressed-XXXXXX",
+ &decompressed_path, nullptr));
+ string compressed_path;
+ ASSERT_TRUE(utils::MakeTempFile("BzipExtentWriterTest-compressed-XXXXXX",
+ &compressed_path, nullptr));
+ const size_t kChunkSize = 3;
+
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(0);
+ extent.set_num_blocks(kDecompressedLength / kBlockSize + 1);
+ extents.push_back(extent);
+
+ brillo::Blob decompressed_data(kDecompressedLength);
+ test_utils::FillWithData(&decompressed_data);
+
+ EXPECT_TRUE(test_utils::WriteFileVector(
+ decompressed_path, decompressed_data));
+
+ EXPECT_EQ(0, test_utils::System(
+ string("cat ") + decompressed_path + "|bzip2>" + compressed_path));
+
+ brillo::Blob compressed_data;
+ EXPECT_TRUE(utils::ReadFile(compressed_path, &compressed_data));
+
+ BzipExtentWriter bzip_writer(
+ brillo::make_unique_ptr(new DirectExtentWriter()));
+ EXPECT_TRUE(bzip_writer.Init(fd_, extents, kBlockSize));
+
+ brillo::Blob original_compressed_data = compressed_data;
+ for (brillo::Blob::size_type i = 0; i < compressed_data.size();
+ i += kChunkSize) {
+ size_t this_chunk_size = min(kChunkSize, compressed_data.size() - i);
+ EXPECT_TRUE(bzip_writer.Write(&compressed_data[i], this_chunk_size));
+ }
+ EXPECT_TRUE(bzip_writer.End());
+
+ // Check that the const input has not been clobbered.
+ test_utils::ExpectVectorsEq(original_compressed_data, compressed_data);
+
+ brillo::Blob output;
+ EXPECT_TRUE(utils::ReadFile(path_, &output));
+ EXPECT_EQ(kDecompressedLength, output.size());
+ test_utils::ExpectVectorsEq(decompressed_data, output);
+
+ unlink(decompressed_path.c_str());
+ unlink(compressed_path.c_str());
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
new file mode 100644
index 0000000..1df5214
--- /dev/null
+++ b/payload_consumer/delta_performer.cc
@@ -0,0 +1,1744 @@
+//
+// 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/delta_performer.h"
+
+#include <endian.h>
+#include <errno.h>
+#include <linux/fs.h>
+
+#include <algorithm>
+#include <cstring>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/format_macros.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/data_encoding.h>
+#include <brillo/make_unique_ptr.h>
+#include <google/protobuf/repeated_field.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/payload_consumer/bzip_extent_writer.h"
+#include "update_engine/payload_consumer/extent_writer.h"
+#if USE_MTD
+#include "update_engine/payload_consumer/mtd_file_descriptor.h"
+#endif
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/terminator.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_consumer/xz_extent_writer.h"
+#include "update_engine/payload_state_interface.h"
+#include "update_engine/update_attempter.h"
+
+using google::protobuf::RepeatedPtrField;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+const uint64_t DeltaPerformer::kDeltaVersionOffset = sizeof(kDeltaMagic);
+const uint64_t DeltaPerformer::kDeltaVersionSize = 8;
+const uint64_t DeltaPerformer::kDeltaManifestSizeOffset =
+ kDeltaVersionOffset + kDeltaVersionSize;
+const uint64_t DeltaPerformer::kDeltaManifestSizeSize = 8;
+const uint64_t DeltaPerformer::kDeltaMetadataSignatureSizeSize = 4;
+const uint64_t DeltaPerformer::kMaxPayloadHeaderSize = 24;
+const uint64_t DeltaPerformer::kSupportedMajorPayloadVersion = 2;
+const uint32_t DeltaPerformer::kSupportedMinorPayloadVersion = 2;
+
+const unsigned DeltaPerformer::kProgressLogMaxChunks = 10;
+const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
+const unsigned DeltaPerformer::kProgressDownloadWeight = 50;
+const unsigned DeltaPerformer::kProgressOperationsWeight = 50;
+
+namespace {
+const int kUpdateStateOperationInvalid = -1;
+const int kMaxResumedUpdateFailures = 10;
+#if USE_MTD
+const int kUbiVolumeAttachTimeout = 5 * 60;
+#endif
+
+FileDescriptorPtr CreateFileDescriptor(const char* path) {
+ FileDescriptorPtr ret;
+#if USE_MTD
+ if (strstr(path, "/dev/ubi") == path) {
+ if (!UbiFileDescriptor::IsUbi(path)) {
+ // The volume might not have been attached at boot time.
+ int volume_no;
+ if (utils::SplitPartitionName(path, nullptr, &volume_no)) {
+ utils::TryAttachingUbiVolume(volume_no, kUbiVolumeAttachTimeout);
+ }
+ }
+ if (UbiFileDescriptor::IsUbi(path)) {
+ LOG(INFO) << path << " is a UBI device.";
+ ret.reset(new UbiFileDescriptor);
+ }
+ } else if (MtdFileDescriptor::IsMtd(path)) {
+ LOG(INFO) << path << " is an MTD device.";
+ ret.reset(new MtdFileDescriptor);
+ } else {
+ LOG(INFO) << path << " is not an MTD nor a UBI device.";
+#endif
+ ret.reset(new EintrSafeFileDescriptor);
+#if USE_MTD
+ }
+#endif
+ return ret;
+}
+
+// Opens path for read/write. On success returns an open FileDescriptor
+// and sets *err to 0. On failure, sets *err to errno and returns nullptr.
+FileDescriptorPtr OpenFile(const char* path, int mode, int* err) {
+ FileDescriptorPtr fd = CreateFileDescriptor(path);
+#if USE_MTD
+ // On NAND devices, we can either read, or write, but not both. So here we
+ // use O_WRONLY.
+ if (UbiFileDescriptor::IsUbi(path) || MtdFileDescriptor::IsMtd(path)) {
+ mode = O_WRONLY;
+ }
+#endif
+ if (!fd->Open(path, mode, 000)) {
+ *err = errno;
+ PLOG(ERROR) << "Unable to open file " << path;
+ return nullptr;
+ }
+ *err = 0;
+ return fd;
+}
+} // namespace
+
+
+// Computes the ratio of |part| and |total|, scaled to |norm|, using integer
+// arithmetic.
+static uint64_t IntRatio(uint64_t part, uint64_t total, uint64_t norm) {
+ return part * norm / total;
+}
+
+void DeltaPerformer::LogProgress(const char* message_prefix) {
+ // Format operations total count and percentage.
+ string total_operations_str("?");
+ string completed_percentage_str("");
+ if (num_total_operations_) {
+ total_operations_str = std::to_string(num_total_operations_);
+ // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+ completed_percentage_str =
+ base::StringPrintf(" (%" PRIu64 "%%)",
+ IntRatio(next_operation_num_, num_total_operations_,
+ 100));
+ }
+
+ // Format download total count and percentage.
+ size_t payload_size = install_plan_->payload_size;
+ string payload_size_str("?");
+ string downloaded_percentage_str("");
+ if (payload_size) {
+ payload_size_str = std::to_string(payload_size);
+ // Upcasting to 64-bit to avoid overflow, back to size_t for formatting.
+ downloaded_percentage_str =
+ base::StringPrintf(" (%" PRIu64 "%%)",
+ IntRatio(total_bytes_received_, payload_size, 100));
+ }
+
+ LOG(INFO) << (message_prefix ? message_prefix : "") << next_operation_num_
+ << "/" << total_operations_str << " operations"
+ << completed_percentage_str << ", " << total_bytes_received_
+ << "/" << payload_size_str << " bytes downloaded"
+ << downloaded_percentage_str << ", overall progress "
+ << overall_progress_ << "%";
+}
+
+void DeltaPerformer::UpdateOverallProgress(bool force_log,
+ const char* message_prefix) {
+ // Compute our download and overall progress.
+ unsigned new_overall_progress = 0;
+ COMPILE_ASSERT(kProgressDownloadWeight + kProgressOperationsWeight == 100,
+ progress_weight_dont_add_up);
+ // Only consider download progress if its total size is known; otherwise
+ // adjust the operations weight to compensate for the absence of download
+ // progress. Also, make sure to cap the download portion at
+ // kProgressDownloadWeight, in case we end up downloading more than we
+ // initially expected (this indicates a problem, but could generally happen).
+ // TODO(garnold) the correction of operations weight when we do not have the
+ // total payload size, as well as the conditional guard below, should both be
+ // eliminated once we ensure that the payload_size in the install plan is
+ // always given and is non-zero. This currently isn't the case during unit
+ // tests (see chromium-os:37969).
+ size_t payload_size = install_plan_->payload_size;
+ unsigned actual_operations_weight = kProgressOperationsWeight;
+ if (payload_size)
+ new_overall_progress += min(
+ static_cast<unsigned>(IntRatio(total_bytes_received_, payload_size,
+ kProgressDownloadWeight)),
+ kProgressDownloadWeight);
+ else
+ actual_operations_weight += kProgressDownloadWeight;
+
+ // Only add completed operations if their total number is known; we definitely
+ // expect an update to have at least one operation, so the expectation is that
+ // this will eventually reach |actual_operations_weight|.
+ if (num_total_operations_)
+ new_overall_progress += IntRatio(next_operation_num_, num_total_operations_,
+ actual_operations_weight);
+
+ // Progress ratio cannot recede, unless our assumptions about the total
+ // payload size, total number of operations, or the monotonicity of progress
+ // is breached.
+ if (new_overall_progress < overall_progress_) {
+ LOG(WARNING) << "progress counter receded from " << overall_progress_
+ << "% down to " << new_overall_progress << "%; this is a bug";
+ force_log = true;
+ }
+ overall_progress_ = new_overall_progress;
+
+ // Update chunk index, log as needed: if forced by called, or we completed a
+ // progress chunk, or a timeout has expired.
+ base::Time curr_time = base::Time::Now();
+ unsigned curr_progress_chunk =
+ overall_progress_ * kProgressLogMaxChunks / 100;
+ if (force_log || curr_progress_chunk > last_progress_chunk_ ||
+ curr_time > forced_progress_log_time_) {
+ forced_progress_log_time_ = curr_time + forced_progress_log_wait_;
+ LogProgress(message_prefix);
+ }
+ last_progress_chunk_ = curr_progress_chunk;
+}
+
+
+size_t DeltaPerformer::CopyDataToBuffer(const char** bytes_p, size_t* count_p,
+ size_t max) {
+ const size_t count = *count_p;
+ if (!count)
+ return 0; // Special case shortcut.
+ size_t read_len = min(count, max - buffer_.size());
+ const char* bytes_start = *bytes_p;
+ const char* bytes_end = bytes_start + read_len;
+ buffer_.insert(buffer_.end(), bytes_start, bytes_end);
+ *bytes_p = bytes_end;
+ *count_p = count - read_len;
+ return read_len;
+}
+
+
+bool DeltaPerformer::HandleOpResult(bool op_result, const char* op_type_name,
+ ErrorCode* error) {
+ if (op_result)
+ return true;
+
+ LOG(ERROR) << "Failed to perform " << op_type_name << " operation "
+ << next_operation_num_;
+ *error = ErrorCode::kDownloadOperationExecutionError;
+ return false;
+}
+
+int DeltaPerformer::Close() {
+ int err = -CloseCurrentPartition();
+ LOG_IF(ERROR, !payload_hash_calculator_.Finalize() ||
+ !signed_hash_calculator_.Finalize())
+ << "Unable to finalize the hash.";
+ if (!buffer_.empty()) {
+ LOG(INFO) << "Discarding " << buffer_.size() << " unused downloaded bytes";
+ if (err >= 0)
+ err = 1;
+ }
+ return -err;
+}
+
+int DeltaPerformer::CloseCurrentPartition() {
+ int err = 0;
+ if (source_fd_ && !source_fd_->Close()) {
+ err = errno;
+ PLOG(ERROR) << "Error closing source partition";
+ if (!err)
+ err = 1;
+ }
+ source_fd_.reset();
+ source_path_.clear();
+
+ if (target_fd_ && !target_fd_->Close()) {
+ err = errno;
+ PLOG(ERROR) << "Error closing target partition";
+ if (!err)
+ err = 1;
+ }
+ target_fd_.reset();
+ target_path_.clear();
+ return -err;
+}
+
+bool DeltaPerformer::OpenCurrentPartition() {
+ if (current_partition_ >= partitions_.size())
+ return false;
+
+ const PartitionUpdate& partition = partitions_[current_partition_];
+ // Open source fds if we have a delta payload with minor version 2.
+ if (!install_plan_->is_full_update &&
+ GetMinorVersion() == kSourceMinorPayloadVersion) {
+ source_path_ = install_plan_->partitions[current_partition_].source_path;
+ int err;
+ source_fd_ = OpenFile(source_path_.c_str(), O_RDONLY, &err);
+ if (!source_fd_) {
+ LOG(ERROR) << "Unable to open source partition "
+ << partition.partition_name() << " on slot "
+ << BootControlInterface::SlotName(install_plan_->source_slot)
+ << ", file " << source_path_;
+ return false;
+ }
+ }
+
+ target_path_ = install_plan_->partitions[current_partition_].target_path;
+ int err;
+ target_fd_ = OpenFile(target_path_.c_str(), O_RDWR, &err);
+ if (!target_fd_) {
+ LOG(ERROR) << "Unable to open target partition "
+ << partition.partition_name() << " on slot "
+ << BootControlInterface::SlotName(install_plan_->target_slot)
+ << ", file " << target_path_;
+ return false;
+ }
+ return true;
+}
+
+namespace {
+
+void LogPartitionInfoHash(const PartitionInfo& info, const string& tag) {
+ string sha256 = brillo::data_encoding::Base64Encode(info.hash());
+ LOG(INFO) << "PartitionInfo " << tag << " sha256: " << sha256
+ << " size: " << info.size();
+}
+
+void LogPartitionInfo(const vector<PartitionUpdate>& partitions) {
+ for (const PartitionUpdate& partition : partitions) {
+ LogPartitionInfoHash(partition.old_partition_info(),
+ "old " + partition.partition_name());
+ LogPartitionInfoHash(partition.new_partition_info(),
+ "new " + partition.partition_name());
+ }
+}
+
+} // namespace
+
+bool DeltaPerformer::GetMetadataSignatureSizeOffset(
+ uint64_t* out_offset) const {
+ if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
+ *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
+ return true;
+ }
+ return false;
+}
+
+bool DeltaPerformer::GetManifestOffset(uint64_t* out_offset) const {
+ // Actual manifest begins right after the manifest size field or
+ // metadata signature size field if major version >= 2.
+ if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
+ *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize;
+ return true;
+ }
+ if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+ *out_offset = kDeltaManifestSizeOffset + kDeltaManifestSizeSize +
+ kDeltaMetadataSignatureSizeSize;
+ return true;
+ }
+ LOG(ERROR) << "Unknown major payload version: " << major_payload_version_;
+ return false;
+}
+
+uint64_t DeltaPerformer::GetMetadataSize() const {
+ return metadata_size_;
+}
+
+uint64_t DeltaPerformer::GetMajorVersion() const {
+ return major_payload_version_;
+}
+
+uint32_t DeltaPerformer::GetMinorVersion() const {
+ if (manifest_.has_minor_version()) {
+ return manifest_.minor_version();
+ } else {
+ return (install_plan_->is_full_update ?
+ kFullPayloadMinorVersion :
+ kSupportedMinorPayloadVersion);
+ }
+}
+
+bool DeltaPerformer::GetManifest(DeltaArchiveManifest* out_manifest_p) const {
+ if (!manifest_parsed_)
+ return false;
+ *out_manifest_p = manifest_;
+ return true;
+}
+
+bool DeltaPerformer::IsHeaderParsed() const {
+ return metadata_size_ != 0;
+}
+
+DeltaPerformer::MetadataParseResult DeltaPerformer::ParsePayloadMetadata(
+ const brillo::Blob& payload, ErrorCode* error) {
+ *error = ErrorCode::kSuccess;
+ uint64_t manifest_offset;
+
+ if (!IsHeaderParsed()) {
+ // Ensure we have data to cover the major payload version.
+ if (payload.size() < kDeltaManifestSizeOffset)
+ return kMetadataParseInsufficientData;
+
+ // Validate the magic string.
+ if (memcmp(payload.data(), kDeltaMagic, sizeof(kDeltaMagic)) != 0) {
+ LOG(ERROR) << "Bad payload format -- invalid delta magic.";
+ *error = ErrorCode::kDownloadInvalidMetadataMagicString;
+ return kMetadataParseError;
+ }
+
+ // Extract the payload version from the metadata.
+ COMPILE_ASSERT(sizeof(major_payload_version_) == kDeltaVersionSize,
+ major_payload_version_size_mismatch);
+ memcpy(&major_payload_version_,
+ &payload[kDeltaVersionOffset],
+ kDeltaVersionSize);
+ // switch big endian to host
+ major_payload_version_ = be64toh(major_payload_version_);
+
+ if (major_payload_version_ != supported_major_version_ &&
+ major_payload_version_ != kChromeOSMajorPayloadVersion) {
+ LOG(ERROR) << "Bad payload format -- unsupported payload version: "
+ << major_payload_version_;
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+
+ // Get the manifest offset now that we have payload version.
+ if (!GetManifestOffset(&manifest_offset)) {
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+ // Check again with the manifest offset.
+ if (payload.size() < manifest_offset)
+ return kMetadataParseInsufficientData;
+
+ // Next, parse the manifest size.
+ COMPILE_ASSERT(sizeof(manifest_size_) == kDeltaManifestSizeSize,
+ manifest_size_size_mismatch);
+ memcpy(&manifest_size_,
+ &payload[kDeltaManifestSizeOffset],
+ kDeltaManifestSizeSize);
+ manifest_size_ = be64toh(manifest_size_); // switch big endian to host
+
+ if (GetMajorVersion() == kBrilloMajorPayloadVersion) {
+ // Parse the metadata signature size.
+ COMPILE_ASSERT(sizeof(metadata_signature_size_) ==
+ kDeltaMetadataSignatureSizeSize,
+ metadata_signature_size_size_mismatch);
+ uint64_t metadata_signature_size_offset;
+ if (!GetMetadataSignatureSizeOffset(&metadata_signature_size_offset)) {
+ *error = ErrorCode::kError;
+ return kMetadataParseError;
+ }
+ memcpy(&metadata_signature_size_,
+ &payload[metadata_signature_size_offset],
+ kDeltaMetadataSignatureSizeSize);
+ metadata_signature_size_ = be32toh(metadata_signature_size_);
+ }
+
+ // If the metadata size is present in install plan, check for it immediately
+ // even before waiting for that many number of bytes to be downloaded in the
+ // payload. This will prevent any attack which relies on us downloading data
+ // beyond the expected metadata size.
+ metadata_size_ = manifest_offset + manifest_size_;
+ if (install_plan_->hash_checks_mandatory) {
+ if (install_plan_->metadata_size != metadata_size_) {
+ LOG(ERROR) << "Mandatory metadata size in Omaha response ("
+ << install_plan_->metadata_size
+ << ") is missing/incorrect, actual = " << metadata_size_;
+ *error = ErrorCode::kDownloadInvalidMetadataSize;
+ return kMetadataParseError;
+ }
+ }
+ }
+
+ // Now that we have validated the metadata size, we should wait for the full
+ // metadata and its signature (if exist) to be read in before we can parse it.
+ if (payload.size() < metadata_size_ + metadata_signature_size_)
+ return kMetadataParseInsufficientData;
+
+ // Log whether we validated the size or simply trusting what's in the payload
+ // here. This is logged here (after we received the full metadata data) so
+ // that we just log once (instead of logging n times) if it takes n
+ // DeltaPerformer::Write calls to download the full manifest.
+ if (install_plan_->metadata_size == metadata_size_) {
+ LOG(INFO) << "Manifest size in payload matches expected value from Omaha";
+ } else {
+ // For mandatory-cases, we'd have already returned a kMetadataParseError
+ // above. We'll be here only for non-mandatory cases. Just send a UMA stat.
+ LOG(WARNING) << "Ignoring missing/incorrect metadata size ("
+ << install_plan_->metadata_size
+ << ") in Omaha response as validation is not mandatory. "
+ << "Trusting metadata size in payload = " << metadata_size_;
+ }
+
+ // We have the full metadata in |payload|. Verify its integrity
+ // and authenticity based on the information we have in Omaha response.
+ *error = ValidateMetadataSignature(payload);
+ if (*error != ErrorCode::kSuccess) {
+ if (install_plan_->hash_checks_mandatory) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(ERROR) << "Mandatory metadata signature validation failed";
+ return kMetadataParseError;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Ignoring metadata signature validation failures";
+ *error = ErrorCode::kSuccess;
+ }
+
+ if (!GetManifestOffset(&manifest_offset)) {
+ *error = ErrorCode::kUnsupportedMajorPayloadVersion;
+ return kMetadataParseError;
+ }
+ // The payload metadata is deemed valid, it's safe to parse the protobuf.
+ if (!manifest_.ParseFromArray(&payload[manifest_offset], manifest_size_)) {
+ LOG(ERROR) << "Unable to parse manifest in update file.";
+ *error = ErrorCode::kDownloadManifestParseError;
+ return kMetadataParseError;
+ }
+
+ manifest_parsed_ = true;
+ return kMetadataParseSuccess;
+}
+
+// Wrapper around write. Returns true if all requested bytes
+// were written, or false on any error, regardless of progress
+// and stores an action exit code in |error|.
+bool DeltaPerformer::Write(const void* bytes, size_t count, ErrorCode *error) {
+ *error = ErrorCode::kSuccess;
+
+ const char* c_bytes = reinterpret_cast<const char*>(bytes);
+ system_state_->payload_state()->DownloadProgress(count);
+
+ // Update the total byte downloaded count and the progress logs.
+ total_bytes_received_ += count;
+ UpdateOverallProgress(false, "Completed ");
+
+ while (!manifest_valid_) {
+ // Read data up to the needed limit; this is either maximium payload header
+ // size, or the full metadata size (once it becomes known).
+ const bool do_read_header = !IsHeaderParsed();
+ CopyDataToBuffer(&c_bytes, &count,
+ (do_read_header ? kMaxPayloadHeaderSize :
+ metadata_size_ + metadata_signature_size_));
+
+ MetadataParseResult result = ParsePayloadMetadata(buffer_, error);
+ if (result == kMetadataParseError)
+ return false;
+ if (result == kMetadataParseInsufficientData) {
+ // If we just processed the header, make an attempt on the manifest.
+ if (do_read_header && IsHeaderParsed())
+ continue;
+
+ return true;
+ }
+
+ // Checks the integrity of the payload manifest.
+ if ((*error = ValidateManifest()) != ErrorCode::kSuccess)
+ return false;
+ manifest_valid_ = true;
+
+ // Clear the download buffer.
+ DiscardBuffer(false, metadata_size_);
+
+ // This populates |partitions_| and the |install_plan.partitions| with the
+ // list of partitions from the manifest.
+ if (!ParseManifestPartitions(error))
+ return false;
+
+ num_total_operations_ = 0;
+ for (const auto& partition : partitions_) {
+ num_total_operations_ += partition.operations_size();
+ acc_num_operations_.push_back(num_total_operations_);
+ }
+
+ LOG_IF(WARNING, !prefs_->SetInt64(kPrefsManifestMetadataSize,
+ metadata_size_))
+ << "Unable to save the manifest metadata size.";
+
+ if (!PrimeUpdateState()) {
+ *error = ErrorCode::kDownloadStateInitializationError;
+ LOG(ERROR) << "Unable to prime the update state.";
+ return false;
+ }
+
+ if (!OpenCurrentPartition()) {
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
+
+ if (next_operation_num_ > 0)
+ UpdateOverallProgress(true, "Resuming after ");
+ LOG(INFO) << "Starting to apply update payload operations";
+ }
+
+ while (next_operation_num_ < num_total_operations_) {
+ // Check if we should cancel the current attempt for any reason.
+ // In this case, *error will have already been populated with the reason
+ // why we're canceling.
+ if (system_state_->update_attempter()->ShouldCancel(error))
+ return false;
+
+ // We know there are more operations to perform because we didn't reach the
+ // |num_total_operations_| limit yet.
+ while (next_operation_num_ >= acc_num_operations_[current_partition_]) {
+ CloseCurrentPartition();
+ current_partition_++;
+ if (!OpenCurrentPartition()) {
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
+ }
+ const size_t partition_operation_num = next_operation_num_ - (
+ current_partition_ ? acc_num_operations_[current_partition_ - 1] : 0);
+
+ const InstallOperation& op =
+ partitions_[current_partition_].operations(partition_operation_num);
+
+ CopyDataToBuffer(&c_bytes, &count, op.data_length());
+
+ // Check whether we received all of the next operation's data payload.
+ if (!CanPerformInstallOperation(op))
+ return true;
+
+ // Validate the operation only if the metadata signature is present.
+ // Otherwise, keep the old behavior. This serves as a knob to disable
+ // the validation logic in case we find some regression after rollout.
+ // NOTE: If hash checks are mandatory and if metadata_signature is empty,
+ // we would have already failed in ParsePayloadMetadata method and thus not
+ // even be here. So no need to handle that case again here.
+ if (!install_plan_->metadata_signature.empty()) {
+ // Note: Validate must be called only if CanPerformInstallOperation is
+ // called. Otherwise, we might be failing operations before even if there
+ // isn't sufficient data to compute the proper hash.
+ *error = ValidateOperationHash(op);
+ if (*error != ErrorCode::kSuccess) {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Mandatory operation hash check failed";
+ return false;
+ }
+
+ // For non-mandatory cases, just send a UMA stat.
+ LOG(WARNING) << "Ignoring operation validation errors";
+ *error = ErrorCode::kSuccess;
+ }
+ }
+
+ // Makes sure we unblock exit when this operation completes.
+ ScopedTerminatorExitUnblocker exit_unblocker =
+ ScopedTerminatorExitUnblocker(); // Avoids a compiler unused var bug.
+
+ bool op_result;
+ switch (op.type()) {
+ case InstallOperation::REPLACE:
+ case InstallOperation::REPLACE_BZ:
+ case InstallOperation::REPLACE_XZ:
+ op_result = PerformReplaceOperation(op);
+ break;
+ case InstallOperation::ZERO:
+ case InstallOperation::DISCARD:
+ op_result = PerformZeroOrDiscardOperation(op);
+ break;
+ case InstallOperation::MOVE:
+ op_result = PerformMoveOperation(op);
+ break;
+ case InstallOperation::BSDIFF:
+ op_result = PerformBsdiffOperation(op);
+ break;
+ case InstallOperation::SOURCE_COPY:
+ op_result = PerformSourceCopyOperation(op);
+ break;
+ case InstallOperation::SOURCE_BSDIFF:
+ op_result = PerformSourceBsdiffOperation(op);
+ break;
+ default:
+ op_result = false;
+ }
+ if (!HandleOpResult(op_result, InstallOperationTypeName(op.type()), error))
+ return false;
+
+ next_operation_num_++;
+ UpdateOverallProgress(false, "Completed ");
+ CheckpointUpdateProgress();
+ }
+
+ // In major version 2, we don't add dummy operation to the payload.
+ if (major_payload_version_ == kBrilloMajorPayloadVersion &&
+ manifest_.has_signatures_offset() && manifest_.has_signatures_size()) {
+ if (manifest_.signatures_offset() != buffer_offset_) {
+ LOG(ERROR) << "Payload signatures offset points to blob offset "
+ << manifest_.signatures_offset()
+ << " but signatures are expected at offset "
+ << buffer_offset_;
+ *error = ErrorCode::kDownloadPayloadVerificationError;
+ return false;
+ }
+ CopyDataToBuffer(&c_bytes, &count, manifest_.signatures_size());
+ // Needs more data to cover entire signature.
+ if (buffer_.size() < manifest_.signatures_size())
+ return true;
+ if (!ExtractSignatureMessage()) {
+ LOG(ERROR) << "Extract payload signature failed.";
+ *error = ErrorCode::kDownloadPayloadVerificationError;
+ return false;
+ }
+ DiscardBuffer(true, 0);
+ }
+
+ return true;
+}
+
+bool DeltaPerformer::IsManifestValid() {
+ return manifest_valid_;
+}
+
+bool DeltaPerformer::ParseManifestPartitions(ErrorCode* error) {
+ if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+ partitions_.clear();
+ for (const PartitionUpdate& partition : manifest_.partitions()) {
+ partitions_.push_back(partition);
+ }
+ manifest_.clear_partitions();
+ } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
+ LOG(INFO) << "Converting update information from old format.";
+ PartitionUpdate root_part;
+ root_part.set_partition_name(kLegacyPartitionNameRoot);
+#ifdef __ANDROID__
+ LOG(WARNING) << "Legacy payload major version provided to an Android "
+ "build. Assuming no post-install. Please use major version "
+ "2 or newer.";
+ root_part.set_run_postinstall(false);
+#else
+ root_part.set_run_postinstall(true);
+#endif // __ANDROID__
+ if (manifest_.has_old_rootfs_info()) {
+ *root_part.mutable_old_partition_info() = manifest_.old_rootfs_info();
+ manifest_.clear_old_rootfs_info();
+ }
+ if (manifest_.has_new_rootfs_info()) {
+ *root_part.mutable_new_partition_info() = manifest_.new_rootfs_info();
+ manifest_.clear_new_rootfs_info();
+ }
+ *root_part.mutable_operations() = manifest_.install_operations();
+ manifest_.clear_install_operations();
+ partitions_.push_back(std::move(root_part));
+
+ PartitionUpdate kern_part;
+ kern_part.set_partition_name(kLegacyPartitionNameKernel);
+ kern_part.set_run_postinstall(false);
+ if (manifest_.has_old_kernel_info()) {
+ *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info();
+ manifest_.clear_old_kernel_info();
+ }
+ if (manifest_.has_new_kernel_info()) {
+ *kern_part.mutable_new_partition_info() = manifest_.new_kernel_info();
+ manifest_.clear_new_kernel_info();
+ }
+ *kern_part.mutable_operations() = manifest_.kernel_install_operations();
+ manifest_.clear_kernel_install_operations();
+ partitions_.push_back(std::move(kern_part));
+ }
+
+ // TODO(deymo): Remove this block of code once we switched to optional
+ // source partition verification. This list of partitions in the InstallPlan
+ // is initialized with the expected hashes in the payload major version 1,
+ // so we need to check those now if already set. See b/23182225.
+ if (!install_plan_->partitions.empty()) {
+ if (!VerifySourcePartitions()) {
+ *error = ErrorCode::kDownloadStateInitializationError;
+ return false;
+ }
+ }
+
+ // Fill in the InstallPlan::partitions based on the partitions from the
+ // payload.
+ install_plan_->partitions.clear();
+ for (const auto& partition : partitions_) {
+ InstallPlan::Partition install_part;
+ install_part.name = partition.partition_name();
+ install_part.run_postinstall =
+ partition.has_run_postinstall() && partition.run_postinstall();
+
+ if (partition.has_old_partition_info()) {
+ const PartitionInfo& info = partition.old_partition_info();
+ install_part.source_size = info.size();
+ install_part.source_hash.assign(info.hash().begin(), info.hash().end());
+ }
+
+ if (!partition.has_new_partition_info()) {
+ LOG(ERROR) << "Unable to get new partition hash info on partition "
+ << install_part.name << ".";
+ *error = ErrorCode::kDownloadNewPartitionInfoError;
+ return false;
+ }
+ const PartitionInfo& info = partition.new_partition_info();
+ install_part.target_size = info.size();
+ install_part.target_hash.assign(info.hash().begin(), info.hash().end());
+
+ install_plan_->partitions.push_back(install_part);
+ }
+
+ if (!install_plan_->LoadPartitionsFromSlots(system_state_)) {
+ LOG(ERROR) << "Unable to determine all the partition devices.";
+ *error = ErrorCode::kInstallDeviceOpenError;
+ return false;
+ }
+ LogPartitionInfo(partitions_);
+ return true;
+}
+
+bool DeltaPerformer::CanPerformInstallOperation(
+ const chromeos_update_engine::InstallOperation& operation) {
+ // Move and source_copy operations don't require any data blob, so they can
+ // always be performed.
+ if (operation.type() == InstallOperation::MOVE ||
+ operation.type() == InstallOperation::SOURCE_COPY)
+ return true;
+
+ // See if we have the entire data blob in the buffer
+ if (operation.data_offset() < buffer_offset_) {
+ LOG(ERROR) << "we threw away data it seems?";
+ return false;
+ }
+
+ return (operation.data_offset() + operation.data_length() <=
+ buffer_offset_ + buffer_.size());
+}
+
+bool DeltaPerformer::PerformReplaceOperation(
+ const InstallOperation& operation) {
+ CHECK(operation.type() == InstallOperation::REPLACE ||
+ operation.type() == InstallOperation::REPLACE_BZ ||
+ operation.type() == InstallOperation::REPLACE_XZ);
+
+ // Since we delete data off the beginning of the buffer as we use it,
+ // the data we need should be exactly at the beginning of the buffer.
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+ // Extract the signature message if it's in this operation.
+ if (ExtractSignatureMessageFromOperation(operation)) {
+ // If this is dummy replace operation, we ignore it after extracting the
+ // signature.
+ DiscardBuffer(true, 0);
+ return true;
+ }
+
+ // Setup the ExtentWriter stack based on the operation type.
+ std::unique_ptr<ExtentWriter> writer =
+ brillo::make_unique_ptr(new ZeroPadExtentWriter(
+ brillo::make_unique_ptr(new DirectExtentWriter())));
+
+ if (operation.type() == InstallOperation::REPLACE_BZ) {
+ writer.reset(new BzipExtentWriter(std::move(writer)));
+ } else if (operation.type() == InstallOperation::REPLACE_XZ) {
+ writer.reset(new XzExtentWriter(std::move(writer)));
+ }
+
+ // Create a vector of extents to pass to the ExtentWriter.
+ vector<Extent> extents;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ extents.push_back(operation.dst_extents(i));
+ }
+
+ TEST_AND_RETURN_FALSE(writer->Init(target_fd_, extents, block_size_));
+ TEST_AND_RETURN_FALSE(writer->Write(buffer_.data(), operation.data_length()));
+ TEST_AND_RETURN_FALSE(writer->End());
+
+ // Update buffer
+ DiscardBuffer(true, buffer_.size());
+ return true;
+}
+
+bool DeltaPerformer::PerformZeroOrDiscardOperation(
+ const InstallOperation& operation) {
+ CHECK(operation.type() == InstallOperation::DISCARD ||
+ operation.type() == InstallOperation::ZERO);
+
+ // These operations have no blob.
+ TEST_AND_RETURN_FALSE(!operation.has_data_offset());
+ TEST_AND_RETURN_FALSE(!operation.has_data_length());
+
+ int request =
+ (operation.type() == InstallOperation::ZERO ? BLKZEROOUT : BLKDISCARD);
+
+ bool attempt_ioctl = true;
+ brillo::Blob zeros;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ Extent extent = operation.dst_extents(i);
+ const uint64_t start = extent.start_block() * block_size_;
+ const uint64_t length = extent.num_blocks() * block_size_;
+ if (attempt_ioctl) {
+ int result = 0;
+ if (target_fd_->BlkIoctl(request, start, length, &result) && result == 0)
+ continue;
+ attempt_ioctl = false;
+ zeros.resize(16 * block_size_);
+ }
+ // In case of failure, we fall back to writing 0 to the selected region.
+ for (uint64_t offset = 0; offset < length; offset += zeros.size()) {
+ uint64_t chunk_length = min(length - offset,
+ static_cast<uint64_t>(zeros.size()));
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(target_fd_, zeros.data(), chunk_length, start + offset));
+ }
+ }
+ return true;
+}
+
+bool DeltaPerformer::PerformMoveOperation(const InstallOperation& operation) {
+ // Calculate buffer size. Note, this function doesn't do a sliding
+ // window to copy in case the source and destination blocks overlap.
+ // If we wanted to do a sliding window, we could program the server
+ // to generate deltas that effectively did a sliding window.
+
+ uint64_t blocks_to_read = 0;
+ for (int i = 0; i < operation.src_extents_size(); i++)
+ blocks_to_read += operation.src_extents(i).num_blocks();
+
+ uint64_t blocks_to_write = 0;
+ for (int i = 0; i < operation.dst_extents_size(); i++)
+ blocks_to_write += operation.dst_extents(i).num_blocks();
+
+ DCHECK_EQ(blocks_to_write, blocks_to_read);
+ brillo::Blob buf(blocks_to_write * block_size_);
+
+ // Read in bytes.
+ ssize_t bytes_read = 0;
+ for (int i = 0; i < operation.src_extents_size(); i++) {
+ ssize_t bytes_read_this_iteration = 0;
+ const Extent& extent = operation.src_extents(i);
+ const size_t bytes = extent.num_blocks() * block_size_;
+ TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+ TEST_AND_RETURN_FALSE(utils::PReadAll(target_fd_,
+ &buf[bytes_read],
+ bytes,
+ extent.start_block() * block_size_,
+ &bytes_read_this_iteration));
+ TEST_AND_RETURN_FALSE(
+ bytes_read_this_iteration == static_cast<ssize_t>(bytes));
+ bytes_read += bytes_read_this_iteration;
+ }
+
+ // Write bytes out.
+ ssize_t bytes_written = 0;
+ for (int i = 0; i < operation.dst_extents_size(); i++) {
+ const Extent& extent = operation.dst_extents(i);
+ const size_t bytes = extent.num_blocks() * block_size_;
+ TEST_AND_RETURN_FALSE(extent.start_block() != kSparseHole);
+ TEST_AND_RETURN_FALSE(utils::PWriteAll(target_fd_,
+ &buf[bytes_written],
+ bytes,
+ extent.start_block() * block_size_));
+ bytes_written += bytes;
+ }
+ DCHECK_EQ(bytes_written, bytes_read);
+ DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size()));
+ return true;
+}
+
+namespace {
+
+// Takes |extents| and fills an empty vector |blocks| with a block index for
+// each block in |extents|. For example, [(3, 2), (8, 1)] would give [3, 4, 8].
+void ExtentsToBlocks(const RepeatedPtrField<Extent>& extents,
+ vector<uint64_t>* blocks) {
+ for (Extent ext : extents) {
+ for (uint64_t j = 0; j < ext.num_blocks(); j++)
+ blocks->push_back(ext.start_block() + j);
+ }
+}
+
+// Takes |extents| and returns the number of blocks in those extents.
+uint64_t GetBlockCount(const RepeatedPtrField<Extent>& extents) {
+ uint64_t sum = 0;
+ for (Extent ext : extents) {
+ sum += ext.num_blocks();
+ }
+ return sum;
+}
+
+} // namespace
+
+bool DeltaPerformer::PerformSourceCopyOperation(
+ const InstallOperation& operation) {
+ if (operation.has_src_length())
+ TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+ if (operation.has_dst_length())
+ TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+ uint64_t blocks_to_read = GetBlockCount(operation.src_extents());
+ uint64_t blocks_to_write = GetBlockCount(operation.dst_extents());
+ TEST_AND_RETURN_FALSE(blocks_to_write == blocks_to_read);
+
+ // Create vectors of all the individual src/dst blocks.
+ vector<uint64_t> src_blocks;
+ vector<uint64_t> dst_blocks;
+ ExtentsToBlocks(operation.src_extents(), &src_blocks);
+ ExtentsToBlocks(operation.dst_extents(), &dst_blocks);
+ DCHECK_EQ(src_blocks.size(), blocks_to_read);
+ DCHECK_EQ(src_blocks.size(), dst_blocks.size());
+
+ brillo::Blob buf(block_size_);
+ ssize_t bytes_read = 0;
+ // Read/write one block at a time.
+ for (uint64_t i = 0; i < blocks_to_read; i++) {
+ ssize_t bytes_read_this_iteration = 0;
+ uint64_t src_block = src_blocks[i];
+ uint64_t dst_block = dst_blocks[i];
+
+ // Read in bytes.
+ TEST_AND_RETURN_FALSE(
+ utils::PReadAll(source_fd_,
+ buf.data(),
+ block_size_,
+ src_block * block_size_,
+ &bytes_read_this_iteration));
+
+ // Write bytes out.
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(target_fd_,
+ buf.data(),
+ block_size_,
+ dst_block * block_size_));
+
+ bytes_read += bytes_read_this_iteration;
+ TEST_AND_RETURN_FALSE(bytes_read_this_iteration ==
+ static_cast<ssize_t>(block_size_));
+ }
+ DCHECK_EQ(bytes_read, static_cast<ssize_t>(blocks_to_read * block_size_));
+ return true;
+}
+
+bool DeltaPerformer::ExtentsToBsdiffPositionsString(
+ const RepeatedPtrField<Extent>& extents,
+ uint64_t block_size,
+ uint64_t full_length,
+ string* positions_string) {
+ string ret;
+ uint64_t length = 0;
+ for (int i = 0; i < extents.size(); i++) {
+ Extent extent = extents.Get(i);
+ int64_t start = extent.start_block() * block_size;
+ uint64_t this_length = min(full_length - length,
+ extent.num_blocks() * block_size);
+ ret += base::StringPrintf("%" PRIi64 ":%" PRIu64 ",", start, this_length);
+ length += this_length;
+ }
+ TEST_AND_RETURN_FALSE(length == full_length);
+ if (!ret.empty())
+ ret.resize(ret.size() - 1); // Strip trailing comma off
+ *positions_string = ret;
+ return true;
+}
+
+bool DeltaPerformer::PerformBsdiffOperation(const InstallOperation& operation) {
+ // Since we delete data off the beginning of the buffer as we use it,
+ // the data we need should be exactly at the beginning of the buffer.
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+
+ string input_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+ block_size_,
+ operation.src_length(),
+ &input_positions));
+ string output_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+ block_size_,
+ operation.dst_length(),
+ &output_positions));
+
+ string temp_filename;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile("au_patch.XXXXXX",
+ &temp_filename,
+ nullptr));
+ ScopedPathUnlinker path_unlinker(temp_filename);
+ {
+ int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ScopedFdCloser fd_closer(&fd);
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+ }
+
+ // Update the buffer to release the patch data memory as soon as the patch
+ // file is written out.
+ DiscardBuffer(true, buffer_.size());
+
+ vector<string> cmd{kBspatchPath, target_path_, target_path_, temp_filename,
+ input_positions, output_positions};
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(
+ Subprocess::SynchronousExecFlags(cmd, Subprocess::kSearchPath,
+ &return_code, nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+
+ if (operation.dst_length() % block_size_) {
+ // Zero out rest of final block.
+ // TODO(adlr): build this into bspatch; it's more efficient that way.
+ const Extent& last_extent =
+ operation.dst_extents(operation.dst_extents_size() - 1);
+ const uint64_t end_byte =
+ (last_extent.start_block() + last_extent.num_blocks()) * block_size_;
+ const uint64_t begin_byte =
+ end_byte - (block_size_ - operation.dst_length() % block_size_);
+ brillo::Blob zeros(end_byte - begin_byte);
+ TEST_AND_RETURN_FALSE(
+ utils::PWriteAll(target_fd_, zeros.data(), end_byte - begin_byte, begin_byte));
+ }
+ return true;
+}
+
+bool DeltaPerformer::PerformSourceBsdiffOperation(
+ const InstallOperation& operation) {
+ // Since we delete data off the beginning of the buffer as we use it,
+ // the data we need should be exactly at the beginning of the buffer.
+ TEST_AND_RETURN_FALSE(buffer_offset_ == operation.data_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= operation.data_length());
+ if (operation.has_src_length())
+ TEST_AND_RETURN_FALSE(operation.src_length() % block_size_ == 0);
+ if (operation.has_dst_length())
+ TEST_AND_RETURN_FALSE(operation.dst_length() % block_size_ == 0);
+
+ string input_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
+ block_size_,
+ operation.src_length(),
+ &input_positions));
+ string output_positions;
+ TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
+ block_size_,
+ operation.dst_length(),
+ &output_positions));
+
+ string temp_filename;
+ TEST_AND_RETURN_FALSE(utils::MakeTempFile("au_patch.XXXXXX",
+ &temp_filename,
+ nullptr));
+ ScopedPathUnlinker path_unlinker(temp_filename);
+ {
+ int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ScopedFdCloser fd_closer(&fd);
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd, buffer_.data(), operation.data_length()));
+ }
+
+ // Update the buffer to release the patch data memory as soon as the patch
+ // file is written out.
+ DiscardBuffer(true, buffer_.size());
+
+ vector<string> cmd{kBspatchPath, source_path_, target_path_, temp_filename,
+ input_positions, output_positions};
+
+ int return_code = 0;
+ TEST_AND_RETURN_FALSE(
+ Subprocess::SynchronousExecFlags(cmd, Subprocess::kSearchPath,
+ &return_code, nullptr));
+ TEST_AND_RETURN_FALSE(return_code == 0);
+ return true;
+}
+
+bool DeltaPerformer::ExtractSignatureMessageFromOperation(
+ const InstallOperation& operation) {
+ if (operation.type() != InstallOperation::REPLACE ||
+ !manifest_.has_signatures_offset() ||
+ manifest_.signatures_offset() != operation.data_offset()) {
+ return false;
+ }
+ TEST_AND_RETURN_FALSE(manifest_.has_signatures_size() &&
+ manifest_.signatures_size() == operation.data_length());
+ TEST_AND_RETURN_FALSE(ExtractSignatureMessage());
+ return true;
+}
+
+bool DeltaPerformer::ExtractSignatureMessage() {
+ TEST_AND_RETURN_FALSE(signatures_message_data_.empty());
+ TEST_AND_RETURN_FALSE(buffer_offset_ == manifest_.signatures_offset());
+ TEST_AND_RETURN_FALSE(buffer_.size() >= manifest_.signatures_size());
+ signatures_message_data_.assign(
+ buffer_.begin(),
+ buffer_.begin() + manifest_.signatures_size());
+
+ // Save the signature blob because if the update is interrupted after the
+ // download phase we don't go through this path anymore. Some alternatives to
+ // consider:
+ //
+ // 1. On resume, re-download the signature blob from the server and re-verify
+ // it.
+ //
+ // 2. Verify the signature as soon as it's received and don't checkpoint the
+ // blob and the signed sha-256 context.
+ LOG_IF(WARNING, !prefs_->SetString(kPrefsUpdateStateSignatureBlob,
+ string(signatures_message_data_.begin(),
+ signatures_message_data_.end())))
+ << "Unable to store the signature blob.";
+
+ LOG(INFO) << "Extracted signature data of size "
+ << manifest_.signatures_size() << " at "
+ << manifest_.signatures_offset();
+ return true;
+}
+
+bool DeltaPerformer::GetPublicKeyFromResponse(base::FilePath *out_tmp_key) {
+ if (system_state_->hardware()->IsOfficialBuild() ||
+ utils::FileExists(public_key_path_.c_str()) ||
+ install_plan_->public_key_rsa.empty())
+ return false;
+
+ if (!utils::DecodeAndStoreBase64String(install_plan_->public_key_rsa,
+ out_tmp_key))
+ return false;
+
+ return true;
+}
+
+ErrorCode DeltaPerformer::ValidateMetadataSignature(
+ const brillo::Blob& payload) {
+ if (payload.size() < metadata_size_ + metadata_signature_size_)
+ return ErrorCode::kDownloadMetadataSignatureError;
+
+ brillo::Blob metadata_signature_blob, metadata_signature_protobuf_blob;
+ if (!install_plan_->metadata_signature.empty()) {
+ // Convert base64-encoded signature to raw bytes.
+ if (!brillo::data_encoding::Base64Decode(
+ install_plan_->metadata_signature, &metadata_signature_blob)) {
+ LOG(ERROR) << "Unable to decode base64 metadata signature: "
+ << install_plan_->metadata_signature;
+ return ErrorCode::kDownloadMetadataSignatureError;
+ }
+ } else if (major_payload_version_ == kBrilloMajorPayloadVersion) {
+ metadata_signature_protobuf_blob.assign(payload.begin() + metadata_size_,
+ payload.begin() + metadata_size_ +
+ metadata_signature_size_);
+ }
+
+ if (metadata_signature_blob.empty() &&
+ metadata_signature_protobuf_blob.empty()) {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Missing mandatory metadata signature in both Omaha "
+ << "response and payload.";
+ return ErrorCode::kDownloadMetadataSignatureMissingError;
+ }
+
+ LOG(WARNING) << "Cannot validate metadata as the signature is empty";
+ return ErrorCode::kSuccess;
+ }
+
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
+ LOG(INFO) << "Verifying metadata hash signature using public key: "
+ << path_to_public_key.value();
+
+ HashCalculator metadata_hasher;
+ metadata_hasher.Update(payload.data(), metadata_size_);
+ if (!metadata_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of manifest";
+ return ErrorCode::kDownloadMetadataSignatureVerificationError;
+ }
+
+ brillo::Blob calculated_metadata_hash = metadata_hasher.raw_hash();
+ PayloadVerifier::PadRSA2048SHA256Hash(&calculated_metadata_hash);
+ if (calculated_metadata_hash.empty()) {
+ LOG(ERROR) << "Computed actual hash of metadata is empty.";
+ return ErrorCode::kDownloadMetadataSignatureVerificationError;
+ }
+
+ if (!metadata_signature_blob.empty()) {
+ brillo::Blob expected_metadata_hash;
+ if (!PayloadVerifier::GetRawHashFromSignature(metadata_signature_blob,
+ path_to_public_key.value(),
+ &expected_metadata_hash)) {
+ LOG(ERROR) << "Unable to compute expected hash from metadata signature";
+ return ErrorCode::kDownloadMetadataSignatureError;
+ }
+ if (calculated_metadata_hash != expected_metadata_hash) {
+ LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
+ utils::HexDumpVector(expected_metadata_hash);
+ LOG(ERROR) << "Calculated hash = ";
+ utils::HexDumpVector(calculated_metadata_hash);
+ return ErrorCode::kDownloadMetadataSignatureMismatch;
+ }
+ } else {
+ if (!PayloadVerifier::VerifySignature(metadata_signature_protobuf_blob,
+ path_to_public_key.value(),
+ calculated_metadata_hash)) {
+ LOG(ERROR) << "Manifest hash verification failed.";
+ return ErrorCode::kDownloadMetadataSignatureMismatch;
+ }
+ }
+
+ // The autoupdate_CatchBadSignatures test checks for this string in
+ // log-files. Keep in sync.
+ LOG(INFO) << "Metadata hash signature matches value in Omaha response.";
+ return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateManifest() {
+ // Perform assorted checks to sanity check the manifest, make sure it
+ // matches data from other sources, and that it is a supported version.
+ //
+ // TODO(garnold) in general, the presence of an old partition hash should be
+ // the sole indicator for a delta update, as we would generally like update
+ // payloads to be self contained and not assume an Omaha response to tell us
+ // that. However, since this requires some massive reengineering of the update
+ // flow (making filesystem copying happen conditionally only *after*
+ // downloading and parsing of the update manifest) we'll put it off for now.
+ // See chromium-os:7597 for further discussion.
+ if (install_plan_->is_full_update) {
+ if (manifest_.has_old_kernel_info() || manifest_.has_old_rootfs_info()) {
+ LOG(ERROR) << "Purported full payload contains old partition "
+ "hash(es), aborting update";
+ return ErrorCode::kPayloadMismatchedType;
+ }
+
+ for (const PartitionUpdate& partition : manifest_.partitions()) {
+ if (partition.has_old_partition_info()) {
+ LOG(ERROR) << "Purported full payload contains old partition "
+ "hash(es), aborting update";
+ return ErrorCode::kPayloadMismatchedType;
+ }
+ }
+
+ if (manifest_.minor_version() != kFullPayloadMinorVersion) {
+ LOG(ERROR) << "Manifest contains minor version "
+ << manifest_.minor_version()
+ << ", but all full payloads should have version "
+ << kFullPayloadMinorVersion << ".";
+ return ErrorCode::kUnsupportedMinorPayloadVersion;
+ }
+ } else {
+ if (manifest_.minor_version() != supported_minor_version_) {
+ LOG(ERROR) << "Manifest contains minor version "
+ << manifest_.minor_version()
+ << " not the supported "
+ << supported_minor_version_;
+ return ErrorCode::kUnsupportedMinorPayloadVersion;
+ }
+ }
+
+ if (major_payload_version_ != kChromeOSMajorPayloadVersion) {
+ if (manifest_.has_old_rootfs_info() ||
+ manifest_.has_new_rootfs_info() ||
+ manifest_.has_old_kernel_info() ||
+ manifest_.has_new_kernel_info() ||
+ manifest_.install_operations_size() != 0 ||
+ manifest_.kernel_install_operations_size() != 0) {
+ LOG(ERROR) << "Manifest contains deprecated field only supported in "
+ << "major payload version 1, but the payload major version is "
+ << major_payload_version_;
+ return ErrorCode::kPayloadMismatchedType;
+ }
+ }
+
+ // TODO(garnold) we should be adding more and more manifest checks, such as
+ // partition boundaries etc (see chromium-os:37661).
+
+ return ErrorCode::kSuccess;
+}
+
+ErrorCode DeltaPerformer::ValidateOperationHash(
+ const InstallOperation& operation) {
+ if (!operation.data_sha256_hash().size()) {
+ if (!operation.data_length()) {
+ // Operations that do not have any data blob won't have any operation hash
+ // either. So, these operations are always considered validated since the
+ // metadata that contains all the non-data-blob portions of the operation
+ // has already been validated. This is true for both HTTP and HTTPS cases.
+ return ErrorCode::kSuccess;
+ }
+
+ // No hash is present for an operation that has data blobs. This shouldn't
+ // happen normally for any client that has this code, because the
+ // corresponding update should have been produced with the operation
+ // hashes. So if it happens it means either we've turned operation hash
+ // generation off in DeltaDiffGenerator or it's a regression of some sort.
+ // One caveat though: The last operation is a dummy signature operation
+ // that doesn't have a hash at the time the manifest is created. So we
+ // should not complaint about that operation. This operation can be
+ // recognized by the fact that it's offset is mentioned in the manifest.
+ if (manifest_.signatures_offset() &&
+ manifest_.signatures_offset() == operation.data_offset()) {
+ LOG(INFO) << "Skipping hash verification for signature operation "
+ << next_operation_num_ + 1;
+ } else {
+ if (install_plan_->hash_checks_mandatory) {
+ LOG(ERROR) << "Missing mandatory operation hash for operation "
+ << next_operation_num_ + 1;
+ return ErrorCode::kDownloadOperationHashMissingError;
+ }
+
+ LOG(WARNING) << "Cannot validate operation " << next_operation_num_ + 1
+ << " as there's no operation hash in manifest";
+ }
+ return ErrorCode::kSuccess;
+ }
+
+ brillo::Blob expected_op_hash;
+ expected_op_hash.assign(operation.data_sha256_hash().data(),
+ (operation.data_sha256_hash().data() +
+ operation.data_sha256_hash().size()));
+
+ HashCalculator operation_hasher;
+ operation_hasher.Update(buffer_.data(), operation.data_length());
+ if (!operation_hasher.Finalize()) {
+ LOG(ERROR) << "Unable to compute actual hash of operation "
+ << next_operation_num_;
+ return ErrorCode::kDownloadOperationHashVerificationError;
+ }
+
+ brillo::Blob calculated_op_hash = operation_hasher.raw_hash();
+ if (calculated_op_hash != expected_op_hash) {
+ LOG(ERROR) << "Hash verification failed for operation "
+ << next_operation_num_ << ". Expected hash = ";
+ utils::HexDumpVector(expected_op_hash);
+ LOG(ERROR) << "Calculated hash over " << operation.data_length()
+ << " bytes at offset: " << operation.data_offset() << " = ";
+ utils::HexDumpVector(calculated_op_hash);
+ return ErrorCode::kDownloadOperationHashMismatch;
+ }
+
+ return ErrorCode::kSuccess;
+}
+
+#define TEST_AND_RETURN_VAL(_retval, _condition) \
+ do { \
+ if (!(_condition)) { \
+ LOG(ERROR) << "VerifyPayload failure: " << #_condition; \
+ return _retval; \
+ } \
+ } while (0);
+
+ErrorCode DeltaPerformer::VerifyPayload(
+ const string& update_check_response_hash,
+ const uint64_t update_check_response_size) {
+
+ // See if we should use the public RSA key in the Omaha response.
+ base::FilePath path_to_public_key(public_key_path_);
+ base::FilePath tmp_key;
+ if (GetPublicKeyFromResponse(&tmp_key))
+ path_to_public_key = tmp_key;
+ ScopedPathUnlinker tmp_key_remover(tmp_key.value());
+ if (tmp_key.empty())
+ tmp_key_remover.set_should_remove(false);
+
+ LOG(INFO) << "Verifying payload using public key: "
+ << path_to_public_key.value();
+
+ // Verifies the download size.
+ TEST_AND_RETURN_VAL(ErrorCode::kPayloadSizeMismatchError,
+ update_check_response_size ==
+ metadata_size_ + metadata_signature_size_ +
+ buffer_offset_);
+
+ // Verifies the payload hash.
+ const string& payload_hash_data = payload_hash_calculator_.hash();
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
+ !payload_hash_data.empty());
+ TEST_AND_RETURN_VAL(ErrorCode::kPayloadHashMismatchError,
+ payload_hash_data == update_check_response_hash);
+
+ // Verifies the signed payload hash.
+ if (!utils::FileExists(path_to_public_key.value().c_str())) {
+ LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
+ return ErrorCode::kSuccess;
+ }
+ TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
+ !signatures_message_data_.empty());
+ brillo::Blob hash_data = signed_hash_calculator_.raw_hash();
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ PayloadVerifier::PadRSA2048SHA256Hash(&hash_data));
+ TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
+ !hash_data.empty());
+
+ if (!PayloadVerifier::VerifySignature(
+ signatures_message_data_, path_to_public_key.value(), hash_data)) {
+ // The autoupdate_CatchBadSignatures test checks for this string
+ // in log-files. Keep in sync.
+ LOG(ERROR) << "Public key verification failed, thus update failed.";
+ return ErrorCode::kDownloadPayloadPubKeyVerificationError;
+ }
+
+ LOG(INFO) << "Payload hash matches value in payload.";
+
+ // At this point, we are guaranteed to have downloaded a full payload, i.e
+ // the one whose size matches the size mentioned in Omaha response. If any
+ // errors happen after this, it's likely a problem with the payload itself or
+ // the state of the system and not a problem with the URL or network. So,
+ // indicate that to the payload state so that AU can backoff appropriately.
+ system_state_->payload_state()->DownloadComplete();
+
+ return ErrorCode::kSuccess;
+}
+
+namespace {
+void LogVerifyError(const string& type,
+ const string& device,
+ uint64_t size,
+ const string& local_hash,
+ const string& expected_hash) {
+ LOG(ERROR) << "This is a server-side error due to "
+ << "mismatched delta update image!";
+ LOG(ERROR) << "The delta I've been given contains a " << type << " delta "
+ << "update that must be applied over a " << type << " with "
+ << "a specific checksum, but the " << type << " we're starting "
+ << "with doesn't have that checksum! This means that "
+ << "the delta I've been given doesn't match my existing "
+ << "system. The " << type << " partition I have has hash: "
+ << local_hash << " but the update expected me to have "
+ << expected_hash << " .";
+ LOG(INFO) << "To get the checksum of the " << type << " partition run this"
+ "command: dd if=" << device << " bs=1M count=" << size
+ << " iflag=count_bytes 2>/dev/null | openssl dgst -sha256 -binary "
+ "| openssl base64";
+ LOG(INFO) << "To get the checksum of partitions in a bin file, "
+ << "run: .../src/scripts/sha256_partitions.sh .../file.bin";
+}
+
+string StringForHashBytes(const void* bytes, size_t size) {
+ return brillo::data_encoding::Base64Encode(bytes, size);
+}
+} // namespace
+
+bool DeltaPerformer::VerifySourcePartitions() {
+ LOG(INFO) << "Verifying source partitions.";
+ CHECK(manifest_valid_);
+ CHECK(install_plan_);
+ if (install_plan_->partitions.size() != partitions_.size()) {
+ DLOG(ERROR) << "The list of partitions in the InstallPlan doesn't match the "
+ "list received in the payload. The InstallPlan has "
+ << install_plan_->partitions.size()
+ << " partitions while the payload has " << partitions_.size()
+ << " partitions.";
+ return false;
+ }
+ for (size_t i = 0; i < partitions_.size(); ++i) {
+ if (partitions_[i].partition_name() != install_plan_->partitions[i].name) {
+ DLOG(ERROR) << "The InstallPlan's partition " << i << " is \""
+ << install_plan_->partitions[i].name
+ << "\" but the payload expects it to be \""
+ << partitions_[i].partition_name()
+ << "\". This is an error in the DeltaPerformer setup.";
+ return false;
+ }
+ if (!partitions_[i].has_old_partition_info())
+ continue;
+ const PartitionInfo& info = partitions_[i].old_partition_info();
+ const InstallPlan::Partition& plan_part = install_plan_->partitions[i];
+ bool valid =
+ !plan_part.source_hash.empty() &&
+ plan_part.source_hash.size() == info.hash().size() &&
+ memcmp(plan_part.source_hash.data(),
+ info.hash().data(),
+ plan_part.source_hash.size()) == 0;
+ if (!valid) {
+ LogVerifyError(partitions_[i].partition_name(),
+ plan_part.source_path,
+ info.hash().size(),
+ StringForHashBytes(plan_part.source_hash.data(),
+ plan_part.source_hash.size()),
+ StringForHashBytes(info.hash().data(),
+ info.hash().size()));
+ return false;
+ }
+ }
+ return true;
+}
+
+void DeltaPerformer::DiscardBuffer(bool do_advance_offset,
+ size_t signed_hash_buffer_size) {
+ // Update the buffer offset.
+ if (do_advance_offset)
+ buffer_offset_ += buffer_.size();
+
+ // Hash the content.
+ payload_hash_calculator_.Update(buffer_.data(), buffer_.size());
+ signed_hash_calculator_.Update(buffer_.data(), signed_hash_buffer_size);
+
+ // Swap content with an empty vector to ensure that all memory is released.
+ brillo::Blob().swap(buffer_);
+}
+
+bool DeltaPerformer::CanResumeUpdate(PrefsInterface* prefs,
+ string update_check_response_hash) {
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!(prefs->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) &&
+ next_operation != kUpdateStateOperationInvalid &&
+ next_operation > 0))
+ return false;
+
+ string interrupted_hash;
+ if (!(prefs->GetString(kPrefsUpdateCheckResponseHash, &interrupted_hash) &&
+ !interrupted_hash.empty() &&
+ interrupted_hash == update_check_response_hash))
+ return false;
+
+ int64_t resumed_update_failures;
+ if (!(prefs->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)
+ && resumed_update_failures > kMaxResumedUpdateFailures))
+ return false;
+
+ // Sanity check the rest.
+ int64_t next_data_offset = -1;
+ if (!(prefs->GetInt64(kPrefsUpdateStateNextDataOffset, &next_data_offset) &&
+ next_data_offset >= 0))
+ return false;
+
+ string sha256_context;
+ if (!(prefs->GetString(kPrefsUpdateStateSHA256Context, &sha256_context) &&
+ !sha256_context.empty()))
+ return false;
+
+ int64_t manifest_metadata_size = 0;
+ if (!(prefs->GetInt64(kPrefsManifestMetadataSize, &manifest_metadata_size) &&
+ manifest_metadata_size > 0))
+ return false;
+
+ return true;
+}
+
+bool DeltaPerformer::ResetUpdateProgress(PrefsInterface* prefs, bool quick) {
+ TEST_AND_RETURN_FALSE(prefs->SetInt64(kPrefsUpdateStateNextOperation,
+ kUpdateStateOperationInvalid));
+ if (!quick) {
+ prefs->SetString(kPrefsUpdateCheckResponseHash, "");
+ prefs->SetInt64(kPrefsUpdateStateNextDataOffset, -1);
+ prefs->SetInt64(kPrefsUpdateStateNextDataLength, 0);
+ prefs->SetString(kPrefsUpdateStateSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignedSHA256Context, "");
+ prefs->SetString(kPrefsUpdateStateSignatureBlob, "");
+ prefs->SetInt64(kPrefsManifestMetadataSize, -1);
+ prefs->SetInt64(kPrefsResumedUpdateFailures, 0);
+ }
+ return true;
+}
+
+bool DeltaPerformer::CheckpointUpdateProgress() {
+ Terminator::set_exit_blocked(true);
+ if (last_updated_buffer_offset_ != buffer_offset_) {
+ // Resets the progress in case we die in the middle of the state update.
+ ResetUpdateProgress(prefs_, true);
+ TEST_AND_RETURN_FALSE(
+ prefs_->SetString(kPrefsUpdateStateSHA256Context,
+ payload_hash_calculator_.GetContext()));
+ TEST_AND_RETURN_FALSE(
+ prefs_->SetString(kPrefsUpdateStateSignedSHA256Context,
+ signed_hash_calculator_.GetContext()));
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataOffset,
+ buffer_offset_));
+ last_updated_buffer_offset_ = buffer_offset_;
+
+ if (next_operation_num_ < num_total_operations_) {
+ size_t partition_index = current_partition_;
+ while (next_operation_num_ >= acc_num_operations_[partition_index])
+ partition_index++;
+ const size_t partition_operation_num = next_operation_num_ - (
+ partition_index ? acc_num_operations_[partition_index - 1] : 0);
+ const InstallOperation& op =
+ partitions_[partition_index].operations(partition_operation_num);
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ op.data_length()));
+ } else {
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextDataLength,
+ 0));
+ }
+ }
+ TEST_AND_RETURN_FALSE(prefs_->SetInt64(kPrefsUpdateStateNextOperation,
+ next_operation_num_));
+ return true;
+}
+
+bool DeltaPerformer::PrimeUpdateState() {
+ CHECK(manifest_valid_);
+ block_size_ = manifest_.block_size();
+
+ int64_t next_operation = kUpdateStateOperationInvalid;
+ if (!prefs_->GetInt64(kPrefsUpdateStateNextOperation, &next_operation) ||
+ next_operation == kUpdateStateOperationInvalid ||
+ next_operation <= 0) {
+ // Initiating a new update, no more state needs to be initialized.
+ return true;
+ }
+ next_operation_num_ = next_operation;
+
+ // Resuming an update -- load the rest of the update state.
+ int64_t next_data_offset = -1;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsUpdateStateNextDataOffset,
+ &next_data_offset) &&
+ next_data_offset >= 0);
+ buffer_offset_ = next_data_offset;
+
+ // The signed hash context and the signature blob may be empty if the
+ // interrupted update didn't reach the signature.
+ string signed_hash_context;
+ if (prefs_->GetString(kPrefsUpdateStateSignedSHA256Context,
+ &signed_hash_context)) {
+ TEST_AND_RETURN_FALSE(
+ signed_hash_calculator_.SetContext(signed_hash_context));
+ }
+
+ string signature_blob;
+ if (prefs_->GetString(kPrefsUpdateStateSignatureBlob, &signature_blob)) {
+ signatures_message_data_.assign(signature_blob.begin(),
+ signature_blob.end());
+ }
+
+ string hash_context;
+ TEST_AND_RETURN_FALSE(prefs_->GetString(kPrefsUpdateStateSHA256Context,
+ &hash_context) &&
+ payload_hash_calculator_.SetContext(hash_context));
+
+ int64_t manifest_metadata_size = 0;
+ TEST_AND_RETURN_FALSE(prefs_->GetInt64(kPrefsManifestMetadataSize,
+ &manifest_metadata_size) &&
+ manifest_metadata_size > 0);
+ metadata_size_ = manifest_metadata_size;
+
+ // Advance the download progress to reflect what doesn't need to be
+ // re-downloaded.
+ total_bytes_received_ += buffer_offset_;
+
+ // Speculatively count the resume as a failure.
+ int64_t resumed_update_failures;
+ if (prefs_->GetInt64(kPrefsResumedUpdateFailures, &resumed_update_failures)) {
+ resumed_update_failures++;
+ } else {
+ resumed_update_failures = 1;
+ }
+ prefs_->SetInt64(kPrefsResumedUpdateFailures, resumed_update_failures);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
new file mode 100644
index 0000000..db938c1
--- /dev/null
+++ b/payload_consumer/delta_performer.h
@@ -0,0 +1,396 @@
+//
+// Copyright (C) 2010 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+#include <brillo/secure_blob.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/platform_constants.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/payload_consumer/file_writer.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/system_state.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+class PrefsInterface;
+
+// This class performs the actions in a delta update synchronously. The delta
+// update itself should be passed in in chunks as it is received.
+
+class DeltaPerformer : public FileWriter {
+ public:
+ enum MetadataParseResult {
+ kMetadataParseSuccess,
+ kMetadataParseError,
+ kMetadataParseInsufficientData,
+ };
+
+ static const uint64_t kDeltaVersionOffset;
+ static const uint64_t kDeltaVersionSize;
+ static const uint64_t kDeltaManifestSizeOffset;
+ static const uint64_t kDeltaManifestSizeSize;
+ static const uint64_t kDeltaMetadataSignatureSizeSize;
+ static const uint64_t kMaxPayloadHeaderSize;
+ static const uint64_t kSupportedMajorPayloadVersion;
+ static const uint32_t kSupportedMinorPayloadVersion;
+
+ // Defines the granularity of progress logging in terms of how many "completed
+ // chunks" we want to report at the most.
+ static const unsigned kProgressLogMaxChunks;
+ // Defines a timeout since the last progress was logged after which we want to
+ // force another log message (even if the current chunk was not completed).
+ static const unsigned kProgressLogTimeoutSeconds;
+ // These define the relative weights (0-100) we give to the different work
+ // components associated with an update when computing an overall progress.
+ // Currently they include the download progress and the number of completed
+ // operations. They must add up to one hundred (100).
+ static const unsigned kProgressDownloadWeight;
+ static const unsigned kProgressOperationsWeight;
+
+ DeltaPerformer(PrefsInterface* prefs,
+ SystemState* system_state,
+ InstallPlan* install_plan)
+ : prefs_(prefs),
+ system_state_(system_state),
+ install_plan_(install_plan) {}
+
+ // FileWriter's Write implementation where caller doesn't care about
+ // error codes.
+ bool Write(const void* bytes, size_t count) override {
+ ErrorCode error;
+ return Write(bytes, count, &error);
+ }
+
+ // FileWriter's Write implementation that returns a more specific |error| code
+ // in case of failures in Write operation.
+ bool Write(const void* bytes, size_t count, ErrorCode *error) override;
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ // Closes both 'path' given to Open() and the kernel path.
+ int Close() override;
+
+ // Open the target and source (if delta payload) file descriptors for the
+ // |current_partition_|. The manifest needs to be already parsed for this to
+ // work. Returns whether the required file descriptors were successfully open.
+ bool OpenCurrentPartition();
+
+ // Closes the current partition file descriptors if open. Returns 0 on success
+ // or -errno on error.
+ int CloseCurrentPartition();
+
+ // Returns |true| only if the manifest has been processed and it's valid.
+ bool IsManifestValid();
+
+ // Verifies the downloaded payload against the signed hash included in the
+ // payload, against the update check hash (which is in base64 format) and
+ // size using the public key and returns ErrorCode::kSuccess on success, an
+ // error code on failure. This method should be called after closing the
+ // stream. Note this method skips the signed hash check if the public key is
+ // unavailable; it returns ErrorCode::kSignedDeltaPayloadExpectedError if the
+ // public key is available but the delta payload doesn't include a signature.
+ ErrorCode VerifyPayload(const std::string& update_check_response_hash,
+ const uint64_t update_check_response_size);
+
+ // Converts an ordered collection of Extent objects which contain data of
+ // length full_length to a comma-separated string. For each Extent, the
+ // string will have the start offset and then the length in bytes.
+ // The length value of the last extent in the string may be short, since
+ // the full length of all extents in the string is capped to full_length.
+ // Also, an extent starting at kSparseHole, appears as -1 in the string.
+ // For example, if the Extents are {1, 1}, {4, 2}, {kSparseHole, 1},
+ // {0, 1}, block_size is 4096, and full_length is 5 * block_size - 13,
+ // the resulting string will be: "4096:4096,16384:8192,-1:4096,0:4083"
+ static bool ExtentsToBsdiffPositionsString(
+ const google::protobuf::RepeatedPtrField<Extent>& extents,
+ uint64_t block_size,
+ uint64_t full_length,
+ std::string* positions_string);
+
+ // Returns true if a previous update attempt can be continued based on the
+ // persistent preferences and the new update check response hash.
+ static bool CanResumeUpdate(PrefsInterface* prefs,
+ std::string update_check_response_hash);
+
+ // Resets the persistent update progress state to indicate that an update
+ // can't be resumed. Performs a quick update-in-progress reset if |quick| is
+ // true, otherwise resets all progress-related update state. Returns true on
+ // success, false otherwise.
+ static bool ResetUpdateProgress(PrefsInterface* prefs, bool quick);
+
+ // Attempts to parse the update metadata starting from the beginning of
+ // |payload|. On success, returns kMetadataParseSuccess. Returns
+ // kMetadataParseInsufficientData if more data is needed to parse the complete
+ // metadata. Returns kMetadataParseError if the metadata can't be parsed given
+ // the payload.
+ MetadataParseResult ParsePayloadMetadata(const brillo::Blob& payload,
+ ErrorCode* error);
+
+ void set_public_key_path(const std::string& public_key_path) {
+ public_key_path_ = public_key_path;
+ }
+
+ // Set |*out_offset| to the byte offset where the size of the metadata signature
+ // is stored in a payload. Return true on success, if this field is not
+ // present in the payload, return false.
+ bool GetMetadataSignatureSizeOffset(uint64_t* out_offset) const;
+
+ // Set |*out_offset| to the byte offset at which the manifest protobuf begins
+ // in a payload. Return true on success, false if the offset is unknown.
+ bool GetManifestOffset(uint64_t* out_offset) const;
+
+ // Returns the size of the payload metadata, which includes the payload header
+ // and the manifest. If the header was not yet parsed, returns zero.
+ uint64_t GetMetadataSize() const;
+
+ // If the manifest was successfully parsed, copies it to |*out_manifest_p|.
+ // Returns true on success.
+ bool GetManifest(DeltaArchiveManifest* out_manifest_p) const;
+
+ // Return true if header parsing is finished and no errors occurred.
+ bool IsHeaderParsed() const;
+
+ // Returns the major payload version. If the version was not yet parsed,
+ // returns zero.
+ uint64_t GetMajorVersion() const;
+
+ // Returns the delta minor version. If this value is defined in the manifest,
+ // it returns that value, otherwise it returns the default value.
+ uint32_t GetMinorVersion() const;
+
+ private:
+ friend class DeltaPerformerTest;
+ friend class DeltaPerformerIntegrationTest;
+ FRIEND_TEST(DeltaPerformerTest, BrilloMetadataSignatureSizeTest);
+ FRIEND_TEST(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest);
+ FRIEND_TEST(DeltaPerformerTest, UsePublicKeyFromResponse);
+
+ // Parse and move the update instructions of all partitions into our local
+ // |partitions_| variable based on the version of the payload. Requires the
+ // manifest to be parsed and valid.
+ bool ParseManifestPartitions(ErrorCode* error);
+
+ // Appends up to |*count_p| bytes from |*bytes_p| to |buffer_|, but only to
+ // the extent that the size of |buffer_| does not exceed |max|. Advances
+ // |*cbytes_p| and decreases |*count_p| by the actual number of bytes copied,
+ // and returns this number.
+ size_t CopyDataToBuffer(const char** bytes_p, size_t* count_p, size_t max);
+
+ // If |op_result| is false, emits an error message using |op_type_name| and
+ // sets |*error| accordingly. Otherwise does nothing. Returns |op_result|.
+ bool HandleOpResult(bool op_result, const char* op_type_name,
+ ErrorCode* error);
+
+ // Logs the progress of downloading/applying an update.
+ void LogProgress(const char* message_prefix);
+
+ // Update overall progress metrics, log as necessary.
+ void UpdateOverallProgress(bool force_log, const char* message_prefix);
+
+ // Verifies that the expected source partition hashes (if present) match the
+ // hashes for the current partitions. Returns true if there are no expected
+ // hashes in the payload (e.g., if it's a new-style full update) or if the
+ // hashes match; returns false otherwise.
+ bool VerifySourcePartitions();
+
+ // Returns true if enough of the delta file has been passed via Write()
+ // to be able to perform a given install operation.
+ bool CanPerformInstallOperation(const InstallOperation& operation);
+
+ // Checks the integrity of the payload manifest. Returns true upon success,
+ // false otherwise.
+ ErrorCode ValidateManifest();
+
+ // Validates that the hash of the blobs corresponding to the given |operation|
+ // matches what's specified in the manifest in the payload.
+ // Returns ErrorCode::kSuccess on match or a suitable error code otherwise.
+ ErrorCode ValidateOperationHash(const InstallOperation& operation);
+
+ // Given the |payload|, verifies that the signed hash of its metadata matches
+ // what's specified in the install plan from Omaha (if present) or the
+ // metadata signature in payload itself (if present). Returns
+ // ErrorCode::kSuccess on match or a suitable error code otherwise. This
+ // method must be called before any part of the metadata is parsed so that a
+ // man-in-the-middle attack on the SSL connection to the payload server
+ // doesn't exploit any vulnerability in the code that parses the protocol
+ // buffer.
+ ErrorCode ValidateMetadataSignature(const brillo::Blob& payload);
+
+ // Returns true on success.
+ bool PerformInstallOperation(const InstallOperation& operation);
+
+ // These perform a specific type of operation and return true on success.
+ bool PerformReplaceOperation(const InstallOperation& operation);
+ bool PerformZeroOrDiscardOperation(const InstallOperation& operation);
+ bool PerformMoveOperation(const InstallOperation& operation);
+ bool PerformBsdiffOperation(const InstallOperation& operation);
+ bool PerformSourceCopyOperation(const InstallOperation& operation);
+ bool PerformSourceBsdiffOperation(const InstallOperation& operation);
+
+ // Extracts the payload signature message from the blob on the |operation| if
+ // the offset matches the one specified by the manifest. Returns whether the
+ // signature was extracted.
+ bool ExtractSignatureMessageFromOperation(const InstallOperation& operation);
+
+ // Extracts the payload signature message from the current |buffer_| if the
+ // offset matches the one specified by the manifest. Returns whether the
+ // signature was extracted.
+ bool ExtractSignatureMessage();
+
+ // Updates the payload hash calculator with the bytes in |buffer_|, also
+ // updates the signed hash calculator with the first |signed_hash_buffer_size|
+ // bytes in |buffer_|. Then discard the content, ensuring that memory is being
+ // deallocated. If |do_advance_offset|, advances the internal offset counter
+ // accordingly.
+ void DiscardBuffer(bool do_advance_offset, size_t signed_hash_buffer_size);
+
+ // Checkpoints the update progress into persistent storage to allow this
+ // update attempt to be resumed after reboot.
+ bool CheckpointUpdateProgress();
+
+ // Primes the required update state. Returns true if the update state was
+ // successfully initialized to a saved resume state or if the update is a new
+ // update. Returns false otherwise.
+ bool PrimeUpdateState();
+
+ // If the Omaha response contains a public RSA key and we're allowed
+ // to use it (e.g. if we're in developer mode), extract the key from
+ // the response and store it in a temporary file and return true. In
+ // the affirmative the path to the temporary file is stored in
+ // |out_tmp_key| and it is the responsibility of the caller to clean
+ // it up.
+ bool GetPublicKeyFromResponse(base::FilePath *out_tmp_key);
+
+ // Update Engine preference store.
+ PrefsInterface* prefs_;
+
+ // Global context of the system.
+ SystemState* system_state_;
+
+ // Install Plan based on Omaha Response.
+ InstallPlan* install_plan_;
+
+ // File descriptor of the source partition. Only set while updating a
+ // partition when using a delta payload.
+ FileDescriptorPtr source_fd_{nullptr};
+
+ // File descriptor of the target partition. Only set while performing the
+ // operations of a given partition.
+ FileDescriptorPtr target_fd_{nullptr};
+
+ // Paths the |source_fd_| and |target_fd_| refer to.
+ std::string source_path_;
+ std::string target_path_;
+
+ // Parsed manifest. Set after enough bytes to parse the manifest were
+ // downloaded.
+ DeltaArchiveManifest manifest_;
+ bool manifest_parsed_{false};
+ bool manifest_valid_{false};
+ uint64_t metadata_size_{0};
+ uint64_t manifest_size_{0};
+ uint32_t metadata_signature_size_{0};
+ uint64_t major_payload_version_{0};
+
+ // Accumulated number of operations per partition. The i-th element is the
+ // sum of the number of operations for all the partitions from 0 to i
+ // inclusive. Valid when |manifest_valid_| is true.
+ std::vector<size_t> acc_num_operations_;
+
+ // The total operations in a payload. Valid when |manifest_valid_| is true,
+ // otherwise 0.
+ size_t num_total_operations_{0};
+
+ // The list of partitions to update as found in the manifest major version 2.
+ // When parsing an older manifest format, the information is converted over to
+ // this format instead.
+ std::vector<PartitionUpdate> partitions_;
+
+ // Index in the list of partitions (|partitions_| member) of the current
+ // partition being processed.
+ size_t current_partition_{0};
+
+ // Index of the next operation to perform in the manifest. The index is linear
+ // on the total number of operation on the manifest.
+ size_t next_operation_num_{0};
+
+ // A buffer used for accumulating downloaded data. Initially, it stores the
+ // payload metadata; once that's downloaded and parsed, it stores data for the
+ // next update operation.
+ brillo::Blob buffer_;
+ // Offset of buffer_ in the binary blobs section of the update.
+ uint64_t buffer_offset_{0};
+
+ // Last |buffer_offset_| value updated as part of the progress update.
+ uint64_t last_updated_buffer_offset_{kuint64max};
+
+ // The block size (parsed from the manifest).
+ uint32_t block_size_{0};
+
+ // Calculates the whole payload file hash, including headers and signatures.
+ HashCalculator payload_hash_calculator_;
+
+ // Calculates the hash of the portion of the payload signed by the payload
+ // signature. This hash skips the metadata signature portion, located after
+ // the metadata and doesn't include the payload signature itself.
+ HashCalculator signed_hash_calculator_;
+
+ // Signatures message blob extracted directly from the payload.
+ brillo::Blob signatures_message_data_;
+
+ // The public key to be used. Provided as a member so that tests can
+ // override with test keys.
+ std::string public_key_path_{constants::kUpdatePayloadPublicKeyPath};
+
+ // The number of bytes received so far, used for progress tracking.
+ size_t total_bytes_received_{0};
+
+ // An overall progress counter, which should reflect both download progress
+ // and the ratio of applied operations. Range is 0-100.
+ unsigned overall_progress_{0};
+
+ // The last progress chunk recorded.
+ unsigned last_progress_chunk_{0};
+
+ // The timeout after which we should force emitting a progress log (constant),
+ // and the actual point in time for the next forced log to be emitted.
+ const base::TimeDelta forced_progress_log_wait_{
+ base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)};
+ base::Time forced_progress_log_time_;
+
+ // The payload major payload version supported by DeltaPerformer.
+ uint64_t supported_major_version_{kSupportedMajorPayloadVersion};
+
+ // The delta minor payload version supported by DeltaPerformer.
+ uint32_t supported_minor_version_{kSupportedMinorPayloadVersion};
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_DELTA_PERFORMER_H_
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
new file mode 100644
index 0000000..728e12e
--- /dev/null
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -0,0 +1,1031 @@
+//
+// 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/delta_performer.h"
+
+#include <inttypes.h>
+#include <sys/mount.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/delta_diff_generator.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using test_utils::ScopedLoopMounter;
+using test_utils::System;
+using test_utils::kRandomString;
+using testing::Return;
+using testing::_;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+extern const char* kUnittestPrivateKey2Path;
+extern const char* kUnittestPublicKey2Path;
+
+static const int kDefaultKernelSize = 4096; // Something small for a test
+static const uint8_t kNewData[] = {'T', 'h', 'i', 's', ' ', 'i', 's', ' ',
+ 'n', 'e', 'w', ' ', 'd', 'a', 't', 'a', '.'};
+
+namespace {
+struct DeltaState {
+ string a_img;
+ string b_img;
+ string result_img;
+ size_t image_size;
+
+ string delta_path;
+ uint64_t metadata_size;
+
+ string old_kernel;
+ brillo::Blob old_kernel_data;
+
+ string new_kernel;
+ brillo::Blob new_kernel_data;
+
+ string result_kernel;
+ brillo::Blob result_kernel_data;
+ size_t kernel_size;
+
+ // The InstallPlan referenced by the DeltaPerformer. This needs to outlive
+ // the DeltaPerformer.
+ InstallPlan install_plan;
+
+ // The in-memory copy of delta file.
+ brillo::Blob delta;
+
+ // The mock system state object with which we initialize the
+ // delta performer.
+ FakeSystemState fake_system_state;
+};
+
+enum SignatureTest {
+ kSignatureNone, // No payload signing.
+ kSignatureGenerator, // Sign the payload at generation time.
+ kSignatureGenerated, // Sign the payload after it's generated.
+ kSignatureGeneratedPlaceholder, // Insert placeholder signatures, then real.
+ kSignatureGeneratedPlaceholderMismatch, // Insert a wrong sized placeholder.
+ kSignatureGeneratedShell, // Sign the generated payload through shell cmds.
+ kSignatureGeneratedShellBadKey, // Sign with a bad key through shell cmds.
+ kSignatureGeneratedShellRotateCl1, // Rotate key, test client v1
+ kSignatureGeneratedShellRotateCl2, // Rotate key, test client v2
+};
+
+enum OperationHashTest {
+ kInvalidOperationData,
+ kValidOperationData,
+};
+
+} // namespace
+
+class DeltaPerformerIntegrationTest : public ::testing::Test {
+ public:
+ static void SetSupportedVersion(DeltaPerformer* performer,
+ uint64_t minor_version) {
+ performer->supported_minor_version_ = minor_version;
+ }
+};
+
+static void CompareFilesByBlock(const string& a_file, const string& b_file,
+ size_t image_size) {
+ EXPECT_EQ(0, image_size % kBlockSize);
+
+ brillo::Blob a_data, b_data;
+ EXPECT_TRUE(utils::ReadFile(a_file, &a_data)) << "file failed: " << a_file;
+ EXPECT_TRUE(utils::ReadFile(b_file, &b_data)) << "file failed: " << b_file;
+
+ EXPECT_GE(a_data.size(), image_size);
+ EXPECT_GE(b_data.size(), image_size);
+ for (size_t i = 0; i < image_size; i += kBlockSize) {
+ EXPECT_EQ(0, i % kBlockSize);
+ brillo::Blob a_sub(&a_data[i], &a_data[i + kBlockSize]);
+ brillo::Blob b_sub(&b_data[i], &b_data[i + kBlockSize]);
+ EXPECT_TRUE(a_sub == b_sub) << "Block " << (i/kBlockSize) << " differs";
+ }
+ if (::testing::Test::HasNonfatalFailure()) {
+ LOG(INFO) << "Compared filesystems with size " << image_size
+ << ", partition A " << a_file << " size: " << a_data.size()
+ << ", partition B " << b_file << " size: " << b_data.size();
+ }
+}
+
+static bool WriteSparseFile(const string& path, off_t size) {
+ int fd = open(path.c_str(), O_CREAT | O_TRUNC | O_WRONLY, 0644);
+ TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
+ ScopedFdCloser fd_closer(&fd);
+ off_t rc = lseek(fd, size + 1, SEEK_SET);
+ TEST_AND_RETURN_FALSE_ERRNO(rc != static_cast<off_t>(-1));
+ int return_code = ftruncate(fd, size);
+ TEST_AND_RETURN_FALSE_ERRNO(return_code == 0);
+ return true;
+}
+
+static size_t GetSignatureSize(const string& private_key_path) {
+ const brillo::Blob data(1, 'x');
+ brillo::Blob hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(data, &hash));
+ brillo::Blob signature;
+ EXPECT_TRUE(PayloadSigner::SignHash(hash,
+ private_key_path,
+ &signature));
+ return signature.size();
+}
+
+static bool InsertSignaturePlaceholder(int signature_size,
+ const string& payload_path,
+ uint64_t* out_metadata_size) {
+ vector<brillo::Blob> signatures;
+ signatures.push_back(brillo::Blob(signature_size, 0));
+
+ return PayloadSigner::AddSignatureToPayload(
+ payload_path,
+ signatures,
+ {},
+ payload_path,
+ out_metadata_size);
+}
+
+static void SignGeneratedPayload(const string& payload_path,
+ uint64_t* out_metadata_size) {
+ int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+ brillo::Blob hash;
+ ASSERT_TRUE(PayloadSigner::HashPayloadForSigning(
+ payload_path,
+ vector<int>(1, signature_size),
+ &hash,
+ nullptr));
+ brillo::Blob signature;
+ ASSERT_TRUE(PayloadSigner::SignHash(hash,
+ kUnittestPrivateKeyPath,
+ &signature));
+ ASSERT_TRUE(PayloadSigner::AddSignatureToPayload(
+ payload_path,
+ vector<brillo::Blob>(1, signature),
+ {},
+ payload_path,
+ out_metadata_size));
+ EXPECT_TRUE(PayloadSigner::VerifySignedPayload(
+ payload_path,
+ kUnittestPublicKeyPath));
+}
+
+static void SignGeneratedShellPayload(SignatureTest signature_test,
+ const string& payload_path) {
+ string private_key_path = kUnittestPrivateKeyPath;
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ ASSERT_TRUE(utils::MakeTempFile("key.XXXXXX",
+ &private_key_path,
+ nullptr));
+ } else {
+ ASSERT_TRUE(signature_test == kSignatureGeneratedShell ||
+ signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2);
+ }
+ ScopedPathUnlinker key_unlinker(private_key_path);
+ key_unlinker.set_should_remove(signature_test ==
+ kSignatureGeneratedShellBadKey);
+ // Generates a new private key that will not match the public key.
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ LOG(INFO) << "Generating a mismatched private key.";
+ ASSERT_EQ(0, System(base::StringPrintf(
+ "openssl genrsa -out %s 2048", private_key_path.c_str())));
+ }
+ int signature_size = GetSignatureSize(private_key_path);
+ string hash_file;
+ ASSERT_TRUE(utils::MakeTempFile("hash.XXXXXX", &hash_file, nullptr));
+ ScopedPathUnlinker hash_unlinker(hash_file);
+ string signature_size_string;
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2)
+ signature_size_string = base::StringPrintf("%d:%d",
+ signature_size, signature_size);
+ else
+ signature_size_string = base::StringPrintf("%d", signature_size);
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -signature_size=%s "
+ "-out_hash_file=%s",
+ payload_path.c_str(),
+ signature_size_string.c_str(),
+ hash_file.c_str())));
+
+ // Pad the hash
+ brillo::Blob hash;
+ ASSERT_TRUE(utils::ReadFile(hash_file, &hash));
+ ASSERT_TRUE(PayloadVerifier::PadRSA2048SHA256Hash(&hash));
+ ASSERT_TRUE(test_utils::WriteFileVector(hash_file, hash));
+
+ string sig_file;
+ ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file, nullptr));
+ ScopedPathUnlinker sig_unlinker(sig_file);
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+ private_key_path.c_str(),
+ hash_file.c_str(),
+ sig_file.c_str())));
+ string sig_file2;
+ ASSERT_TRUE(utils::MakeTempFile("signature.XXXXXX", &sig_file2, nullptr));
+ ScopedPathUnlinker sig2_unlinker(sig_file2);
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "openssl rsautl -raw -sign -inkey %s -in %s -out %s",
+ kUnittestPrivateKey2Path,
+ hash_file.c_str(),
+ sig_file2.c_str())));
+ // Append second sig file to first path
+ sig_file += ":" + sig_file2;
+ }
+
+ ASSERT_EQ(0,
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -signature_file=%s "
+ "-out_file=%s",
+ payload_path.c_str(),
+ sig_file.c_str(),
+ payload_path.c_str())));
+ int verify_result =
+ System(base::StringPrintf(
+ "./delta_generator -in_file=%s -public_key=%s -public_key_version=%d",
+ payload_path.c_str(),
+ signature_test == kSignatureGeneratedShellRotateCl2 ?
+ kUnittestPublicKey2Path : kUnittestPublicKeyPath,
+ signature_test == kSignatureGeneratedShellRotateCl2 ? 2 : 1));
+ if (signature_test == kSignatureGeneratedShellBadKey) {
+ ASSERT_NE(0, verify_result);
+ } else {
+ ASSERT_EQ(0, verify_result);
+ }
+}
+
+static void GenerateDeltaFile(bool full_kernel,
+ bool full_rootfs,
+ bool noop,
+ ssize_t chunk_size,
+ SignatureTest signature_test,
+ DeltaState *state,
+ uint32_t minor_version) {
+ EXPECT_TRUE(utils::MakeTempFile("a_img.XXXXXX", &state->a_img, nullptr));
+ EXPECT_TRUE(utils::MakeTempFile("b_img.XXXXXX", &state->b_img, nullptr));
+
+ // result_img is used in minor version 2. Instead of applying the update
+ // in-place on A, we apply it to a new image, result_img.
+ EXPECT_TRUE(
+ utils::MakeTempFile("result_img.XXXXXX", &state->result_img, nullptr));
+ test_utils::CreateExtImageAtPath(state->a_img, nullptr);
+
+ state->image_size = utils::FileSize(state->a_img);
+
+ // Create ImageInfo A & B
+ ImageInfo old_image_info;
+ ImageInfo new_image_info;
+
+ if (!full_rootfs) {
+ old_image_info.set_channel("src-channel");
+ old_image_info.set_board("src-board");
+ old_image_info.set_version("src-version");
+ old_image_info.set_key("src-key");
+ old_image_info.set_build_channel("src-build-channel");
+ old_image_info.set_build_version("src-build-version");
+ }
+
+ new_image_info.set_channel("test-channel");
+ new_image_info.set_board("test-board");
+ new_image_info.set_version("test-version");
+ new_image_info.set_key("test-key");
+ new_image_info.set_build_channel("test-build-channel");
+ new_image_info.set_build_version("test-build-version");
+
+ // Make some changes to the A image.
+ {
+ string a_mnt;
+ ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0);
+
+ brillo::Blob hardtocompress;
+ while (hardtocompress.size() < 3 * kBlockSize) {
+ hardtocompress.insert(hardtocompress.end(),
+ std::begin(kRandomString), std::end(kRandomString));
+ }
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+ a_mnt.c_str()).c_str(),
+ hardtocompress.data(),
+ hardtocompress.size()));
+
+ brillo::Blob zeros(16 * 1024, 0);
+ EXPECT_EQ(zeros.size(),
+ base::WriteFile(base::FilePath(base::StringPrintf(
+ "%s/move-to-sparse", a_mnt.c_str())),
+ reinterpret_cast<const char*>(zeros.data()),
+ zeros.size()));
+
+ EXPECT_TRUE(
+ WriteSparseFile(base::StringPrintf("%s/move-from-sparse",
+ a_mnt.c_str()), 16 * 1024));
+
+ EXPECT_EQ(0,
+ System(base::StringPrintf("dd if=/dev/zero of=%s/move-semi-sparse"
+ " bs=1 seek=4096 count=1 status=none",
+ a_mnt.c_str()).c_str()));
+
+ // Write 1 MiB of 0xff to try to catch the case where writing a bsdiff
+ // patch fails to zero out the final block.
+ brillo::Blob ones(1024 * 1024, 0xff);
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/ones",
+ a_mnt.c_str()).c_str(),
+ ones.data(),
+ ones.size()));
+ }
+
+ if (noop) {
+ EXPECT_TRUE(base::CopyFile(base::FilePath(state->a_img),
+ base::FilePath(state->b_img)));
+ old_image_info = new_image_info;
+ } else {
+ if (minor_version == kSourceMinorPayloadVersion) {
+ // Create a result image with image_size bytes of garbage.
+ brillo::Blob ones(state->image_size, 0xff);
+ EXPECT_TRUE(utils::WriteFile(state->result_img.c_str(),
+ ones.data(),
+ ones.size()));
+ EXPECT_EQ(utils::FileSize(state->a_img),
+ utils::FileSize(state->result_img));
+ }
+
+ test_utils::CreateExtImageAtPath(state->b_img, nullptr);
+
+ // Make some changes to the B image.
+ string b_mnt;
+ ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0);
+
+ EXPECT_EQ(0, System(base::StringPrintf("cp %s/hello %s/hello2",
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("rm %s/hello",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("mv %s/hello2 %s/hello",
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("echo foo > %s/foo",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("touch %s/emptyfile",
+ b_mnt.c_str()).c_str()));
+ EXPECT_TRUE(WriteSparseFile(base::StringPrintf("%s/fullsparse",
+ b_mnt.c_str()),
+ 1024 * 1024));
+
+ EXPECT_TRUE(
+ WriteSparseFile(base::StringPrintf("%s/move-to-sparse", b_mnt.c_str()),
+ 16 * 1024));
+
+ brillo::Blob zeros(16 * 1024, 0);
+ EXPECT_EQ(zeros.size(),
+ base::WriteFile(base::FilePath(base::StringPrintf(
+ "%s/move-from-sparse", b_mnt.c_str())),
+ reinterpret_cast<const char*>(zeros.data()),
+ zeros.size()));
+
+ EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+ "of=%s/move-semi-sparse "
+ "bs=1 seek=4096 count=1 status=none",
+ b_mnt.c_str()).c_str()));
+
+ EXPECT_EQ(0, System(base::StringPrintf("dd if=/dev/zero "
+ "of=%s/partsparse bs=1 "
+ "seek=4096 count=1 status=none",
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(base::StringPrintf("cp %s/srchardlink0 %s/tmp && "
+ "mv %s/tmp %s/srchardlink1",
+ b_mnt.c_str(),
+ b_mnt.c_str(),
+ b_mnt.c_str(),
+ b_mnt.c_str()).c_str()));
+ EXPECT_EQ(0, System(
+ base::StringPrintf("rm %s/boguslink && echo foobar > %s/boguslink",
+ b_mnt.c_str(), b_mnt.c_str()).c_str()));
+
+ brillo::Blob hardtocompress;
+ while (hardtocompress.size() < 3 * kBlockSize) {
+ hardtocompress.insert(hardtocompress.end(),
+ std::begin(kRandomString), std::end(kRandomString));
+ }
+ EXPECT_TRUE(utils::WriteFile(base::StringPrintf("%s/hardtocompress",
+ b_mnt.c_str()).c_str(),
+ hardtocompress.data(),
+ hardtocompress.size()));
+ }
+
+ string old_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("old_kernel.XXXXXX",
+ &state->old_kernel,
+ nullptr));
+
+ string new_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("new_kernel.XXXXXX",
+ &state->new_kernel,
+ nullptr));
+
+ string result_kernel;
+ EXPECT_TRUE(utils::MakeTempFile("result_kernel.XXXXXX",
+ &state->result_kernel,
+ nullptr));
+
+ state->kernel_size = kDefaultKernelSize;
+ state->old_kernel_data.resize(kDefaultKernelSize);
+ state->new_kernel_data.resize(state->old_kernel_data.size());
+ state->result_kernel_data.resize(state->old_kernel_data.size());
+ test_utils::FillWithData(&state->old_kernel_data);
+ test_utils::FillWithData(&state->new_kernel_data);
+ test_utils::FillWithData(&state->result_kernel_data);
+
+ // change the new kernel data
+ std::copy(std::begin(kNewData), std::end(kNewData),
+ state->new_kernel_data.begin());
+
+ if (noop) {
+ state->old_kernel_data = state->new_kernel_data;
+ }
+
+ // Write kernels to disk
+ EXPECT_TRUE(utils::WriteFile(state->old_kernel.c_str(),
+ state->old_kernel_data.data(),
+ state->old_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(),
+ state->new_kernel_data.data(),
+ state->new_kernel_data.size()));
+ EXPECT_TRUE(utils::WriteFile(state->result_kernel.c_str(),
+ state->result_kernel_data.data(),
+ state->result_kernel_data.size()));
+
+ EXPECT_TRUE(utils::MakeTempFile("delta.XXXXXX",
+ &state->delta_path,
+ nullptr));
+ LOG(INFO) << "delta path: " << state->delta_path;
+ {
+ const string private_key =
+ signature_test == kSignatureGenerator ? kUnittestPrivateKeyPath : "";
+
+ PayloadGenerationConfig payload_config;
+ payload_config.is_delta = !full_rootfs;
+ payload_config.hard_chunk_size = chunk_size;
+ payload_config.rootfs_partition_size = kRootFSPartitionSize;
+ payload_config.major_version = kChromeOSMajorPayloadVersion;
+ payload_config.minor_version = minor_version;
+ if (!full_rootfs) {
+ payload_config.source.partitions.emplace_back(kLegacyPartitionNameRoot);
+ payload_config.source.partitions.emplace_back(kLegacyPartitionNameKernel);
+ payload_config.source.partitions.front().path = state->a_img;
+ if (!full_kernel)
+ payload_config.source.partitions.back().path = state->old_kernel;
+ payload_config.source.image_info = old_image_info;
+ EXPECT_TRUE(payload_config.source.LoadImageSize());
+ for (PartitionConfig& part : payload_config.source.partitions)
+ EXPECT_TRUE(part.OpenFilesystem());
+ } else {
+ if (payload_config.hard_chunk_size == -1)
+ // Use 1 MiB chunk size for the full unittests.
+ payload_config.hard_chunk_size = 1024 * 1024;
+ }
+ payload_config.target.partitions.emplace_back(kLegacyPartitionNameRoot);
+ payload_config.target.partitions.back().path = state->b_img;
+ payload_config.target.partitions.emplace_back(kLegacyPartitionNameKernel);
+ payload_config.target.partitions.back().path = state->new_kernel;
+ payload_config.target.image_info = new_image_info;
+ EXPECT_TRUE(payload_config.target.LoadImageSize());
+ for (PartitionConfig& part : payload_config.target.partitions)
+ EXPECT_TRUE(part.OpenFilesystem());
+
+ EXPECT_TRUE(payload_config.Validate());
+ EXPECT_TRUE(
+ GenerateUpdatePayloadFile(
+ payload_config,
+ state->delta_path,
+ private_key,
+ &state->metadata_size));
+ }
+ // Extend the "partitions" holding the file system a bit.
+ EXPECT_EQ(0, HANDLE_EINTR(truncate(state->a_img.c_str(),
+ state->image_size + 1024 * 1024)));
+ EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->a_img));
+ EXPECT_EQ(0, HANDLE_EINTR(truncate(state->b_img.c_str(),
+ state->image_size + 1024 * 1024)));
+ EXPECT_EQ(state->image_size + 1024 * 1024, utils::FileSize(state->b_img));
+
+ if (signature_test == kSignatureGeneratedPlaceholder ||
+ signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ int signature_size = GetSignatureSize(kUnittestPrivateKeyPath);
+ LOG(INFO) << "Inserting placeholder signature.";
+ ASSERT_TRUE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+ &state->metadata_size));
+
+ if (signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ signature_size -= 1;
+ LOG(INFO) << "Inserting mismatched placeholder signature.";
+ ASSERT_FALSE(InsertSignaturePlaceholder(signature_size, state->delta_path,
+ &state->metadata_size));
+ return;
+ }
+ }
+
+ if (signature_test == kSignatureGenerated ||
+ signature_test == kSignatureGeneratedPlaceholder ||
+ signature_test == kSignatureGeneratedPlaceholderMismatch) {
+ // Generate the signed payload and update the metadata size in state to
+ // reflect the new size after adding the signature operation to the
+ // manifest.
+ LOG(INFO) << "Signing payload.";
+ SignGeneratedPayload(state->delta_path, &state->metadata_size);
+ } else if (signature_test == kSignatureGeneratedShell ||
+ signature_test == kSignatureGeneratedShellBadKey ||
+ signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ SignGeneratedShellPayload(signature_test, state->delta_path);
+ }
+}
+
+static void ApplyDeltaFile(bool full_kernel, bool full_rootfs, bool noop,
+ SignatureTest signature_test, DeltaState* state,
+ bool hash_checks_mandatory,
+ OperationHashTest op_hash_test,
+ DeltaPerformer** performer,
+ uint32_t minor_version) {
+ // Check the metadata.
+ {
+ DeltaArchiveManifest manifest;
+ EXPECT_TRUE(PayloadSigner::LoadPayload(state->delta_path,
+ &state->delta,
+ &manifest,
+ nullptr,
+ &state->metadata_size,
+ nullptr));
+ LOG(INFO) << "Metadata size: " << state->metadata_size;
+
+
+
+ if (signature_test == kSignatureNone) {
+ EXPECT_FALSE(manifest.has_signatures_offset());
+ EXPECT_FALSE(manifest.has_signatures_size());
+ } else {
+ EXPECT_TRUE(manifest.has_signatures_offset());
+ EXPECT_TRUE(manifest.has_signatures_size());
+ Signatures sigs_message;
+ EXPECT_TRUE(sigs_message.ParseFromArray(
+ &state->delta[state->metadata_size + manifest.signatures_offset()],
+ manifest.signatures_size()));
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2)
+ EXPECT_EQ(2, sigs_message.signatures_size());
+ else
+ EXPECT_EQ(1, sigs_message.signatures_size());
+ const Signatures_Signature& signature = sigs_message.signatures(0);
+ EXPECT_EQ(1, signature.version());
+
+ uint64_t expected_sig_data_length = 0;
+ vector<string> key_paths{kUnittestPrivateKeyPath};
+ if (signature_test == kSignatureGeneratedShellRotateCl1 ||
+ signature_test == kSignatureGeneratedShellRotateCl2) {
+ key_paths.push_back(kUnittestPrivateKey2Path);
+ }
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength(
+ key_paths,
+ &expected_sig_data_length));
+ EXPECT_EQ(expected_sig_data_length, manifest.signatures_size());
+ EXPECT_FALSE(signature.data().empty());
+ }
+
+ if (noop) {
+ EXPECT_EQ(0, manifest.install_operations_size());
+ EXPECT_EQ(1, manifest.kernel_install_operations_size());
+ }
+
+ if (full_kernel) {
+ EXPECT_FALSE(manifest.has_old_kernel_info());
+ } else {
+ EXPECT_EQ(state->old_kernel_data.size(),
+ manifest.old_kernel_info().size());
+ EXPECT_FALSE(manifest.old_kernel_info().hash().empty());
+ }
+
+ EXPECT_EQ(manifest.new_image_info().channel(), "test-channel");
+ EXPECT_EQ(manifest.new_image_info().board(), "test-board");
+ EXPECT_EQ(manifest.new_image_info().version(), "test-version");
+ EXPECT_EQ(manifest.new_image_info().key(), "test-key");
+ EXPECT_EQ(manifest.new_image_info().build_channel(), "test-build-channel");
+ EXPECT_EQ(manifest.new_image_info().build_version(), "test-build-version");
+
+ if (!full_rootfs) {
+ if (noop) {
+ EXPECT_EQ(manifest.old_image_info().channel(), "test-channel");
+ EXPECT_EQ(manifest.old_image_info().board(), "test-board");
+ EXPECT_EQ(manifest.old_image_info().version(), "test-version");
+ EXPECT_EQ(manifest.old_image_info().key(), "test-key");
+ EXPECT_EQ(manifest.old_image_info().build_channel(),
+ "test-build-channel");
+ EXPECT_EQ(manifest.old_image_info().build_version(),
+ "test-build-version");
+ } else {
+ EXPECT_EQ(manifest.old_image_info().channel(), "src-channel");
+ EXPECT_EQ(manifest.old_image_info().board(), "src-board");
+ EXPECT_EQ(manifest.old_image_info().version(), "src-version");
+ EXPECT_EQ(manifest.old_image_info().key(), "src-key");
+ EXPECT_EQ(manifest.old_image_info().build_channel(),
+ "src-build-channel");
+ EXPECT_EQ(manifest.old_image_info().build_version(),
+ "src-build-version");
+ }
+ }
+
+
+ if (full_rootfs) {
+ EXPECT_FALSE(manifest.has_old_rootfs_info());
+ EXPECT_FALSE(manifest.has_old_image_info());
+ EXPECT_TRUE(manifest.has_new_image_info());
+ } else {
+ EXPECT_EQ(state->image_size, manifest.old_rootfs_info().size());
+ EXPECT_FALSE(manifest.old_rootfs_info().hash().empty());
+ }
+
+ EXPECT_EQ(state->new_kernel_data.size(), manifest.new_kernel_info().size());
+ EXPECT_EQ(state->image_size, manifest.new_rootfs_info().size());
+
+ EXPECT_FALSE(manifest.new_kernel_info().hash().empty());
+ EXPECT_FALSE(manifest.new_rootfs_info().hash().empty());
+ }
+
+ MockPrefs prefs;
+ EXPECT_CALL(prefs, SetInt64(kPrefsManifestMetadataSize,
+ state->metadata_size)).WillOnce(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, GetInt64(kPrefsUpdateStateNextOperation, _))
+ .WillOnce(Return(false));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataOffset, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetInt64(kPrefsUpdateStateNextDataLength, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignedSHA256Context, _))
+ .WillRepeatedly(Return(true));
+ if (op_hash_test == kValidOperationData && signature_test != kSignatureNone) {
+ EXPECT_CALL(prefs, SetString(kPrefsUpdateStateSignatureBlob, _))
+ .WillOnce(Return(true));
+ }
+
+ // Update the A image in place.
+ InstallPlan* install_plan = &state->install_plan;
+ install_plan->hash_checks_mandatory = hash_checks_mandatory;
+ install_plan->metadata_size = state->metadata_size;
+ install_plan->is_full_update = full_kernel && full_rootfs;
+ install_plan->source_slot = 0;
+ install_plan->target_slot = 1;
+
+ InstallPlan::Partition root_part;
+ root_part.name = kLegacyPartitionNameRoot;
+
+ InstallPlan::Partition kernel_part;
+ kernel_part.name = kLegacyPartitionNameKernel;
+
+ LOG(INFO) << "Setting payload metadata size in Omaha = "
+ << state->metadata_size;
+ ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+ state->delta.data(),
+ state->metadata_size,
+ kUnittestPrivateKeyPath,
+ &install_plan->metadata_signature));
+ EXPECT_FALSE(install_plan->metadata_signature.empty());
+
+ *performer = new DeltaPerformer(&prefs,
+ &state->fake_system_state,
+ install_plan);
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ (*performer)->set_public_key_path(kUnittestPublicKeyPath);
+ DeltaPerformerIntegrationTest::SetSupportedVersion(*performer, minor_version);
+
+ EXPECT_EQ(state->image_size,
+ HashCalculator::RawHashOfFile(
+ state->a_img,
+ state->image_size,
+ &root_part.source_hash));
+ EXPECT_TRUE(HashCalculator::RawHashOfData(
+ state->old_kernel_data,
+ &kernel_part.source_hash));
+
+ // This partitions are normally filed by the FilesystemVerifierAction with
+ // the source hashes used for deltas.
+ install_plan->partitions = {root_part, kernel_part};
+
+ // With minor version 2, we want the target to be the new image, result_img,
+ // but with version 1, we want to update A in place.
+ string target_root, target_kernel;
+ if (minor_version == kSourceMinorPayloadVersion) {
+ target_root = state->result_img;
+ target_kernel = state->result_kernel;
+ } else {
+ target_root = state->a_img;
+ target_kernel = state->old_kernel;
+ }
+
+ state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan->source_slot, state->a_img);
+ state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan->source_slot, state->old_kernel);
+ state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan->target_slot, target_root);
+ state->fake_system_state.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan->target_slot, target_kernel);
+
+ ErrorCode expected_error, actual_error;
+ bool continue_writing;
+ switch (op_hash_test) {
+ case kInvalidOperationData: {
+ // Muck with some random offset post the metadata size so that
+ // some operation hash will result in a mismatch.
+ int some_offset = state->metadata_size + 300;
+ LOG(INFO) << "Tampered value at offset: " << some_offset;
+ state->delta[some_offset]++;
+ expected_error = ErrorCode::kDownloadOperationHashMismatch;
+ continue_writing = false;
+ break;
+ }
+
+ case kValidOperationData:
+ default:
+ // no change.
+ expected_error = ErrorCode::kSuccess;
+ continue_writing = true;
+ break;
+ }
+
+ // Write at some number of bytes per operation. Arbitrarily chose 5.
+ const size_t kBytesPerWrite = 5;
+ for (size_t i = 0; i < state->delta.size(); i += kBytesPerWrite) {
+ size_t count = std::min(state->delta.size() - i, kBytesPerWrite);
+ bool write_succeeded = ((*performer)->Write(&state->delta[i],
+ count,
+ &actual_error));
+ // Normally write_succeeded should be true every time and
+ // actual_error should be ErrorCode::kSuccess. If so, continue the loop.
+ // But if we seeded an operation hash error above, then write_succeeded
+ // will be false. The failure may happen at any operation n. So, all
+ // Writes until n-1 should succeed and the nth operation will fail with
+ // actual_error. In this case, we should bail out of the loop because
+ // we cannot proceed applying the delta.
+ if (!write_succeeded) {
+ LOG(INFO) << "Write failed. Checking if it failed with expected error";
+ EXPECT_EQ(expected_error, actual_error);
+ if (!continue_writing) {
+ LOG(INFO) << "Cannot continue writing. Bailing out.";
+ break;
+ }
+ }
+
+ EXPECT_EQ(ErrorCode::kSuccess, actual_error);
+ }
+
+ // If we had continued all the way through, Close should succeed.
+ // Otherwise, it should fail. Check appropriately.
+ bool close_result = (*performer)->Close();
+ if (continue_writing)
+ EXPECT_EQ(0, close_result);
+ else
+ EXPECT_LE(0, close_result);
+}
+
+void VerifyPayloadResult(DeltaPerformer* performer,
+ DeltaState* state,
+ ErrorCode expected_result,
+ uint32_t minor_version) {
+ if (!performer) {
+ EXPECT_TRUE(!"Skipping payload verification since performer is null.");
+ return;
+ }
+
+ int expected_times = (expected_result == ErrorCode::kSuccess) ? 1 : 0;
+ EXPECT_CALL(*(state->fake_system_state.mock_payload_state()),
+ DownloadComplete()).Times(expected_times);
+
+ LOG(INFO) << "Verifying payload for expected result "
+ << expected_result;
+ EXPECT_EQ(expected_result, performer->VerifyPayload(
+ HashCalculator::HashOfData(state->delta),
+ state->delta.size()));
+ LOG(INFO) << "Verified payload.";
+
+ if (expected_result != ErrorCode::kSuccess) {
+ // no need to verify new partition if VerifyPayload failed.
+ return;
+ }
+
+ brillo::Blob updated_kernel_partition;
+ if (minor_version == kSourceMinorPayloadVersion) {
+ CompareFilesByBlock(state->result_kernel, state->new_kernel,
+ state->kernel_size);
+ CompareFilesByBlock(state->result_img, state->b_img,
+ state->image_size);
+ EXPECT_TRUE(utils::ReadFile(state->result_kernel,
+ &updated_kernel_partition));
+ } else {
+ CompareFilesByBlock(state->old_kernel, state->new_kernel,
+ state->kernel_size);
+ CompareFilesByBlock(state->a_img, state->b_img,
+ state->image_size);
+ EXPECT_TRUE(utils::ReadFile(state->old_kernel, &updated_kernel_partition));
+ }
+
+ ASSERT_GE(updated_kernel_partition.size(), arraysize(kNewData));
+ EXPECT_TRUE(std::equal(std::begin(kNewData), std::end(kNewData),
+ updated_kernel_partition.begin()));
+
+ const auto& partitions = state->install_plan.partitions;
+ EXPECT_EQ(2, partitions.size());
+ EXPECT_EQ(kLegacyPartitionNameRoot, partitions[0].name);
+ EXPECT_EQ(kLegacyPartitionNameKernel, partitions[1].name);
+
+ EXPECT_EQ(kDefaultKernelSize, partitions[1].target_size);
+ brillo::Blob expected_new_kernel_hash;
+ EXPECT_TRUE(HashCalculator::RawHashOfData(state->new_kernel_data,
+ &expected_new_kernel_hash));
+ EXPECT_EQ(expected_new_kernel_hash, partitions[1].target_hash);
+
+ EXPECT_EQ(state->image_size, partitions[0].target_size);
+ brillo::Blob expected_new_rootfs_hash;
+ EXPECT_EQ(state->image_size,
+ HashCalculator::RawHashOfFile(state->b_img,
+ state->image_size,
+ &expected_new_rootfs_hash));
+ EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
+}
+
+void VerifyPayload(DeltaPerformer* performer,
+ DeltaState* state,
+ SignatureTest signature_test,
+ uint32_t minor_version) {
+ ErrorCode expected_result = ErrorCode::kSuccess;
+ switch (signature_test) {
+ case kSignatureNone:
+ expected_result = ErrorCode::kSignedDeltaPayloadExpectedError;
+ break;
+ case kSignatureGeneratedShellBadKey:
+ expected_result = ErrorCode::kDownloadPayloadPubKeyVerificationError;
+ break;
+ default: break; // appease gcc
+ }
+
+ VerifyPayloadResult(performer, state, expected_result, minor_version);
+}
+
+void DoSmallImageTest(bool full_kernel, bool full_rootfs, bool noop,
+ ssize_t chunk_size,
+ SignatureTest signature_test,
+ bool hash_checks_mandatory, uint32_t minor_version) {
+ DeltaState state;
+ DeltaPerformer *performer = nullptr;
+ GenerateDeltaFile(full_kernel, full_rootfs, noop, chunk_size,
+ signature_test, &state, minor_version);
+
+ ScopedPathUnlinker a_img_unlinker(state.a_img);
+ ScopedPathUnlinker b_img_unlinker(state.b_img);
+ ScopedPathUnlinker new_img_unlinker(state.result_img);
+ ScopedPathUnlinker delta_unlinker(state.delta_path);
+ ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+ ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+ ScopedPathUnlinker result_kernel_unlinker(state.result_kernel);
+ ApplyDeltaFile(full_kernel, full_rootfs, noop, signature_test,
+ &state, hash_checks_mandatory, kValidOperationData,
+ &performer, minor_version);
+ VerifyPayload(performer, &state, signature_test, minor_version);
+ delete performer;
+}
+
+void DoOperationHashMismatchTest(OperationHashTest op_hash_test,
+ bool hash_checks_mandatory) {
+ DeltaState state;
+ uint64_t minor_version = kFullPayloadMinorVersion;
+ GenerateDeltaFile(true, true, false, -1, kSignatureGenerated, &state,
+ minor_version);
+ ScopedPathUnlinker a_img_unlinker(state.a_img);
+ ScopedPathUnlinker b_img_unlinker(state.b_img);
+ ScopedPathUnlinker delta_unlinker(state.delta_path);
+ ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
+ ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
+ DeltaPerformer *performer = nullptr;
+ ApplyDeltaFile(true, true, false, kSignatureGenerated, &state,
+ hash_checks_mandatory, op_hash_test, &performer,
+ minor_version);
+ delete performer;
+}
+
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignaturePlaceholderTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedPlaceholder,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignaturePlaceholderMismatchTest) {
+ DeltaState state;
+ GenerateDeltaFile(false, false, false, -1,
+ kSignatureGeneratedPlaceholderMismatch, &state,
+ kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageChunksTest) {
+ DoSmallImageTest(false, false, false, kBlockSize, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootFullKernelSmallImageTest) {
+ DoSmallImageTest(true, false, false, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootFullSmallImageTest) {
+ DoSmallImageTest(true, true, false, -1, kSignatureGenerator,
+ true, kFullPayloadMinorVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootNoopSmallImageTest) {
+ DoSmallImageTest(false, false, true, -1, kSignatureGenerator,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignNoneTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureNone,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerated,
+ true, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShell,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellBadKeyTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellBadKey,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellRotateCl1Test) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl1,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSignGeneratedShellRotateCl2Test) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGeneratedShellRotateCl2,
+ false, kInPlaceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootSmallImageSourceOpsTest) {
+ DoSmallImageTest(false, false, false, -1, kSignatureGenerator,
+ false, kSourceMinorPayloadVersion);
+}
+
+TEST(DeltaPerformerIntegrationTest, RunAsRootMandatoryOperationHashMismatchTest) {
+ DoOperationHashMismatchTest(kInvalidOperationData, true);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
new file mode 100644
index 0000000..c0a1a57
--- /dev/null
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -0,0 +1,736 @@
+//
+// 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/delta_performer.h"
+
+#include <inttypes.h>
+
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <google/protobuf/repeated_field.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_hardware.h"
+#include "update_engine/common/fake_prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+#include "update_engine/payload_generator/bzip.h"
+#include "update_engine/payload_generator/extent_ranges.h"
+#include "update_engine/payload_generator/payload_file.h"
+#include "update_engine/payload_generator/payload_signer.h"
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+using test_utils::System;
+using test_utils::kRandomString;
+
+extern const char* kUnittestPrivateKeyPath;
+extern const char* kUnittestPublicKeyPath;
+
+static const char* kBogusMetadataSignature1 =
+ "awSFIUdUZz2VWFiR+ku0Pj00V7bPQPQFYQSXjEXr3vaw3TE4xHV5CraY3/YrZpBv"
+ "J5z4dSBskoeuaO1TNC/S6E05t+yt36tE4Fh79tMnJ/z9fogBDXWgXLEUyG78IEQr"
+ "YH6/eBsQGT2RJtBgXIXbZ9W+5G9KmGDoPOoiaeNsDuqHiBc/58OFsrxskH8E6vMS"
+ "BmMGGk82mvgzic7ApcoURbCGey1b3Mwne/hPZ/bb9CIyky8Og9IfFMdL2uAweOIR"
+ "fjoTeLYZpt+WN65Vu7jJ0cQN8e1y+2yka5112wpRf/LLtPgiAjEZnsoYpLUd7CoV"
+ "pLRtClp97kN2+tXGNBQqkA==";
+
+namespace {
+// Different options that determine what we should fill into the
+// install_plan.metadata_signature to simulate the contents received in the
+// Omaha response.
+enum MetadataSignatureTest {
+ kEmptyMetadataSignature,
+ kInvalidMetadataSignature,
+ kValidMetadataSignature,
+};
+
+// Compressed data without checksum, generated with:
+// echo -n a | xz -9 --check=none | hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kXzCompressedData[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0x01, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x11, 0x01,
+ 0xad, 0xa6, 0x58, 0x04, 0x06, 0x72, 0x9e, 0x7a, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x59, 0x5a,
+};
+
+} // namespace
+
+class DeltaPerformerTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ install_plan_.source_slot = 0;
+ install_plan_.target_slot = 1;
+ }
+
+ // Test helper placed where it can easily be friended from DeltaPerformer.
+ void RunManifestValidation(const DeltaArchiveManifest& manifest,
+ uint64_t major_version,
+ bool full_payload,
+ ErrorCode expected) {
+ // The install plan is for Full or Delta.
+ install_plan_.is_full_update = full_payload;
+
+ // The Manifest we are validating.
+ performer_.manifest_.CopyFrom(manifest);
+ performer_.major_payload_version_ = major_version;
+
+ EXPECT_EQ(expected, performer_.ValidateManifest());
+ }
+
+ brillo::Blob GeneratePayload(const brillo::Blob& blob_data,
+ const vector<AnnotatedOperation>& aops,
+ bool sign_payload,
+ uint64_t major_version,
+ uint32_t minor_version) {
+ string blob_path;
+ EXPECT_TRUE(utils::MakeTempFile("Blob-XXXXXX", &blob_path, nullptr));
+ ScopedPathUnlinker blob_unlinker(blob_path);
+ EXPECT_TRUE(utils::WriteFile(blob_path.c_str(),
+ blob_data.data(),
+ blob_data.size()));
+
+ PayloadGenerationConfig config;
+ config.major_version = major_version;
+ config.minor_version = minor_version;
+
+ PayloadFile payload;
+ EXPECT_TRUE(payload.Init(config));
+
+ PartitionConfig old_part(kLegacyPartitionNameRoot);
+ PartitionConfig new_part(kLegacyPartitionNameRoot);
+ new_part.path = "/dev/zero";
+ new_part.size = 1234;
+
+ payload.AddPartition(old_part, new_part, aops);
+
+ // We include a kernel partition without operations.
+ old_part.name = kLegacyPartitionNameKernel;
+ new_part.name = kLegacyPartitionNameKernel;
+ new_part.size = 0;
+ payload.AddPartition(old_part, new_part, {});
+
+ string payload_path;
+ EXPECT_TRUE(utils::MakeTempFile("Payload-XXXXXX", &payload_path, nullptr));
+ ScopedPathUnlinker payload_unlinker(payload_path);
+ EXPECT_TRUE(payload.WritePayload(payload_path, blob_path,
+ sign_payload ? kUnittestPrivateKeyPath : "",
+ &install_plan_.metadata_size));
+
+ brillo::Blob payload_data;
+ EXPECT_TRUE(utils::ReadFile(payload_path, &payload_data));
+ return payload_data;
+ }
+
+ // Apply |payload_data| on partition specified in |source_path|.
+ brillo::Blob ApplyPayload(const brillo::Blob& payload_data,
+ const string& source_path) {
+ return ApplyPayloadToData(payload_data, source_path, brillo::Blob());
+ }
+
+ // Apply the payload provided in |payload_data| reading from the |source_path|
+ // file and writing the contents to a new partition. The existing data in the
+ // new target file are set to |target_data| before applying the payload.
+ // Returns the result of the payload application.
+ brillo::Blob ApplyPayloadToData(const brillo::Blob& payload_data,
+ const string& source_path,
+ const brillo::Blob& target_data) {
+ string new_part;
+ EXPECT_TRUE(utils::MakeTempFile("Partition-XXXXXX", &new_part, nullptr));
+ ScopedPathUnlinker partition_unlinker(new_part);
+ EXPECT_TRUE(utils::WriteFile(new_part.c_str(), target_data.data(),
+ target_data.size()));
+
+ // We installed the operations only in the rootfs partition, but the
+ // delta performer needs to access all the partitions.
+ fake_system_state_.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan_.target_slot, new_part);
+ fake_system_state_.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan_.source_slot, source_path);
+ fake_system_state_.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan_.target_slot, "/dev/null");
+ fake_system_state_.fake_boot_control()->SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan_.source_slot, "/dev/null");
+
+ EXPECT_TRUE(performer_.Write(payload_data.data(), payload_data.size()));
+ EXPECT_EQ(0, performer_.Close());
+
+ brillo::Blob partition_data;
+ EXPECT_TRUE(utils::ReadFile(new_part, &partition_data));
+ return partition_data;
+ }
+
+ // Calls delta performer's Write method by pretending to pass in bytes from a
+ // delta file whose metadata size is actual_metadata_size and tests if all
+ // checks are correctly performed if the install plan contains
+ // expected_metadata_size and that the result of the parsing are as per
+ // hash_checks_mandatory flag.
+ void DoMetadataSizeTest(uint64_t expected_metadata_size,
+ uint64_t actual_metadata_size,
+ bool hash_checks_mandatory) {
+ install_plan_.hash_checks_mandatory = hash_checks_mandatory;
+
+ // Set a valid magic string and version number 1.
+ EXPECT_TRUE(performer_.Write("CrAU", 4));
+ uint64_t version = htobe64(kChromeOSMajorPayloadVersion);
+ EXPECT_TRUE(performer_.Write(&version, 8));
+
+ install_plan_.metadata_size = expected_metadata_size;
+ ErrorCode error_code;
+ // When filling in size in manifest, exclude the size of the 20-byte header.
+ uint64_t size_in_manifest = htobe64(actual_metadata_size - 20);
+ bool result = performer_.Write(&size_in_manifest, 8, &error_code);
+ if (expected_metadata_size == actual_metadata_size ||
+ !hash_checks_mandatory) {
+ EXPECT_TRUE(result);
+ } else {
+ EXPECT_FALSE(result);
+ EXPECT_EQ(ErrorCode::kDownloadInvalidMetadataSize, error_code);
+ }
+
+ EXPECT_LT(performer_.Close(), 0);
+ }
+
+ // Generates a valid delta file but tests the delta performer by suppling
+ // different metadata signatures as per metadata_signature_test flag and
+ // sees if the result of the parsing are as per hash_checks_mandatory flag.
+ void DoMetadataSignatureTest(MetadataSignatureTest metadata_signature_test,
+ bool sign_payload,
+ bool hash_checks_mandatory) {
+
+ // Loads the payload and parses the manifest.
+ brillo::Blob payload = GeneratePayload(brillo::Blob(),
+ vector<AnnotatedOperation>(), sign_payload,
+ kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+ LOG(INFO) << "Payload size: " << payload.size();
+
+ install_plan_.hash_checks_mandatory = hash_checks_mandatory;
+
+ DeltaPerformer::MetadataParseResult expected_result, actual_result;
+ ErrorCode expected_error, actual_error;
+
+ // Fill up the metadata signature in install plan according to the test.
+ switch (metadata_signature_test) {
+ case kEmptyMetadataSignature:
+ install_plan_.metadata_signature.clear();
+ expected_result = DeltaPerformer::kMetadataParseError;
+ expected_error = ErrorCode::kDownloadMetadataSignatureMissingError;
+ break;
+
+ case kInvalidMetadataSignature:
+ install_plan_.metadata_signature = kBogusMetadataSignature1;
+ expected_result = DeltaPerformer::kMetadataParseError;
+ expected_error = ErrorCode::kDownloadMetadataSignatureMismatch;
+ break;
+
+ case kValidMetadataSignature:
+ default:
+ // Set the install plan's metadata size to be the same as the one
+ // in the manifest so that we pass the metadata size checks. Only
+ // then we can get to manifest signature checks.
+ ASSERT_TRUE(PayloadSigner::GetMetadataSignature(
+ payload.data(),
+ install_plan_.metadata_size,
+ kUnittestPrivateKeyPath,
+ &install_plan_.metadata_signature));
+ EXPECT_FALSE(install_plan_.metadata_signature.empty());
+ expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_error = ErrorCode::kSuccess;
+ break;
+ }
+
+ // Ignore the expected result/error if hash checks are not mandatory.
+ if (!hash_checks_mandatory) {
+ expected_result = DeltaPerformer::kMetadataParseSuccess;
+ expected_error = ErrorCode::kSuccess;
+ }
+
+ // Use the public key corresponding to the private key used above to
+ // sign the metadata.
+ EXPECT_TRUE(utils::FileExists(kUnittestPublicKeyPath));
+ performer_.set_public_key_path(kUnittestPublicKeyPath);
+
+ // Init actual_error with an invalid value so that we make sure
+ // ParsePayloadMetadata properly populates it in all cases.
+ actual_error = ErrorCode::kUmaReportedMax;
+ actual_result = performer_.ParsePayloadMetadata(payload, &actual_error);
+
+ EXPECT_EQ(expected_result, actual_result);
+ EXPECT_EQ(expected_error, actual_error);
+
+ // Check that the parsed metadata size is what's expected. This test
+ // implicitly confirms that the metadata signature is valid, if required.
+ EXPECT_EQ(install_plan_.metadata_size, performer_.GetMetadataSize());
+ }
+
+ void SetSupportedMajorVersion(uint64_t major_version) {
+ performer_.supported_major_version_ = major_version;
+ }
+ FakePrefs prefs_;
+ InstallPlan install_plan_;
+ FakeSystemState fake_system_state_;
+ DeltaPerformer performer_{&prefs_, &fake_system_state_, &install_plan_};
+};
+
+TEST_F(DeltaPerformerTest, FullPayloadWriteTest) {
+ install_plan_.is_full_update = true;
+ brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+ std::end(kRandomString));
+ expected_data.resize(4096); // block size
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(expected_data.size());
+ aop.op.set_type(InstallOperation::REPLACE);
+ aops.push_back(aop);
+
+ brillo::Blob payload_data = GeneratePayload(expected_data, aops, false,
+ kChromeOSMajorPayloadVersion, kFullPayloadMinorVersion);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceOperationTest) {
+ brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+ std::end(kRandomString));
+ expected_data.resize(4096); // block size
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(expected_data.size());
+ aop.op.set_type(InstallOperation::REPLACE);
+ aops.push_back(aop);
+
+ brillo::Blob payload_data = GeneratePayload(expected_data, aops, false,
+ kChromeOSMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceBzOperationTest) {
+ brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+ std::end(kRandomString));
+ expected_data.resize(4096); // block size
+ brillo::Blob bz_data;
+ EXPECT_TRUE(BzipCompress(expected_data, &bz_data));
+
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(bz_data.size());
+ aop.op.set_type(InstallOperation::REPLACE_BZ);
+ aops.push_back(aop);
+
+ brillo::Blob payload_data = GeneratePayload(bz_data, aops, false,
+ kChromeOSMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+}
+
+TEST_F(DeltaPerformerTest, ReplaceXzOperationTest) {
+ brillo::Blob xz_data(std::begin(kXzCompressedData),
+ std::end(kXzCompressedData));
+ // The compressed xz data contains only a single "a", but the operation should
+ // pad the rest of the two blocks with zeros.
+ brillo::Blob expected_data = brillo::Blob(4096, 0);
+ expected_data[0] = 'a';
+
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_data_offset(0);
+ aop.op.set_data_length(xz_data.size());
+ aop.op.set_type(InstallOperation::REPLACE_XZ);
+ vector<AnnotatedOperation> aops = {aop};
+
+ brillo::Blob payload_data = GeneratePayload(xz_data, aops, false,
+ kChromeOSMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, "/dev/null"));
+}
+
+TEST_F(DeltaPerformerTest, ZeroOperationTest) {
+ brillo::Blob existing_data = brillo::Blob(4096 * 10, 'a');
+ brillo::Blob expected_data = existing_data;
+ // Blocks 4, 5 and 7 should have zeros instead of 'a' after the operation is
+ // applied.
+ std::fill(expected_data.data() + 4096 * 4, expected_data.data() + 4096 * 6,
+ 0);
+ std::fill(expected_data.data() + 4096 * 7, expected_data.data() + 4096 * 8,
+ 0);
+
+ AnnotatedOperation aop;
+ *(aop.op.add_dst_extents()) = ExtentForRange(4, 2);
+ *(aop.op.add_dst_extents()) = ExtentForRange(7, 1);
+ aop.op.set_type(InstallOperation::ZERO);
+ vector<AnnotatedOperation> aops = {aop};
+
+ brillo::Blob payload_data = GeneratePayload(brillo::Blob(), aops, false,
+ kChromeOSMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+
+ EXPECT_EQ(expected_data,
+ ApplyPayloadToData(payload_data, "/dev/null", existing_data));
+}
+
+TEST_F(DeltaPerformerTest, SourceCopyOperationTest) {
+ brillo::Blob expected_data = brillo::Blob(std::begin(kRandomString),
+ std::end(kRandomString));
+ expected_data.resize(4096); // block size
+ vector<AnnotatedOperation> aops;
+ AnnotatedOperation aop;
+ *(aop.op.add_src_extents()) = ExtentForRange(0, 1);
+ *(aop.op.add_dst_extents()) = ExtentForRange(0, 1);
+ aop.op.set_type(InstallOperation::SOURCE_COPY);
+ aops.push_back(aop);
+
+ brillo::Blob payload_data = GeneratePayload(brillo::Blob(), aops, false,
+ kChromeOSMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+ string source_path;
+ EXPECT_TRUE(utils::MakeTempFile("Source-XXXXXX",
+ &source_path, nullptr));
+ ScopedPathUnlinker path_unlinker(source_path);
+ EXPECT_TRUE(utils::WriteFile(source_path.c_str(),
+ expected_data.data(),
+ expected_data.size()));
+
+ EXPECT_EQ(expected_data, ApplyPayload(payload_data, source_path));
+}
+
+TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
+ uint64_t test[] = {1, 1, 4, 2, 0, 1};
+ COMPILE_ASSERT(arraysize(test) % 2 == 0, array_size_uneven);
+ const uint64_t block_size = 4096;
+ const uint64_t file_length = 4 * block_size - 13;
+
+ google::protobuf::RepeatedPtrField<Extent> extents;
+ for (size_t i = 0; i < arraysize(test); i += 2) {
+ *(extents.Add()) = ExtentForRange(test[i], test[i + 1]);
+ }
+
+ string expected_output = "4096:4096,16384:8192,0:4083";
+ string actual_output;
+ EXPECT_TRUE(DeltaPerformer::ExtentsToBsdiffPositionsString(extents,
+ block_size,
+ file_length,
+ &actual_output));
+ EXPECT_EQ(expected_output, actual_output);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullGoodTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(kFullPayloadMinorVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, true,
+ ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestDeltaGoodTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_old_kernel_info();
+ manifest.mutable_old_rootfs_info();
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, false,
+ ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullUnsetMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ RunManifestValidation(manifest, DeltaPerformer::kSupportedMajorPayloadVersion,
+ true, ErrorCode::kSuccess);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestDeltaUnsetMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ RunManifestValidation(manifest, DeltaPerformer::kSupportedMajorPayloadVersion,
+ false, ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullOldKernelTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_old_kernel_info();
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullOldRootfsTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ manifest.mutable_old_rootfs_info();
+ manifest.mutable_new_kernel_info();
+ manifest.mutable_new_rootfs_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kChromeOSMajorPayloadVersion, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestFullPartitionUpdateTest) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+ PartitionUpdate* partition = manifest.add_partitions();
+ partition->mutable_old_partition_info();
+ partition->mutable_new_partition_info();
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion);
+
+ RunManifestValidation(manifest, kBrilloMajorPayloadVersion, true,
+ ErrorCode::kPayloadMismatchedType);
+}
+
+TEST_F(DeltaPerformerTest, ValidateManifestBadMinorVersion) {
+ // The Manifest we are validating.
+ DeltaArchiveManifest manifest;
+
+ // Generate a bad version number.
+ manifest.set_minor_version(DeltaPerformer::kSupportedMinorPayloadVersion +
+ 10000);
+
+ RunManifestValidation(manifest, DeltaPerformer::kSupportedMajorPayloadVersion,
+ false, ErrorCode::kUnsupportedMinorPayloadVersion);
+}
+
+TEST_F(DeltaPerformerTest, BrilloMetadataSignatureSizeTest) {
+ EXPECT_TRUE(performer_.Write(kDeltaMagic, sizeof(kDeltaMagic)));
+
+ uint64_t major_version = htobe64(kBrilloMajorPayloadVersion);
+ EXPECT_TRUE(performer_.Write(&major_version, 8));
+
+ uint64_t manifest_size = rand() % 256;
+ uint64_t manifest_size_be = htobe64(manifest_size);
+ EXPECT_TRUE(performer_.Write(&manifest_size_be, 8));
+
+ uint32_t metadata_signature_size = rand() % 256;
+ uint32_t metadata_signature_size_be = htobe32(metadata_signature_size);
+ EXPECT_TRUE(performer_.Write(&metadata_signature_size_be, 4));
+
+ EXPECT_LT(performer_.Close(), 0);
+
+ EXPECT_TRUE(performer_.IsHeaderParsed());
+ EXPECT_EQ(kBrilloMajorPayloadVersion, performer_.GetMajorVersion());
+ uint64_t manifest_offset;
+ EXPECT_TRUE(performer_.GetManifestOffset(&manifest_offset));
+ EXPECT_EQ(24, manifest_offset); // 4 + 8 + 8 + 4
+ EXPECT_EQ(manifest_offset + manifest_size, performer_.GetMetadataSize());
+ EXPECT_EQ(metadata_signature_size, performer_.metadata_signature_size_);
+}
+
+TEST_F(DeltaPerformerTest, BrilloVerifyMetadataSignatureTest) {
+ brillo::Blob payload_data = GeneratePayload({}, {}, true,
+ kBrilloMajorPayloadVersion,
+ kSourceMinorPayloadVersion);
+ install_plan_.hash_checks_mandatory = true;
+ // Just set these value so that we can use ValidateMetadataSignature directly.
+ performer_.major_payload_version_ = kBrilloMajorPayloadVersion;
+ performer_.metadata_size_ = install_plan_.metadata_size;
+ uint64_t signature_length;
+ EXPECT_TRUE(PayloadSigner::SignatureBlobLength({kUnittestPrivateKeyPath},
+ &signature_length));
+ performer_.metadata_signature_size_ = signature_length;
+ performer_.set_public_key_path(kUnittestPublicKeyPath);
+ EXPECT_EQ(ErrorCode::kSuccess,
+ performer_.ValidateMetadataSignature(payload_data));
+}
+
+TEST_F(DeltaPerformerTest, BadDeltaMagicTest) {
+ EXPECT_TRUE(performer_.Write("junk", 4));
+ EXPECT_FALSE(performer_.Write("morejunk", 8));
+ EXPECT_LT(performer_.Close(), 0);
+}
+
+TEST_F(DeltaPerformerTest, WriteUpdatesPayloadState) {
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
+ DownloadProgress(4)).Times(1);
+ EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
+ DownloadProgress(8)).Times(1);
+
+ EXPECT_TRUE(performer_.Write("junk", 4));
+ EXPECT_FALSE(performer_.Write("morejunk", 8));
+ EXPECT_LT(performer_.Close(), 0);
+}
+
+TEST_F(DeltaPerformerTest, MissingMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(0, 75456, true);
+}
+
+TEST_F(DeltaPerformerTest, MissingNonMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(0, 123456, false);
+}
+
+TEST_F(DeltaPerformerTest, InvalidMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(13000, 140000, true);
+}
+
+TEST_F(DeltaPerformerTest, InvalidNonMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(40000, 50000, false);
+}
+
+TEST_F(DeltaPerformerTest, ValidMandatoryMetadataSizeTest) {
+ DoMetadataSizeTest(85376, 85376, true);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryEmptyMetadataSignatureTest) {
+ DoMetadataSignatureTest(kEmptyMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryEmptyMetadataSignatureTest) {
+ DoMetadataSignatureTest(kEmptyMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryInvalidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kInvalidMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryInvalidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kInvalidMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryValidMetadataSignature1Test) {
+ DoMetadataSignatureTest(kValidMetadataSignature, false, true);
+}
+
+TEST_F(DeltaPerformerTest, MandatoryValidMetadataSignature2Test) {
+ DoMetadataSignatureTest(kValidMetadataSignature, true, true);
+}
+
+TEST_F(DeltaPerformerTest, NonMandatoryValidMetadataSignatureTest) {
+ DoMetadataSignatureTest(kValidMetadataSignature, true, false);
+}
+
+TEST_F(DeltaPerformerTest, UsePublicKeyFromResponse) {
+ base::FilePath key_path;
+
+ // The result of the GetPublicKeyResponse() method is based on three things
+ //
+ // 1. Whether it's an official build; and
+ // 2. Whether the Public RSA key to be used is in the root filesystem; and
+ // 3. Whether the response has a public key
+ //
+ // We test all eight combinations to ensure that we only use the
+ // public key in the response if
+ //
+ // a. it's not an official build; and
+ // b. there is no key in the root filesystem.
+
+ FakeHardware* fake_hardware = fake_system_state_.fake_hardware();
+
+ string temp_dir;
+ EXPECT_TRUE(utils::MakeTempDirectory("PublicKeyFromResponseTests.XXXXXX",
+ &temp_dir));
+ string non_existing_file = temp_dir + "/non-existing";
+ string existing_file = temp_dir + "/existing";
+ EXPECT_EQ(0, System(base::StringPrintf("touch %s", existing_file.c_str())));
+
+ // Non-official build, non-existing public-key, key in response -> true
+ fake_hardware->SetIsOfficialBuild(false);
+ performer_.public_key_path_ = non_existing_file;
+ install_plan_.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+ EXPECT_TRUE(performer_.GetPublicKeyFromResponse(&key_path));
+ EXPECT_FALSE(key_path.empty());
+ EXPECT_EQ(unlink(key_path.value().c_str()), 0);
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, existing public-key, key in response -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer_.public_key_path_ = existing_file;
+ install_plan_.public_key_rsa = "VGVzdAo="; // result of 'echo "Test" | base64'
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, non-existing public-key, no key in response -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer_.public_key_path_ = non_existing_file;
+ install_plan_.public_key_rsa = "";
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, existing public-key, no key in response -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer_.public_key_path_ = existing_file;
+ install_plan_.public_key_rsa = "";
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+ // Same with official build -> false
+ fake_hardware->SetIsOfficialBuild(true);
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ // Non-official build, non-existing public-key, key in response
+ // but invalid base64 -> false
+ fake_hardware->SetIsOfficialBuild(false);
+ performer_.public_key_path_ = non_existing_file;
+ install_plan_.public_key_rsa = "not-valid-base64";
+ EXPECT_FALSE(performer_.GetPublicKeyFromResponse(&key_path));
+
+ EXPECT_TRUE(base::DeleteFile(base::FilePath(temp_dir), true));
+}
+
+TEST_F(DeltaPerformerTest, ConfVersionsMatch) {
+ // Test that the versions in update_engine.conf that is installed to the
+ // image match the supported delta versions in the update engine.
+ uint32_t minor_version;
+ brillo::KeyValueStore store;
+ EXPECT_TRUE(store.Load(base::FilePath("update_engine.conf")));
+ EXPECT_TRUE(utils::GetMinorVersion(store, &minor_version));
+ EXPECT_EQ(DeltaPerformer::kSupportedMinorPayloadVersion, minor_version);
+
+ string major_version_str;
+ uint64_t major_version;
+ EXPECT_TRUE(store.GetString("PAYLOAD_MAJOR_VERSION", &major_version_str));
+ EXPECT_TRUE(base::StringToUint64(major_version_str, &major_version));
+ EXPECT_EQ(DeltaPerformer::kSupportedMajorPayloadVersion, major_version);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/download_action.cc b/payload_consumer/download_action.cc
new file mode 100644
index 0000000..0136f49
--- /dev/null
+++ b/payload_consumer/download_action.cc
@@ -0,0 +1,320 @@
+//
+// Copyright (C) 2011 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/download_action.h"
+
+#include <errno.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/omaha_request_params.h"
+#include "update_engine/p2p_manager.h"
+#include "update_engine/payload_state_interface.h"
+
+using base::FilePath;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+DownloadAction::DownloadAction(PrefsInterface* prefs,
+ SystemState* system_state,
+ HttpFetcher* http_fetcher)
+ : prefs_(prefs),
+ system_state_(system_state),
+ http_fetcher_(http_fetcher),
+ writer_(nullptr),
+ code_(ErrorCode::kSuccess),
+ delegate_(nullptr),
+ bytes_received_(0),
+ p2p_sharing_fd_(-1),
+ p2p_visible_(true) {}
+
+DownloadAction::~DownloadAction() {}
+
+void DownloadAction::CloseP2PSharingFd(bool delete_p2p_file) {
+ if (p2p_sharing_fd_ != -1) {
+ if (close(p2p_sharing_fd_) != 0) {
+ PLOG(ERROR) << "Error closing p2p sharing fd";
+ }
+ p2p_sharing_fd_ = -1;
+ }
+
+ if (delete_p2p_file) {
+ FilePath path =
+ system_state_->p2p_manager()->FileGetPath(p2p_file_id_);
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ } else {
+ LOG(INFO) << "Deleted p2p file " << path.value();
+ }
+ }
+
+ // Don't use p2p from this point onwards.
+ p2p_file_id_.clear();
+}
+
+bool DownloadAction::SetupP2PSharingFd() {
+ P2PManager *p2p_manager = system_state_->p2p_manager();
+
+ if (!p2p_manager->FileShare(p2p_file_id_, install_plan_.payload_size)) {
+ LOG(ERROR) << "Unable to share file via p2p";
+ CloseP2PSharingFd(true); // delete p2p file
+ return false;
+ }
+
+ // File has already been created (and allocated, xattrs been
+ // populated etc.) by FileShare() so just open it for writing.
+ FilePath path = p2p_manager->FileGetPath(p2p_file_id_);
+ p2p_sharing_fd_ = open(path.value().c_str(), O_WRONLY);
+ if (p2p_sharing_fd_ == -1) {
+ PLOG(ERROR) << "Error opening file " << path.value();
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return false;
+ }
+
+ // Ensure file to share is world-readable, otherwise
+ // p2p-server and p2p-http-server can't access it.
+ //
+ // (Q: Why doesn't the file have mode 0644 already? A: Because
+ // the process-wide umask is set to 0700 in main.cc.)
+ if (fchmod(p2p_sharing_fd_, 0644) != 0) {
+ PLOG(ERROR) << "Error setting mode 0644 on " << path.value();
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return false;
+ }
+
+ // All good.
+ LOG(INFO) << "Writing payload contents to " << path.value();
+ p2p_manager->FileGetVisible(p2p_file_id_, &p2p_visible_);
+ return true;
+}
+
+void DownloadAction::WriteToP2PFile(const void* data,
+ size_t length,
+ off_t file_offset) {
+ if (p2p_sharing_fd_ == -1) {
+ if (!SetupP2PSharingFd())
+ return;
+ }
+
+ // Check that the file is at least |file_offset| bytes long - if
+ // it's not something is wrong and we must immediately delete the
+ // file to avoid propagating this problem to other peers.
+ //
+ // How can this happen? It could be that we're resuming an update
+ // after a system crash... in this case, it could be that
+ //
+ // 1. the p2p file didn't get properly synced to stable storage; or
+ // 2. the file was deleted at bootup (it's in /var/cache after all); or
+ // 3. other reasons
+ off_t p2p_size = utils::FileSize(p2p_sharing_fd_);
+ if (p2p_size < 0) {
+ PLOG(ERROR) << "Error getting file status for p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return;
+ }
+ if (p2p_size < file_offset) {
+ LOG(ERROR) << "Wanting to write to file offset " << file_offset
+ << " but existing p2p file is only " << p2p_size
+ << " bytes.";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ return;
+ }
+
+ off_t cur_file_offset = lseek(p2p_sharing_fd_, file_offset, SEEK_SET);
+ if (cur_file_offset != static_cast<off_t>(file_offset)) {
+ PLOG(ERROR) << "Error seeking to position "
+ << file_offset << " in p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ } else {
+ // OK, seeking worked, now write the data
+ ssize_t bytes_written = write(p2p_sharing_fd_, data, length);
+ if (bytes_written != static_cast<ssize_t>(length)) {
+ PLOG(ERROR) << "Error writing "
+ << length << " bytes at file offset "
+ << file_offset << " in p2p file";
+ CloseP2PSharingFd(true); // Delete p2p file.
+ }
+ }
+}
+
+void DownloadAction::PerformAction() {
+ http_fetcher_->set_delegate(this);
+
+ // Get the InstallPlan and read it
+ CHECK(HasInputObject());
+ install_plan_ = GetInputObject();
+ bytes_received_ = 0;
+
+ install_plan_.Dump();
+
+ LOG(INFO) << "Marking new slot as unbootable";
+ if (!system_state_->boot_control()->MarkSlotUnbootable(
+ install_plan_.target_slot)) {
+ LOG(WARNING) << "Unable to mark new slot "
+ << BootControlInterface::SlotName(install_plan_.target_slot)
+ << ". Proceeding with the update anyway.";
+ }
+
+ if (writer_) {
+ LOG(INFO) << "Using writer for test.";
+ } else {
+ delta_performer_.reset(new DeltaPerformer(prefs_,
+ system_state_,
+ &install_plan_));
+ writer_ = delta_performer_.get();
+ }
+ if (delegate_) {
+ delegate_->SetDownloadStatus(true); // Set to active.
+ }
+
+ if (system_state_ != nullptr) {
+ const PayloadStateInterface* payload_state = system_state_->payload_state();
+ string file_id = utils::CalculateP2PFileId(install_plan_.payload_hash,
+ install_plan_.payload_size);
+ if (payload_state->GetUsingP2PForSharing()) {
+ // If we're sharing the update, store the file_id to convey
+ // that we should write to the file.
+ p2p_file_id_ = file_id;
+ LOG(INFO) << "p2p file id: " << p2p_file_id_;
+ } else {
+ // Even if we're not sharing the update, it could be that
+ // there's a partial file from a previous attempt with the same
+ // hash. If this is the case, we NEED to clean it up otherwise
+ // we're essentially timing out other peers downloading from us
+ // (since we're never going to complete the file).
+ FilePath path = system_state_->p2p_manager()->FileGetPath(file_id);
+ if (!path.empty()) {
+ if (unlink(path.value().c_str()) != 0) {
+ PLOG(ERROR) << "Error deleting p2p file " << path.value();
+ } else {
+ LOG(INFO) << "Deleting partial p2p file " << path.value()
+ << " since we're not using p2p to share.";
+ }
+ }
+ }
+
+ // Tweak timeouts on the HTTP fetcher if we're downloading from a
+ // local peer.
+ if (payload_state->GetUsingP2PForDownloading() &&
+ payload_state->GetP2PUrl() == install_plan_.download_url) {
+ LOG(INFO) << "Tweaking HTTP fetcher since we're downloading via p2p";
+ http_fetcher_->set_low_speed_limit(kDownloadP2PLowSpeedLimitBps,
+ kDownloadP2PLowSpeedTimeSeconds);
+ http_fetcher_->set_max_retry_count(kDownloadP2PMaxRetryCount);
+ http_fetcher_->set_connect_timeout(kDownloadP2PConnectTimeoutSeconds);
+ }
+ }
+
+ http_fetcher_->BeginTransfer(install_plan_.download_url);
+}
+
+void DownloadAction::TerminateProcessing() {
+ if (writer_) {
+ writer_->Close();
+ writer_ = nullptr;
+ }
+ if (delegate_) {
+ delegate_->SetDownloadStatus(false); // Set to inactive.
+ }
+ CloseP2PSharingFd(false); // Keep p2p file.
+ // Terminates the transfer. The action is terminated, if necessary, when the
+ // TransferTerminated callback is received.
+ http_fetcher_->TerminateTransfer();
+}
+
+void DownloadAction::SeekToOffset(off_t offset) {
+ bytes_received_ = offset;
+}
+
+void DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes,
+ size_t length) {
+ // Note that bytes_received_ is the current offset.
+ if (!p2p_file_id_.empty()) {
+ WriteToP2PFile(bytes, length, bytes_received_);
+ }
+
+ bytes_received_ += length;
+ if (delegate_)
+ delegate_->BytesReceived(bytes_received_, install_plan_.payload_size);
+ if (writer_ && !writer_->Write(bytes, length, &code_)) {
+ LOG(ERROR) << "Error " << code_ << " in DeltaPerformer's Write method when "
+ << "processing the received payload -- Terminating processing";
+ // Delete p2p file, if applicable.
+ if (!p2p_file_id_.empty())
+ CloseP2PSharingFd(true);
+ // Don't tell the action processor that the action is complete until we get
+ // the TransferTerminated callback. Otherwise, this and the HTTP fetcher
+ // objects may get destroyed before all callbacks are complete.
+ TerminateProcessing();
+ return;
+ }
+
+ // Call p2p_manager_->FileMakeVisible() when we've successfully
+ // verified the manifest!
+ if (!p2p_visible_ &&
+ delta_performer_.get() && delta_performer_->IsManifestValid()) {
+ LOG(INFO) << "Manifest has been validated. Making p2p file visible.";
+ system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
+ p2p_visible_ = true;
+ }
+}
+
+void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
+ if (writer_) {
+ LOG_IF(WARNING, writer_->Close() != 0) << "Error closing the writer.";
+ writer_ = nullptr;
+ }
+ if (delegate_) {
+ delegate_->SetDownloadStatus(false); // Set to inactive.
+ }
+ ErrorCode code =
+ successful ? ErrorCode::kSuccess : ErrorCode::kDownloadTransferError;
+ if (code == ErrorCode::kSuccess && delta_performer_.get()) {
+ code = delta_performer_->VerifyPayload(install_plan_.payload_hash,
+ install_plan_.payload_size);
+ if (code != ErrorCode::kSuccess) {
+ LOG(ERROR) << "Download of " << install_plan_.download_url
+ << " failed due to payload verification error.";
+ // Delete p2p file, if applicable.
+ if (!p2p_file_id_.empty())
+ CloseP2PSharingFd(true);
+ }
+ }
+
+ // Write the path to the output pipe if we're successful.
+ if (code == ErrorCode::kSuccess && HasOutputPipe())
+ SetOutputObject(install_plan_);
+ processor_->ActionComplete(this, code);
+}
+
+void DownloadAction::TransferTerminated(HttpFetcher *fetcher) {
+ if (code_ != ErrorCode::kSuccess) {
+ processor_->ActionComplete(this, code_);
+ }
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/download_action.h b/payload_consumer/download_action.h
new file mode 100644
index 0000000..c0f0688
--- /dev/null
+++ b/payload_consumer/download_action.h
@@ -0,0 +1,166 @@
+//
+// Copyright (C) 2011 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <memory>
+#include <string>
+
+#include <curl/curl.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/http_fetcher.h"
+#include "update_engine/payload_consumer/delta_performer.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/system_state.h"
+
+// The Download Action downloads a specified url to disk. The url should point
+// to an update in a delta payload format. The payload will be piped into a
+// DeltaPerformer that will apply the delta to the disk.
+
+namespace chromeos_update_engine {
+
+class DownloadActionDelegate {
+ public:
+ virtual ~DownloadActionDelegate() = default;
+
+ // Called right before starting the download with |active| set to
+ // true. Called after completing the download with |active| set to
+ // false.
+ virtual void SetDownloadStatus(bool active) = 0;
+
+ // Called periodically after bytes are received. This method will be
+ // invoked only if the download is active. |bytes_received| is the
+ // number of bytes downloaded thus far. |total| is the number of
+ // bytes expected.
+ virtual void BytesReceived(uint64_t bytes_received, uint64_t total) = 0;
+};
+
+class PrefsInterface;
+
+class DownloadAction : public InstallPlanAction,
+ public HttpFetcherDelegate {
+ public:
+ // Takes ownership of the passed in HttpFetcher. Useful for testing.
+ // A good calling pattern is:
+ // DownloadAction(prefs, system_state, new WhateverHttpFetcher);
+ DownloadAction(PrefsInterface* prefs,
+ SystemState* system_state,
+ HttpFetcher* http_fetcher);
+ ~DownloadAction() override;
+ void PerformAction() override;
+ void TerminateProcessing() override;
+
+ // Testing
+ void SetTestFileWriter(FileWriter* writer) {
+ writer_ = writer;
+ }
+
+ int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "DownloadAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ // HttpFetcherDelegate methods (see http_fetcher.h)
+ void ReceivedBytes(HttpFetcher* fetcher,
+ const void* bytes, size_t length) override;
+ void SeekToOffset(off_t offset) override;
+ void TransferComplete(HttpFetcher* fetcher, bool successful) override;
+ void TransferTerminated(HttpFetcher* fetcher) override;
+
+ DownloadActionDelegate* delegate() const { return delegate_; }
+ void set_delegate(DownloadActionDelegate* delegate) {
+ delegate_ = delegate;
+ }
+
+ HttpFetcher* http_fetcher() { return http_fetcher_.get(); }
+
+ // Returns the p2p file id for the file being written or the empty
+ // string if we're not writing to a p2p file.
+ std::string p2p_file_id() { return p2p_file_id_; }
+
+ private:
+ // Closes the file descriptor for the p2p file being written and
+ // clears |p2p_file_id_| to indicate that we're no longer sharing
+ // the file. If |delete_p2p_file| is True, also deletes the file.
+ // If there is no p2p file descriptor, this method does nothing.
+ void CloseP2PSharingFd(bool delete_p2p_file);
+
+ // Starts sharing the p2p file. Must be called before
+ // WriteToP2PFile(). Returns True if this worked.
+ bool SetupP2PSharingFd();
+
+ // Writes |length| bytes of payload from |data| into |file_offset|
+ // of the p2p file. Also does sanity checks; for example ensures we
+ // don't end up with a file with holes in it.
+ //
+ // This method does nothing if SetupP2PSharingFd() hasn't been
+ // called or if CloseP2PSharingFd() has been called.
+ void WriteToP2PFile(const void* data, size_t length, off_t file_offset);
+
+ // The InstallPlan passed in
+ InstallPlan install_plan_;
+
+ // Update Engine preference store.
+ PrefsInterface* prefs_;
+
+ // Global context for the system.
+ SystemState* system_state_;
+
+ // Pointer to the HttpFetcher that does the http work.
+ std::unique_ptr<HttpFetcher> http_fetcher_;
+
+ // The FileWriter that downloaded data should be written to. It will
+ // either point to *decompressing_file_writer_ or *delta_performer_.
+ FileWriter* writer_;
+
+ std::unique_ptr<DeltaPerformer> delta_performer_;
+
+ // Used by TransferTerminated to figure if this action terminated itself or
+ // was terminated by the action processor.
+ ErrorCode code_;
+
+ // For reporting status to outsiders
+ DownloadActionDelegate* delegate_;
+ uint64_t bytes_received_;
+
+ // The file-id for the file we're sharing or the empty string
+ // if we're not using p2p to share.
+ std::string p2p_file_id_;
+
+ // The file descriptor for the p2p file used for caching the payload or -1
+ // if we're not using p2p to share.
+ int p2p_sharing_fd_;
+
+ // Set to |false| if p2p file is not visible.
+ bool p2p_visible_;
+
+ DISALLOW_COPY_AND_ASSIGN(DownloadAction);
+};
+
+// We want to be sure that we're compiled with large file support on linux,
+// just in case we find ourselves downloading large images.
+COMPILE_ASSERT(8 == sizeof(off_t), off_t_not_64_bit);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_DOWNLOAD_ACTION_H_
diff --git a/payload_consumer/download_action_unittest.cc b/payload_consumer/download_action_unittest.cc
new file mode 100644
index 0000000..3bef196
--- /dev/null
+++ b/payload_consumer/download_action_unittest.cc
@@ -0,0 +1,634 @@
+//
+// Copyright (C) 2011 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/download_action.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/location.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop.h>
+
+#include "update_engine/common/action_pipe.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/mock_prefs.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_p2p_manager_configuration.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/update_manager/fake_update_manager.h"
+
+namespace chromeos_update_engine {
+
+using base::FilePath;
+using base::ReadFileToString;
+using base::WriteFile;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+using test_utils::ScopedTempFile;
+using testing::AtLeast;
+using testing::InSequence;
+using testing::Return;
+using testing::_;
+
+class DownloadActionTest : public ::testing::Test { };
+
+namespace {
+class DownloadActionDelegateMock : public DownloadActionDelegate {
+ public:
+ MOCK_METHOD1(SetDownloadStatus, void(bool active));
+ MOCK_METHOD2(BytesReceived, void(uint64_t bytes_received, uint64_t total));
+};
+
+class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code)
+ : processing_done_called_(false),
+ expected_code_(expected_code) {}
+ ~DownloadActionTestProcessorDelegate() override {
+ EXPECT_TRUE(processing_done_called_);
+ }
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) override {
+ brillo::MessageLoop::current()->BreakLoop();
+ brillo::Blob found_data;
+ ASSERT_TRUE(utils::ReadFile(path_, &found_data));
+ if (expected_code_ != ErrorCode::kDownloadWriteError) {
+ ASSERT_EQ(expected_data_.size(), found_data.size());
+ for (unsigned i = 0; i < expected_data_.size(); i++) {
+ EXPECT_EQ(expected_data_[i], found_data[i]);
+ }
+ }
+ processing_done_called_ = true;
+ }
+
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) override {
+ const string type = action->Type();
+ if (type == DownloadAction::StaticType()) {
+ EXPECT_EQ(expected_code_, code);
+ } else {
+ EXPECT_EQ(ErrorCode::kSuccess, code);
+ }
+ }
+
+ string path_;
+ brillo::Blob expected_data_;
+ bool processing_done_called_;
+ ErrorCode expected_code_;
+};
+
+class TestDirectFileWriter : public DirectFileWriter {
+ public:
+ TestDirectFileWriter() : fail_write_(0), current_write_(0) {}
+ void set_fail_write(int fail_write) { fail_write_ = fail_write; }
+
+ virtual bool Write(const void* bytes, size_t count) {
+ if (++current_write_ == fail_write_) {
+ return false;
+ }
+ return DirectFileWriter::Write(bytes, count);
+ }
+
+ private:
+ // If positive, fail on the |fail_write_| call to Write.
+ int fail_write_;
+ int current_write_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+ MockHttpFetcher* http_fetcher) {
+ processor->StartProcessing();
+ http_fetcher->SetOffset(1);
+}
+
+void TestWithData(const brillo::Blob& data,
+ int fail_write,
+ bool use_download_delegate) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+ FakeSystemState fake_system_state;
+
+ // TODO(adlr): see if we need a different file for build bots
+ ScopedTempFile output_temp_file;
+ TestDirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open(output_temp_file.GetPath().c_str(),
+ O_WRONLY | O_CREAT,
+ 0));
+ writer.set_fail_write(fail_write);
+
+ // We pull off the first byte from data and seek past it.
+ string hash = HashCalculator::HashOfBytes(&data[1], data.size() - 1);
+ uint64_t size = data.size();
+ InstallPlan install_plan(false,
+ false,
+ "",
+ size,
+ hash,
+ 0,
+ "",
+ "");
+ install_plan.source_slot = 0;
+ install_plan.target_slot = 1;
+ // We mark both slots as bootable. Only the target slot should be unbootable
+ // after the download starts.
+ fake_system_state.fake_boot_control()->SetSlotBootable(
+ install_plan.source_slot, true);
+ fake_system_state.fake_boot_control()->SetSlotBootable(
+ install_plan.target_slot, true);
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(),
+ data.size(),
+ nullptr);
+ // takes ownership of passed in HttpFetcher
+ DownloadAction download_action(&prefs, &fake_system_state, http_fetcher);
+ download_action.SetTestFileWriter(&writer);
+ BondActions(&feeder_action, &download_action);
+ DownloadActionDelegateMock download_delegate;
+ if (use_download_delegate) {
+ InSequence s;
+ download_action.set_delegate(&download_delegate);
+ EXPECT_CALL(download_delegate, SetDownloadStatus(true)).Times(1);
+ if (data.size() > kMockHttpFetcherChunkSize)
+ EXPECT_CALL(download_delegate,
+ BytesReceived(1 + kMockHttpFetcherChunkSize, _));
+ EXPECT_CALL(download_delegate, BytesReceived(_, _)).Times(AtLeast(1));
+ EXPECT_CALL(download_delegate, SetDownloadStatus(false)).Times(1);
+ }
+ ErrorCode expected_code = ErrorCode::kSuccess;
+ if (fail_write > 0)
+ expected_code = ErrorCode::kDownloadWriteError;
+ DownloadActionTestProcessorDelegate delegate(expected_code);
+ delegate.expected_data_ = brillo::Blob(data.begin() + 1, data.end());
+ delegate.path_ = output_temp_file.GetPath();
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+
+ EXPECT_TRUE(fake_system_state.fake_boot_control()->IsSlotBootable(
+ install_plan.source_slot));
+ EXPECT_FALSE(fake_system_state.fake_boot_control()->IsSlotBootable(
+ install_plan.target_slot));
+}
+} // namespace
+
+TEST(DownloadActionTest, SimpleTest) {
+ brillo::Blob small;
+ const char* foo = "foo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small,
+ 0, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, LargeTest) {
+ brillo::Blob big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ c = ('9' == c) ? '0' : c + 1;
+ }
+ TestWithData(big,
+ 0, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, FailWriteTest) {
+ brillo::Blob big(5 * kMockHttpFetcherChunkSize);
+ char c = '0';
+ for (unsigned int i = 0; i < big.size(); i++) {
+ big[i] = c;
+ c = ('9' == c) ? '0' : c + 1;
+ }
+ TestWithData(big,
+ 2, // fail_write
+ true); // use_download_delegate
+}
+
+TEST(DownloadActionTest, NoDownloadDelegateTest) {
+ brillo::Blob small;
+ const char* foo = "foofoo";
+ small.insert(small.end(), foo, foo + strlen(foo));
+ TestWithData(small,
+ 0, // fail_write
+ false); // use_download_delegate
+}
+
+namespace {
+class TerminateEarlyTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingStopped(const ActionProcessor* processor) {
+ brillo::MessageLoop::current()->BreakLoop();
+ }
+};
+
+void TerminateEarlyTestStarter(ActionProcessor* processor) {
+ processor->StartProcessing();
+ CHECK(processor->IsRunning());
+ processor->StopProcessing();
+}
+
+void TestTerminateEarly(bool use_download_delegate) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ brillo::Blob data(kMockHttpFetcherChunkSize +
+ kMockHttpFetcherChunkSize / 2);
+ memset(data.data(), 0, data.size());
+
+ ScopedTempFile temp_file;
+ {
+ DirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open(temp_file.GetPath().c_str(),
+ O_WRONLY | O_CREAT,
+ 0));
+
+ // takes ownership of passed in HttpFetcher
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan(false, false, "", 0, "", 0, "", "");
+ feeder_action.set_obj(install_plan);
+ FakeSystemState fake_system_state_;
+ MockPrefs prefs;
+ DownloadAction download_action(&prefs, &fake_system_state_,
+ new MockHttpFetcher(data.data(),
+ data.size(),
+ nullptr));
+ download_action.SetTestFileWriter(&writer);
+ DownloadActionDelegateMock download_delegate;
+ if (use_download_delegate) {
+ InSequence s;
+ download_action.set_delegate(&download_delegate);
+ EXPECT_CALL(download_delegate, SetDownloadStatus(true)).Times(1);
+ EXPECT_CALL(download_delegate, SetDownloadStatus(false)).Times(1);
+ }
+ TerminateEarlyTestProcessorDelegate delegate;
+ ActionProcessor processor;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ BondActions(&feeder_action, &download_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind(&TerminateEarlyTestStarter, &processor));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+ }
+
+ // 1 or 0 chunks should have come through
+ const off_t resulting_file_size(utils::FileSize(temp_file.GetPath()));
+ EXPECT_GE(resulting_file_size, 0);
+ if (resulting_file_size != 0)
+ EXPECT_EQ(kMockHttpFetcherChunkSize, resulting_file_size);
+}
+
+} // namespace
+
+TEST(DownloadActionTest, TerminateEarlyTest) {
+ TestTerminateEarly(true);
+}
+
+TEST(DownloadActionTest, TerminateEarlyNoDownloadDelegateTest) {
+ TestTerminateEarly(false);
+}
+
+class DownloadActionTestAction;
+
+template<>
+class ActionTraits<DownloadActionTestAction> {
+ public:
+ typedef InstallPlan OutputObjectType;
+ typedef InstallPlan InputObjectType;
+};
+
+// This is a simple Action class for testing.
+class DownloadActionTestAction : public Action<DownloadActionTestAction> {
+ public:
+ DownloadActionTestAction() : did_run_(false) {}
+ typedef InstallPlan InputObjectType;
+ typedef InstallPlan OutputObjectType;
+ ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
+ ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
+ ActionProcessor* processor() { return processor_; }
+ void PerformAction() {
+ did_run_ = true;
+ ASSERT_TRUE(HasInputObject());
+ EXPECT_TRUE(expected_input_object_ == GetInputObject());
+ ASSERT_TRUE(processor());
+ processor()->ActionComplete(this, ErrorCode::kSuccess);
+ }
+ string Type() const { return "DownloadActionTestAction"; }
+ InstallPlan expected_input_object_;
+ bool did_run_;
+};
+
+namespace {
+// This class is an ActionProcessorDelegate that simply terminates the
+// run loop when the ActionProcessor has completed processing. It's used
+// only by the test PassObjectOutTest.
+class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+ brillo::MessageLoop::current()->BreakLoop();
+ }
+};
+
+} // namespace
+
+TEST(DownloadActionTest, PassObjectOutTest) {
+ brillo::FakeMessageLoop loop(nullptr);
+ loop.SetAsCurrent();
+
+ DirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open("/dev/null", O_WRONLY | O_CREAT, 0));
+
+ // takes ownership of passed in HttpFetcher
+ InstallPlan install_plan(false,
+ false,
+ "",
+ 1,
+ HashCalculator::HashOfString("x"),
+ 0,
+ "",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ FakeSystemState fake_system_state_;
+ DownloadAction download_action(&prefs, &fake_system_state_,
+ new MockHttpFetcher("x", 1, nullptr));
+ download_action.SetTestFileWriter(&writer);
+
+ DownloadActionTestAction test_action;
+ test_action.expected_input_object_ = install_plan;
+ BondActions(&feeder_action, &download_action);
+ BondActions(&download_action, &test_action);
+
+ ActionProcessor processor;
+ PassObjectOutTestProcessorDelegate delegate;
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&download_action);
+ processor.EnqueueAction(&test_action);
+
+ loop.PostTask(FROM_HERE,
+ base::Bind([&processor] { processor.StartProcessing(); }));
+ loop.Run();
+ EXPECT_FALSE(loop.PendingTasks());
+
+ EXPECT_EQ(true, test_action.did_run_);
+}
+
+// Test fixture for P2P tests.
+class P2PDownloadActionTest : public testing::Test {
+ protected:
+ P2PDownloadActionTest()
+ : start_at_offset_(0),
+ fake_um_(fake_system_state_.fake_clock()) {}
+
+ ~P2PDownloadActionTest() override {}
+
+ // Derived from testing::Test.
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ // Derived from testing::Test.
+ void TearDown() override {
+ EXPECT_FALSE(loop_.PendingTasks());
+ }
+
+ // To be called by tests to setup the download. The
+ // |starting_offset| parameter is for where to resume.
+ void SetupDownload(off_t starting_offset) {
+ start_at_offset_ = starting_offset;
+ // Prepare data 10 kB of data.
+ data_.clear();
+ for (unsigned int i = 0; i < 10 * 1000; i++)
+ data_ += 'a' + (i % 25);
+
+ // Setup p2p.
+ FakeP2PManagerConfiguration *test_conf = new FakeP2PManagerConfiguration();
+ p2p_manager_.reset(P2PManager::Construct(
+ test_conf, nullptr, &fake_um_, "cros_au", 3,
+ base::TimeDelta::FromDays(5)));
+ fake_system_state_.set_p2p_manager(p2p_manager_.get());
+ }
+
+ // To be called by tests to perform the download. The
+ // |use_p2p_to_share| parameter is used to indicate whether the
+ // payload should be shared via p2p.
+ void StartDownload(bool use_p2p_to_share) {
+ EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+ GetUsingP2PForSharing())
+ .WillRepeatedly(Return(use_p2p_to_share));
+
+ ScopedTempFile output_temp_file;
+ TestDirectFileWriter writer;
+ EXPECT_EQ(0, writer.Open(output_temp_file.GetPath().c_str(),
+ O_WRONLY | O_CREAT,
+ 0));
+ InstallPlan install_plan(false,
+ false,
+ "",
+ data_.length(),
+ "1234hash",
+ 0,
+ "",
+ "");
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+ MockPrefs prefs;
+ http_fetcher_ = new MockHttpFetcher(data_.c_str(),
+ data_.length(),
+ nullptr);
+ // Note that DownloadAction takes ownership of the passed in HttpFetcher.
+ download_action_.reset(new DownloadAction(&prefs, &fake_system_state_,
+ http_fetcher_));
+ download_action_->SetTestFileWriter(&writer);
+ BondActions(&feeder_action, download_action_.get());
+ DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess);
+ delegate.expected_data_ = brillo::Blob(data_.begin() + start_at_offset_,
+ data_.end());
+ delegate.path_ = output_temp_file.GetPath();
+ processor_.set_delegate(&delegate);
+ processor_.EnqueueAction(&feeder_action);
+ processor_.EnqueueAction(download_action_.get());
+
+ loop_.PostTask(FROM_HERE, base::Bind(
+ &P2PDownloadActionTest::StartProcessorInRunLoopForP2P,
+ base::Unretained(this)));
+ loop_.Run();
+ }
+
+ // Mainloop used to make StartDownload() synchronous.
+ brillo::FakeMessageLoop loop_{nullptr};
+
+ // The DownloadAction instance under test.
+ unique_ptr<DownloadAction> download_action_;
+
+ // The HttpFetcher used in the test.
+ MockHttpFetcher* http_fetcher_;
+
+ // The P2PManager used in the test.
+ unique_ptr<P2PManager> p2p_manager_;
+
+ // The ActionProcessor used for running the actions.
+ ActionProcessor processor_;
+
+ // A fake system state.
+ FakeSystemState fake_system_state_;
+
+ // The data being downloaded.
+ string data_;
+
+ private:
+ // Callback used in StartDownload() method.
+ void StartProcessorInRunLoopForP2P() {
+ processor_.StartProcessing();
+ http_fetcher_->SetOffset(start_at_offset_);
+ }
+
+ // The requested starting offset passed to SetupDownload().
+ off_t start_at_offset_;
+
+ chromeos_update_manager::FakeUpdateManager fake_um_;
+};
+
+TEST_F(P2PDownloadActionTest, IsWrittenTo) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(0); // starting_offset
+ StartDownload(true); // use_p2p_to_share
+
+ // Check the p2p file and its content matches what was sent.
+ string file_id = download_action_->p2p_file_id();
+ EXPECT_NE("", file_id);
+ EXPECT_EQ(data_.length(), p2p_manager_->FileGetSize(file_id));
+ EXPECT_EQ(data_.length(), p2p_manager_->FileGetExpectedSize(file_id));
+ string p2p_file_contents;
+ EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+ &p2p_file_contents));
+ EXPECT_EQ(data_, p2p_file_contents);
+}
+
+TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+ StartDownload(true); // use_p2p_to_share
+
+ // DownloadAction should convey that the file is not being shared.
+ // and that we don't have any p2p files.
+ EXPECT_EQ(download_action_->p2p_file_id(), "");
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+TEST_F(P2PDownloadActionTest, CanAppend) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+
+ // Prepare the file with existing data before starting to write to
+ // it via DownloadAction.
+ string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+ ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+ string existing_data;
+ for (unsigned int i = 0; i < 1000; i++)
+ existing_data += '0' + (i % 10);
+ ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+ 1000), 1000);
+
+ StartDownload(true); // use_p2p_to_share
+
+ // DownloadAction should convey the same file_id and the file should
+ // have the expected size.
+ EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), data_.length());
+ EXPECT_EQ(p2p_manager_->FileGetExpectedSize(file_id), data_.length());
+ string p2p_file_contents;
+ // Check that the first 1000 bytes wasn't touched and that we
+ // appended the remaining as appropriate.
+ EXPECT_TRUE(ReadFileToString(p2p_manager_->FileGetPath(file_id),
+ &p2p_file_contents));
+ EXPECT_EQ(existing_data, p2p_file_contents.substr(0, 1000));
+ EXPECT_EQ(data_.substr(1000), p2p_file_contents.substr(1000));
+}
+
+TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
+ if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
+ LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
+ << "Please update your system to support this feature.";
+ return;
+ }
+
+ SetupDownload(1000); // starting_offset
+
+ // Prepare the file with all existing data before starting to write
+ // to it via DownloadAction.
+ string file_id = utils::CalculateP2PFileId("1234hash", data_.length());
+ ASSERT_TRUE(p2p_manager_->FileShare(file_id, data_.length()));
+ string existing_data;
+ for (unsigned int i = 0; i < 1000; i++)
+ existing_data += '0' + (i % 10);
+ ASSERT_EQ(WriteFile(p2p_manager_->FileGetPath(file_id), existing_data.c_str(),
+ 1000), 1000);
+
+ // Check that the file is there.
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), 1000);
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 1);
+
+ StartDownload(false); // use_p2p_to_share
+
+ // DownloadAction should have deleted the p2p file. Check that it's gone.
+ EXPECT_EQ(p2p_manager_->FileGetSize(file_id), -1);
+ EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/extent_writer.cc b/payload_consumer/extent_writer.cc
new file mode 100644
index 0000000..5501e22
--- /dev/null
+++ b/payload_consumer/extent_writer.cc
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2009 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/extent_writer.h"
+
+#include <errno.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::min;
+
+namespace chromeos_update_engine {
+
+bool DirectExtentWriter::Write(const void* bytes, size_t count) {
+ if (count == 0)
+ return true;
+ const char* c_bytes = reinterpret_cast<const char*>(bytes);
+ size_t bytes_written = 0;
+ while (count - bytes_written > 0) {
+ TEST_AND_RETURN_FALSE(next_extent_index_ < extents_.size());
+ uint64_t bytes_remaining_next_extent =
+ extents_[next_extent_index_].num_blocks() * block_size_ -
+ extent_bytes_written_;
+ CHECK_NE(bytes_remaining_next_extent, static_cast<uint64_t>(0));
+ size_t bytes_to_write =
+ static_cast<size_t>(min(static_cast<uint64_t>(count - bytes_written),
+ bytes_remaining_next_extent));
+ TEST_AND_RETURN_FALSE(bytes_to_write > 0);
+
+ if (extents_[next_extent_index_].start_block() != kSparseHole) {
+ const off64_t offset =
+ extents_[next_extent_index_].start_block() * block_size_ +
+ extent_bytes_written_;
+ TEST_AND_RETURN_FALSE_ERRNO(fd_->Seek(offset, SEEK_SET) !=
+ static_cast<off64_t>(-1));
+ TEST_AND_RETURN_FALSE(
+ utils::WriteAll(fd_, c_bytes + bytes_written, bytes_to_write));
+ }
+ bytes_written += bytes_to_write;
+ extent_bytes_written_ += bytes_to_write;
+ if (bytes_remaining_next_extent == bytes_to_write) {
+ // We filled this extent
+ CHECK_EQ(extent_bytes_written_,
+ extents_[next_extent_index_].num_blocks() * block_size_);
+ // move to next extent
+ extent_bytes_written_ = 0;
+ next_extent_index_++;
+ }
+ }
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/extent_writer.h b/payload_consumer/extent_writer.h
new file mode 100644
index 0000000..6484ebf
--- /dev/null
+++ b/payload_consumer/extent_writer.h
@@ -0,0 +1,134 @@
+//
+// Copyright (C) 2009 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
+
+#include <vector>
+
+#include <base/logging.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/file_descriptor.h"
+#include "update_engine/update_metadata.pb.h"
+
+// ExtentWriter is an abstract class which synchronously writes to a given
+// file descriptor at the extents given.
+
+namespace chromeos_update_engine {
+
+class ExtentWriter {
+ public:
+ ExtentWriter() = default;
+ virtual ~ExtentWriter() {
+ LOG_IF(ERROR, !end_called_) << "End() not called on ExtentWriter.";
+ }
+
+ // Returns true on success.
+ virtual bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) = 0;
+
+ // Returns true on success.
+ virtual bool Write(const void* bytes, size_t count) = 0;
+
+ // Should be called when all writing is complete. Returns true on success.
+ // The fd is not closed. Caller is responsible for closing it.
+ bool End() {
+ end_called_ = true;
+ return EndImpl();
+ }
+ virtual bool EndImpl() = 0;
+ private:
+ bool end_called_{false};
+};
+
+// DirectExtentWriter is probably the simplest ExtentWriter implementation.
+// It writes the data directly into the extents.
+
+class DirectExtentWriter : public ExtentWriter {
+ public:
+ DirectExtentWriter() = default;
+ ~DirectExtentWriter() override = default;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override {
+ fd_ = fd;
+ block_size_ = block_size;
+ extents_ = extents;
+ return true;
+ }
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override { return true; }
+
+ private:
+ FileDescriptorPtr fd_{nullptr};
+
+ size_t block_size_{0};
+ // Bytes written into next_extent_index_ thus far
+ uint64_t extent_bytes_written_{0};
+ std::vector<Extent> extents_;
+ // The next call to write should correspond to extents_[next_extent_index_]
+ std::vector<Extent>::size_type next_extent_index_{0};
+};
+
+// Takes an underlying ExtentWriter to which all operations are delegated.
+// When End() is called, ZeroPadExtentWriter ensures that the total number
+// of bytes written is a multiple of block_size_. If not, it writes zeros
+// to pad as needed.
+
+class ZeroPadExtentWriter : public ExtentWriter {
+ public:
+ explicit ZeroPadExtentWriter(
+ std::unique_ptr<ExtentWriter> underlying_extent_writer)
+ : underlying_extent_writer_(std::move(underlying_extent_writer)) {}
+ ~ZeroPadExtentWriter() override = default;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override {
+ block_size_ = block_size;
+ return underlying_extent_writer_->Init(fd, extents, block_size);
+ }
+ bool Write(const void* bytes, size_t count) override {
+ if (underlying_extent_writer_->Write(bytes, count)) {
+ bytes_written_mod_block_size_ += count;
+ bytes_written_mod_block_size_ %= block_size_;
+ return true;
+ }
+ return false;
+ }
+ bool EndImpl() override {
+ if (bytes_written_mod_block_size_) {
+ const size_t write_size = block_size_ - bytes_written_mod_block_size_;
+ brillo::Blob zeros(write_size, 0);
+ TEST_AND_RETURN_FALSE(underlying_extent_writer_->Write(zeros.data(),
+ write_size));
+ }
+ return underlying_extent_writer_->End();
+ }
+
+ private:
+ std::unique_ptr<ExtentWriter> underlying_extent_writer_;
+ size_t block_size_{0};
+ size_t bytes_written_mod_block_size_{0};
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_EXTENT_WRITER_H_
diff --git a/payload_consumer/extent_writer_unittest.cc b/payload_consumer/extent_writer_unittest.cc
new file mode 100644
index 0000000..24ea5bf
--- /dev/null
+++ b/payload_consumer/extent_writer_unittest.cc
@@ -0,0 +1,270 @@
+//
+// Copyright (C) 2009 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/extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using chromeos_update_engine::test_utils::ExpectVectorsEq;
+using std::min;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+COMPILE_ASSERT(sizeof(off_t) == 8, off_t_not_64_bit);
+
+namespace {
+const char kPathTemplate[] = "./ExtentWriterTest-file.XXXXXX";
+const size_t kBlockSize = 4096;
+}
+
+class ExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ memcpy(path_, kPathTemplate, sizeof(kPathTemplate));
+ fd_.reset(new EintrSafeFileDescriptor);
+ int fd = mkstemp(path_);
+ ASSERT_TRUE(fd_->Open(path_, O_RDWR, 0600));
+ close(fd);
+ }
+ void TearDown() override {
+ fd_->Close();
+ unlink(path_);
+ }
+
+ // Writes data to an extent writer in 'chunk_size' chunks with
+ // the first chunk of size first_chunk_size. It calculates what the
+ // resultant file should look like and ensure that the extent writer
+ // wrote the file correctly.
+ void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
+ void TestZeroPad(bool aligned_size);
+
+ FileDescriptorPtr fd_;
+ char path_[sizeof(kPathTemplate)];
+};
+
+TEST_F(ExtentWriterTest, SimpleTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ const string bytes = "1234";
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(direct_writer.Write(bytes.data(), bytes.size()));
+ EXPECT_TRUE(direct_writer.End());
+
+ EXPECT_EQ(kBlockSize + bytes.size(), utils::FileSize(path_));
+
+ brillo::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ brillo::Blob expected_file(kBlockSize);
+ expected_file.insert(expected_file.end(),
+ bytes.data(), bytes.data() + bytes.size());
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroLengthTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+ EXPECT_TRUE(direct_writer.Write(nullptr, 0));
+ EXPECT_TRUE(direct_writer.End());
+}
+
+TEST_F(ExtentWriterTest, OverflowExtentTest) {
+ WriteAlignedExtents(kBlockSize * 3, kBlockSize * 3);
+}
+
+TEST_F(ExtentWriterTest, UnalignedWriteTest) {
+ WriteAlignedExtents(7, 7);
+}
+
+TEST_F(ExtentWriterTest, LargeUnalignedWriteTest) {
+ WriteAlignedExtents(kBlockSize * 2, kBlockSize / 2);
+}
+
+void ExtentWriterTest::WriteAlignedExtents(size_t chunk_size,
+ size_t first_chunk_size) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(2);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ brillo::Blob data(kBlockSize * 3);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+ size_t bytes_written = 0;
+ while (bytes_written < data.size()) {
+ size_t bytes_to_write = min(data.size() - bytes_written, chunk_size);
+ if (bytes_written == 0) {
+ bytes_to_write = min(data.size() - bytes_written, first_chunk_size);
+ }
+ EXPECT_TRUE(direct_writer.Write(&data[bytes_written], bytes_to_write));
+ bytes_written += bytes_to_write;
+ }
+ EXPECT_TRUE(direct_writer.End());
+
+ EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+ brillo::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ brillo::Blob expected_file;
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize,
+ data.begin() + kBlockSize * 2);
+ expected_file.insert(expected_file.end(),
+ data.begin(), data.begin() + kBlockSize);
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize * 2, data.end());
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadNullTest) {
+ TestZeroPad(true);
+}
+
+TEST_F(ExtentWriterTest, ZeroPadFillTest) {
+ TestZeroPad(false);
+}
+
+void ExtentWriterTest::TestZeroPad(bool aligned_size) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+
+ brillo::Blob data(kBlockSize * 2);
+ test_utils::FillWithData(&data);
+
+ ZeroPadExtentWriter zero_pad_writer(
+ brillo::make_unique_ptr(new DirectExtentWriter()));
+
+ EXPECT_TRUE(zero_pad_writer.Init(fd_, extents, kBlockSize));
+ size_t bytes_to_write = data.size();
+ const size_t missing_bytes = (aligned_size ? 0 : 9);
+ bytes_to_write -= missing_bytes;
+ fd_->Seek(kBlockSize - missing_bytes, SEEK_SET);
+ EXPECT_EQ(3, fd_->Write("xxx", 3));
+ ASSERT_TRUE(zero_pad_writer.Write(data.data(), bytes_to_write));
+ EXPECT_TRUE(zero_pad_writer.End());
+
+ EXPECT_EQ(data.size(), utils::FileSize(path_));
+
+ brillo::Blob result_file;
+ EXPECT_TRUE(utils::ReadFile(path_, &result_file));
+
+ brillo::Blob expected_file;
+ expected_file.insert(expected_file.end(),
+ data.begin() + kBlockSize,
+ data.begin() + kBlockSize * 2);
+ expected_file.insert(expected_file.end(),
+ data.begin(), data.begin() + kBlockSize);
+ if (missing_bytes) {
+ memset(&expected_file[kBlockSize - missing_bytes], 0, missing_bytes);
+ }
+
+ ExpectVectorsEq(expected_file, result_file);
+}
+
+TEST_F(ExtentWriterTest, SparseFileTest) {
+ vector<Extent> extents;
+ Extent extent;
+ extent.set_start_block(1);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ extent.set_start_block(kSparseHole);
+ extent.set_num_blocks(2);
+ extents.push_back(extent);
+ extent.set_start_block(0);
+ extent.set_num_blocks(1);
+ extents.push_back(extent);
+ const int block_count = 4;
+ const int on_disk_count = 2;
+
+ brillo::Blob data(17);
+ test_utils::FillWithData(&data);
+
+ DirectExtentWriter direct_writer;
+ EXPECT_TRUE(direct_writer.Init(fd_, extents, kBlockSize));
+
+ size_t bytes_written = 0;
+ while (bytes_written < (block_count * kBlockSize)) {
+ size_t bytes_to_write = min(block_count * kBlockSize - bytes_written,
+ data.size());
+ EXPECT_TRUE(direct_writer.Write(data.data(), bytes_to_write));
+ bytes_written += bytes_to_write;
+ }
+ EXPECT_TRUE(direct_writer.End());
+
+ // check file size, then data inside
+ ASSERT_EQ(2 * kBlockSize, utils::FileSize(path_));
+
+ brillo::Blob resultant_data;
+ EXPECT_TRUE(utils::ReadFile(path_, &resultant_data));
+
+ // Create expected data
+ brillo::Blob expected_data(on_disk_count * kBlockSize);
+ brillo::Blob big(block_count * kBlockSize);
+ for (brillo::Blob::size_type i = 0; i < big.size(); i++) {
+ big[i] = data[i % data.size()];
+ }
+ memcpy(&expected_data[kBlockSize], &big[0], kBlockSize);
+ memcpy(&expected_data[0], &big[3 * kBlockSize], kBlockSize);
+ ExpectVectorsEq(expected_data, resultant_data);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/fake_extent_writer.h b/payload_consumer/fake_extent_writer.h
new file mode 100644
index 0000000..762c6d5
--- /dev/null
+++ b/payload_consumer/fake_extent_writer.h
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2015 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
+
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+
+namespace chromeos_update_engine {
+
+// FakeExtentWriter is a concrete ExtentWriter subclass that keeps track of all
+// the written data, useful for testing.
+class FakeExtentWriter : public ExtentWriter {
+ public:
+ FakeExtentWriter() = default;
+ ~FakeExtentWriter() override = default;
+
+ // ExtentWriter overrides.
+ bool Init(FileDescriptorPtr /* fd */,
+ const std::vector<Extent>& /* extents */,
+ uint32_t /* block_size */) override {
+ init_called_ = true;
+ return true;
+ };
+ bool Write(const void* bytes, size_t count) override {
+ if (!init_called_ || end_called_)
+ return false;
+ written_data_.insert(written_data_.end(),
+ reinterpret_cast<const uint8_t*>(bytes),
+ reinterpret_cast<const uint8_t*>(bytes) + count);
+ return true;
+ }
+ bool EndImpl() override {
+ end_called_ = true;
+ return true;
+ }
+
+ // Fake methods.
+ bool InitCalled() { return init_called_; }
+ bool EndCalled() { return end_called_; }
+ brillo::Blob WrittenData() { return written_data_; }
+
+ private:
+ bool init_called_{false};
+ bool end_called_{false};
+ brillo::Blob written_data_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeExtentWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FAKE_EXTENT_WRITER_H_
diff --git a/payload_consumer/file_descriptor.cc b/payload_consumer/file_descriptor.cc
new file mode 100644
index 0000000..f26be28
--- /dev/null
+++ b/payload_consumer/file_descriptor.cc
@@ -0,0 +1,116 @@
+//
+// 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/file_descriptor.h"
+
+#include <fcntl.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <base/posix/eintr_wrapper.h>
+
+namespace chromeos_update_engine {
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ CHECK_EQ(fd_, -1);
+ return ((fd_ = HANDLE_EINTR(open(path, flags, mode))) >= 0);
+}
+
+bool EintrSafeFileDescriptor::Open(const char* path, int flags) {
+ CHECK_EQ(fd_, -1);
+ return ((fd_ = HANDLE_EINTR(open(path, flags))) >= 0);
+}
+
+ssize_t EintrSafeFileDescriptor::Read(void* buf, size_t count) {
+ CHECK_GE(fd_, 0);
+ return HANDLE_EINTR(read(fd_, buf, count));
+}
+
+ssize_t EintrSafeFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK_GE(fd_, 0);
+
+ // Attempt repeated writes, as long as some progress is being made.
+ char* char_buf = const_cast<char*>(reinterpret_cast<const char*>(buf));
+ ssize_t written = 0;
+ while (count > 0) {
+ ssize_t ret = HANDLE_EINTR(write(fd_, char_buf, count));
+
+ // Fail on either an error or no progress.
+ if (ret <= 0)
+ return (written ? written : ret);
+ written += ret;
+ count -= ret;
+ char_buf += ret;
+ }
+ return written;
+}
+
+off64_t EintrSafeFileDescriptor::Seek(off64_t offset, int whence) {
+ CHECK_GE(fd_, 0);
+ return lseek64(fd_, offset, whence);
+}
+
+bool EintrSafeFileDescriptor::BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) {
+ DCHECK(request == BLKDISCARD || request == BLKZEROOUT ||
+ request == BLKSECDISCARD);
+ // On some devices, the BLKDISCARD will actually read back as zeros, instead
+ // of "undefined" data. The BLKDISCARDZEROES ioctl tells whether that's the
+ // case, so we issue a BLKDISCARD in those cases to speed up the writes.
+ unsigned int arg;
+ if (request == BLKZEROOUT && ioctl(fd_, BLKDISCARDZEROES, &arg) == 0 && arg)
+ request = BLKDISCARD;
+
+ // Ensure the |fd_| is in O_DIRECT mode during this operation, so the write
+ // cache for this region is invalidated. This is required since otherwise
+ // reading back this region could consume stale data from the cache.
+ int flags = fcntl(fd_, F_GETFL, 0);
+ if (flags == -1) {
+ PLOG(WARNING) << "Couldn't get flags on fd " << fd_;
+ return false;
+ }
+ if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags | O_DIRECT) == -1) {
+ PLOG(WARNING) << "Couldn't set O_DIRECT on fd " << fd_;
+ return false;
+ }
+
+ uint64_t range[2] = {start, length};
+ *result = ioctl(fd_, request, range);
+
+ if ((flags & O_DIRECT) == 0 && fcntl(fd_, F_SETFL, flags) == -1) {
+ PLOG(WARNING) << "Couldn't remove O_DIRECT on fd " << fd_;
+ return false;
+ }
+ return true;
+}
+
+bool EintrSafeFileDescriptor::Close() {
+ CHECK_GE(fd_, 0);
+ if (IGNORE_EINTR(close(fd_)))
+ return false;
+ Reset();
+ return true;
+}
+
+void EintrSafeFileDescriptor::Reset() {
+ fd_ = -1;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/file_descriptor.h b/payload_consumer/file_descriptor.h
new file mode 100644
index 0000000..3c15415
--- /dev/null
+++ b/payload_consumer/file_descriptor.h
@@ -0,0 +1,141 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
+
+#include <errno.h>
+#include <memory>
+#include <sys/types.h>
+
+#include <base/logging.h>
+
+// Abstraction for managing opening, reading, writing and closing of file
+// descriptors. This includes an abstract class and one standard implementation
+// based on POSIX system calls.
+//
+// TODO(garnold) this class is modeled after (and augments the functionality of)
+// the FileWriter class; ultimately, the latter should be replaced by the former
+// throughout the codebase. A few deviations from the original FileWriter:
+//
+// * Providing two flavors of Open()
+//
+// * A FileDescriptor is reusable and can be used to read/write multiple files
+// as long as open/close preconditions are respected.
+//
+// * Write() returns the number of bytes written: this appears to be more useful
+// for clients, who may wish to retry or otherwise do something useful with
+// the remaining data that was not written.
+//
+// * Provides a Reset() method, which will force to abandon a currently open
+// file descriptor and allow opening another file, without necessarily
+// properly closing the old one. This may be useful in cases where a "closer"
+// class does not care whether Close() was successful, but may need to reuse
+// the same file descriptor again.
+
+namespace chromeos_update_engine {
+
+class FileDescriptor;
+using FileDescriptorPtr = std::shared_ptr<FileDescriptor>;
+
+// An abstract class defining the file descriptor API.
+class FileDescriptor {
+ public:
+ FileDescriptor() {}
+ virtual ~FileDescriptor() {}
+
+ // Opens a file descriptor. The descriptor must be in the closed state prior
+ // to this call. Returns true on success, false otherwise. Specific
+ // implementations may set errno accordingly.
+ virtual bool Open(const char* path, int flags, mode_t mode) = 0;
+ virtual bool Open(const char* path, int flags) = 0;
+
+ // Reads from a file descriptor up to a given count. The descriptor must be
+ // open prior to this call. Returns the number of bytes read, or -1 on error.
+ // Specific implementations may set errno accordingly.
+ virtual ssize_t Read(void* buf, size_t count) = 0;
+
+ // Writes to a file descriptor. The descriptor must be open prior to this
+ // call. Returns the number of bytes written, or -1 if an error occurred and
+ // no bytes were written. Specific implementations may set errno accordingly.
+ virtual ssize_t Write(const void* buf, size_t count) = 0;
+
+ // Seeks to an offset. Returns the resulting offset location as measured in
+ // bytes from the beginning. On error, return -1. Specific implementations
+ // may set errno accordingly.
+ virtual off64_t Seek(off64_t offset, int whence) = 0;
+
+ // Runs a ioctl() on the file descriptor if supported. Returns whether
+ // the operation is supported. The |request| can be one of BLKDISCARD,
+ // BLKZEROOUT and BLKSECDISCARD to discard, write zeros or securely discard
+ // the blocks. These ioctls accept a range of bytes (|start| and |length|)
+ // over which they perform the operation. The return value from the ioctl is
+ // stored in |result|.
+ virtual bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) = 0;
+
+ // Closes a file descriptor. The descriptor must be open prior to this call.
+ // Returns true on success, false otherwise. Specific implementations may set
+ // errno accordingly.
+ virtual bool Close() = 0;
+
+ // Resets the file descriptor, abandoning a currently open file and returning
+ // the descriptor to the closed state.
+ virtual void Reset() = 0;
+
+ // Indicates whether or not an implementation sets meaningful errno.
+ virtual bool IsSettingErrno() = 0;
+
+ // Indicates whether the descriptor is currently open.
+ virtual bool IsOpen() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileDescriptor);
+};
+
+// A simple EINTR-immune wrapper implementation around standard system calls.
+class EintrSafeFileDescriptor : public FileDescriptor {
+ public:
+ EintrSafeFileDescriptor() : fd_(-1) {}
+
+ // Interface methods.
+ bool Open(const char* path, int flags, mode_t mode) override;
+ bool Open(const char* path, int flags) override;
+ ssize_t Read(void* buf, size_t count) override;
+ ssize_t Write(const void* buf, size_t count) override;
+ off64_t Seek(off64_t offset, int whence) override;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override;
+ bool Close() override;
+ void Reset() override;
+ bool IsSettingErrno() override {
+ return true;
+ }
+ bool IsOpen() override {
+ return (fd_ >= 0);
+ }
+
+ protected:
+ int fd_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_DESCRIPTOR_H_
diff --git a/payload_consumer/file_writer.cc b/payload_consumer/file_writer.cc
new file mode 100644
index 0000000..d280ddb
--- /dev/null
+++ b/payload_consumer/file_writer.cc
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2009 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/file_writer.h"
+
+#include <errno.h>
+
+namespace chromeos_update_engine {
+
+int DirectFileWriter::Open(const char* path, int flags, mode_t mode) {
+ CHECK_EQ(fd_, -1);
+ fd_ = open(path, flags, mode);
+ if (fd_ < 0)
+ return -errno;
+ return 0;
+}
+
+bool DirectFileWriter::Write(const void* bytes, size_t count) {
+ CHECK_GE(fd_, 0);
+ const char* char_bytes = reinterpret_cast<const char*>(bytes);
+
+ size_t bytes_written = 0;
+ while (bytes_written < count) {
+ ssize_t rc = write(fd_, char_bytes + bytes_written,
+ count - bytes_written);
+ if (rc < 0)
+ return false;
+ bytes_written += rc;
+ }
+ CHECK_EQ(bytes_written, count);
+ return bytes_written == count;
+}
+
+int DirectFileWriter::Close() {
+ CHECK_GE(fd_, 0);
+ int rc = close(fd_);
+
+ // This can be any negative number that's not -1. This way, this FileWriter
+ // won't be used again for another file.
+ fd_ = -2;
+
+ if (rc < 0)
+ return -errno;
+ return rc;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/file_writer.h b/payload_consumer/file_writer.h
new file mode 100644
index 0000000..96ebde6
--- /dev/null
+++ b/payload_consumer/file_writer.h
@@ -0,0 +1,103 @@
+//
+// Copyright (C) 2009 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <base/logging.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/common/utils.h"
+
+// FileWriter is a class that is used to (synchronously, for now) write to
+// a file. This file is a thin wrapper around open/write/close system calls,
+// but provides and interface that can be customized by subclasses that wish
+// to filter the data.
+
+namespace chromeos_update_engine {
+
+class FileWriter {
+ public:
+ FileWriter() {}
+ virtual ~FileWriter() {}
+
+ // Wrapper around write. Returns true if all requested bytes
+ // were written, or false on any error, regardless of progress.
+ virtual bool Write(const void* bytes, size_t count) = 0;
+
+ // Same as the Write method above but returns a detailed |error| code
+ // in addition if the returned value is false. By default this method
+ // returns kActionExitDownloadWriteError as the error code, but subclasses
+ // can override if they wish to return more specific error codes.
+ virtual bool Write(const void* bytes,
+ size_t count,
+ ErrorCode* error) {
+ *error = ErrorCode::kDownloadWriteError;
+ return Write(bytes, count);
+ }
+
+ // Wrapper around close. Returns 0 on success or -errno on error.
+ virtual int Close() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileWriter);
+};
+
+// Direct file writer is probably the simplest FileWriter implementation.
+// It calls the system calls directly.
+
+class DirectFileWriter : public FileWriter {
+ public:
+ DirectFileWriter() = default;
+
+ // FileWriter overrides.
+ bool Write(const void* bytes, size_t count) override;
+ int Close() override;
+
+ // Wrapper around open. Returns 0 on success or -errno on error.
+ int Open(const char* path, int flags, mode_t mode);
+
+ int fd() const { return fd_; }
+
+ private:
+ int fd_{-1};
+
+ DISALLOW_COPY_AND_ASSIGN(DirectFileWriter);
+};
+
+class ScopedFileWriterCloser {
+ public:
+ explicit ScopedFileWriterCloser(FileWriter* writer) : writer_(writer) {}
+ ~ScopedFileWriterCloser() {
+ int err = writer_->Close();
+ if (err)
+ LOG(ERROR) << "FileWriter::Close failed: "
+ << utils::ErrnoNumberAsString(-err);
+ }
+ private:
+ FileWriter* writer_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedFileWriterCloser);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILE_WRITER_H_
diff --git a/payload_consumer/file_writer_unittest.cc b/payload_consumer/file_writer_unittest.cc
new file mode 100644
index 0000000..debb4c3
--- /dev/null
+++ b/payload_consumer/file_writer_unittest.cc
@@ -0,0 +1,79 @@
+//
+// Copyright (C) 2009 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/file_writer.h"
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FileWriterTest : public ::testing::Test { };
+
+TEST(FileWriterTest, SimpleTest) {
+ // Create a uniquely named file for testing.
+ string path;
+ ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+ ScopedPathUnlinker path_unlinker(path);
+
+ DirectFileWriter file_writer;
+ EXPECT_EQ(0, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC | O_WRONLY,
+ 0644));
+ EXPECT_TRUE(file_writer.Write("test", 4));
+ brillo::Blob actual_data;
+ EXPECT_TRUE(utils::ReadFile(path, &actual_data));
+
+ EXPECT_FALSE(memcmp("test", actual_data.data(), actual_data.size()));
+ EXPECT_EQ(0, file_writer.Close());
+}
+
+TEST(FileWriterTest, ErrorTest) {
+ DirectFileWriter file_writer;
+ const string path("/tmp/ENOENT/FileWriterTest");
+ EXPECT_EQ(-ENOENT, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC, 0644));
+}
+
+TEST(FileWriterTest, WriteErrorTest) {
+ // Create a uniquely named file for testing.
+ string path;
+ ASSERT_TRUE(utils::MakeTempFile("FileWriterTest-XXXXXX", &path, nullptr));
+ ScopedPathUnlinker path_unlinker(path);
+
+ DirectFileWriter file_writer;
+ EXPECT_EQ(0, file_writer.Open(path.c_str(),
+ O_CREAT | O_LARGEFILE | O_TRUNC | O_RDONLY,
+ 0644));
+ EXPECT_FALSE(file_writer.Write("x", 1));
+ EXPECT_EQ(0, file_writer.Close());
+}
+
+
+} // namespace chromeos_update_engine
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
diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h
new file mode 100644
index 0000000..0a66b01
--- /dev/null
+++ b/payload_consumer/filesystem_verifier_action.h
@@ -0,0 +1,129 @@
+//
+// 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <string>
+#include <vector>
+
+#include <brillo/streams/stream.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/payload_consumer/install_plan.h"
+
+// This action will hash all the partitions of a single slot involved in the
+// update (either source or target slot). The hashes are then either stored in
+// the InstallPlan (for source partitions) or verified against it (for target
+// partitions).
+
+namespace chromeos_update_engine {
+
+// The mode we are running the FilesystemVerifier on. On kComputeSourceHash mode
+// it computes the source_hash of all the partitions in the InstallPlan, based
+// on the already populated source_size values. On kVerifyTargetHash it computes
+// the hash on the target partitions based on the already populated size and
+// verifies it matches the one in the target_hash in the InstallPlan.
+enum class VerifierMode {
+ kComputeSourceHash,
+ kVerifyTargetHash,
+};
+
+class FilesystemVerifierAction : public InstallPlanAction {
+ public:
+ FilesystemVerifierAction(const BootControlInterface* boot_control,
+ VerifierMode verifier_mode);
+
+ void PerformAction() override;
+ void TerminateProcessing() override;
+
+ // Used for testing. Return true if Cleanup() has not yet been called due
+ // to a callback upon the completion or cancellation of the verifier action.
+ // A test should wait until IsCleanupPending() returns false before
+ // terminating the main loop.
+ bool IsCleanupPending() const;
+
+ // Debugging/logging
+ static std::string StaticType() { return "FilesystemVerifierAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ private:
+ friend class FilesystemVerifierActionTest;
+ FRIEND_TEST(FilesystemVerifierActionTest,
+ RunAsRootDetermineFilesystemSizeTest);
+
+ // Starts the hashing of the current partition. If there aren't any partitions
+ // remaining to be hashed, if finishes the action.
+ void StartPartitionHashing();
+
+ // Schedules the asynchronous read of the filesystem.
+ void ScheduleRead();
+
+ // Called from the main loop when a single read from |src_stream_| succeeds or
+ // fails, calling OnReadDoneCallback() and OnReadErrorCallback() respectively.
+ void OnReadDoneCallback(size_t bytes_read);
+ void OnReadErrorCallback(const brillo::Error* error);
+
+ // When the read is done, finalize the hash checking of the current partition
+ // and continue checking the next one.
+ void FinishPartitionHashing();
+
+ // Cleans up all the variables we use for async operations and tells the
+ // ActionProcessor we're done w/ |code| as passed in. |cancelled_| should be
+ // true if TerminateProcessing() was called.
+ void Cleanup(ErrorCode code);
+
+ // The type of the partition that we are verifying.
+ const VerifierMode verifier_mode_;
+
+ // The BootControlInterface used to get the partitions based on the slots.
+ const BootControlInterface* const boot_control_;
+
+ // The index in the install_plan_.partitions vector of the partition currently
+ // being hashed.
+ size_t partition_index_{0};
+
+ // If not null, the FileStream used to read from the device.
+ brillo::StreamPtr src_stream_;
+
+ // Buffer for storing data we read.
+ brillo::Blob buffer_;
+
+ bool read_done_{false}; // true if reached EOF on the input stream.
+ bool cancelled_{false}; // true if the action has been cancelled.
+
+ // The install plan we're passed in via the input pipe.
+ InstallPlan install_plan_;
+
+ // Calculates the hash of the data.
+ std::unique_ptr<HashCalculator> hasher_;
+
+ // Reads and hashes this many bytes from the head of the input stream. This
+ // field is initialized from the corresponding InstallPlan::Partition size,
+ // when the partition starts to be hashed.
+ int64_t remaining_size_{0};
+
+ DISALLOW_COPY_AND_ASSIGN(FilesystemVerifierAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_FILESYSTEM_VERIFIER_ACTION_H_
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
new file mode 100644
index 0000000..7b88aca
--- /dev/null
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -0,0 +1,387 @@
+//
+// 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 <fcntl.h>
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/posix/eintr_wrapper.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/fake_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/fake_boot_control.h"
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using brillo::MessageLoop;
+using std::set;
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class FilesystemVerifierActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ }
+
+ void TearDown() override {
+ EXPECT_EQ(0, brillo::MessageLoopRunMaxIterations(&loop_, 1));
+ }
+
+ // Returns true iff test has completed successfully.
+ bool DoTest(bool terminate_early,
+ bool hash_fail,
+ VerifierMode verifier_mode);
+
+ brillo::FakeMessageLoop loop_{nullptr};
+ FakeBootControl fake_boot_control_;
+};
+
+class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
+ public:
+ explicit FilesystemVerifierActionTestDelegate(
+ FilesystemVerifierAction* action)
+ : action_(action), ran_(false), code_(ErrorCode::kError) {}
+ void ExitMainLoop() {
+ // We need to wait for the Action to call Cleanup.
+ if (action_->IsCleanupPending()) {
+ LOG(INFO) << "Waiting for Cleanup() to be called.";
+ MessageLoop::current()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
+ base::Unretained(this)),
+ base::TimeDelta::FromMilliseconds(100));
+ } else {
+ MessageLoop::current()->BreakLoop();
+ }
+ }
+ void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+ ExitMainLoop();
+ }
+ void ProcessingStopped(const ActionProcessor* processor) {
+ ExitMainLoop();
+ }
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == FilesystemVerifierAction::StaticType()) {
+ ran_ = true;
+ code_ = code;
+ }
+ }
+ bool ran() const { return ran_; }
+ ErrorCode code() const { return code_; }
+
+ private:
+ FilesystemVerifierAction* action_;
+ bool ran_;
+ ErrorCode code_;
+};
+
+void StartProcessorInRunLoop(ActionProcessor* processor,
+ FilesystemVerifierAction* filesystem_copier_action,
+ bool terminate_early) {
+ processor->StartProcessing();
+ if (terminate_early) {
+ EXPECT_NE(nullptr, filesystem_copier_action);
+ processor->StopProcessing();
+ }
+}
+
+// TODO(garnold) Temporarily disabling this test, see chromium-os:31082 for
+// details; still trying to track down the root cause for these rare write
+// failures and whether or not they are due to the test setup or an inherent
+// issue with the chroot environment, library versions we use, etc.
+TEST_F(FilesystemVerifierActionTest, DISABLED_RunAsRootSimpleTest) {
+ ASSERT_EQ(0, getuid());
+ bool test = DoTest(false, false, VerifierMode::kComputeSourceHash);
+ EXPECT_TRUE(test);
+ if (!test)
+ return;
+ test = DoTest(false, false, VerifierMode::kVerifyTargetHash);
+ EXPECT_TRUE(test);
+}
+
+bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
+ bool hash_fail,
+ VerifierMode verifier_mode) {
+ string a_loop_file;
+
+ if (!(utils::MakeTempFile("a_loop_file.XXXXXX", &a_loop_file, nullptr))) {
+ ADD_FAILURE();
+ return false;
+ }
+ ScopedPathUnlinker a_loop_file_unlinker(a_loop_file);
+
+ // Make random data for a.
+ const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
+ brillo::Blob a_loop_data(kLoopFileSize);
+ test_utils::FillWithData(&a_loop_data);
+
+ // Write data to disk
+ if (!(test_utils::WriteFileVector(a_loop_file, a_loop_data))) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ // Attach loop devices to the files
+ string a_dev;
+ test_utils::ScopedLoopbackDeviceBinder a_dev_releaser(a_loop_file, &a_dev);
+ if (!(a_dev_releaser.is_bound())) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ LOG(INFO) << "verifying: " << a_loop_file << " (" << a_dev << ")";
+
+ bool success = true;
+
+ // Set up the action objects
+ InstallPlan install_plan;
+ install_plan.source_slot = 0;
+ install_plan.target_slot = 1;
+ InstallPlan::Partition part;
+ part.name = "part";
+ switch (verifier_mode) {
+ case VerifierMode::kVerifyTargetHash:
+ part.target_size = kLoopFileSize - (hash_fail ? 1 : 0);
+ part.target_path = a_dev;
+ fake_boot_control_.SetPartitionDevice(
+ part.name, install_plan.target_slot, a_dev);
+ if (!HashCalculator::RawHashOfData(a_loop_data, &part.target_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ break;
+ case VerifierMode::kComputeSourceHash:
+ part.source_size = kLoopFileSize;
+ part.source_path = a_dev;
+ fake_boot_control_.SetPartitionDevice(
+ part.name, install_plan.source_slot, a_dev);
+ if (!HashCalculator::RawHashOfData(a_loop_data, &part.source_hash)) {
+ ADD_FAILURE();
+ success = false;
+ }
+ break;
+ }
+ install_plan.partitions = {part};
+
+ ActionProcessor processor;
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ FilesystemVerifierAction copier_action(&fake_boot_control_, verifier_mode);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&feeder_action, &copier_action);
+ BondActions(&copier_action, &collector_action);
+
+ FilesystemVerifierActionTestDelegate delegate(&copier_action);
+ processor.set_delegate(&delegate);
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+
+ feeder_action.set_obj(install_plan);
+
+ loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
+ &processor,
+ &copier_action,
+ terminate_early));
+ loop_.Run();
+
+ if (!terminate_early) {
+ bool is_delegate_ran = delegate.ran();
+ EXPECT_TRUE(is_delegate_ran);
+ success = success && is_delegate_ran;
+ } else {
+ EXPECT_EQ(ErrorCode::kError, delegate.code());
+ return (ErrorCode::kError == delegate.code());
+ }
+ if (hash_fail) {
+ ErrorCode expected_exit_code = ErrorCode::kNewRootfsVerificationError;
+ EXPECT_EQ(expected_exit_code, delegate.code());
+ return (expected_exit_code == delegate.code());
+ }
+ EXPECT_EQ(ErrorCode::kSuccess, delegate.code());
+
+ // Make sure everything in the out_image is there
+ brillo::Blob a_out;
+ if (!utils::ReadFile(a_dev, &a_out)) {
+ ADD_FAILURE();
+ return false;
+ }
+ const bool is_a_file_reading_eq =
+ test_utils::ExpectVectorsEq(a_loop_data, a_out);
+ EXPECT_TRUE(is_a_file_reading_eq);
+ success = success && is_a_file_reading_eq;
+
+ bool is_install_plan_eq = (collector_action.object() == install_plan);
+ EXPECT_TRUE(is_install_plan_eq);
+ success = success && is_install_plan_eq;
+ return success;
+}
+
+class FilesystemVerifierActionTest2Delegate : public ActionProcessorDelegate {
+ public:
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == FilesystemVerifierAction::StaticType()) {
+ ran_ = true;
+ code_ = code;
+ }
+ }
+ bool ran_;
+ ErrorCode code_;
+};
+
+TEST_F(FilesystemVerifierActionTest, MissingInputObjectTest) {
+ ActionProcessor processor;
+ FilesystemVerifierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ FilesystemVerifierAction copier_action(&fake_boot_control_,
+ VerifierMode::kVerifyTargetHash);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&copier_action, &collector_action);
+
+ processor.EnqueueAction(&copier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, NonExistentDriveTest) {
+ ActionProcessor processor;
+ FilesystemVerifierActionTest2Delegate delegate;
+
+ processor.set_delegate(&delegate);
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan install_plan(false,
+ false,
+ "",
+ 0,
+ "",
+ 0,
+ "",
+ "");
+ InstallPlan::Partition part;
+ part.name = "nope";
+ part.source_path = "/no/such/file";
+ part.target_path = "/no/such/file";
+ install_plan.partitions = {part};
+
+ feeder_action.set_obj(install_plan);
+ FilesystemVerifierAction verifier_action(&fake_boot_control_,
+ VerifierMode::kVerifyTargetHash);
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&verifier_action, &collector_action);
+
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&verifier_action);
+ processor.EnqueueAction(&collector_action);
+ processor.StartProcessing();
+ EXPECT_FALSE(processor.IsRunning());
+ EXPECT_TRUE(delegate.ran_);
+ EXPECT_EQ(ErrorCode::kError, delegate.code_);
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(false, false, VerifierMode::kVerifyTargetHash));
+ EXPECT_TRUE(DoTest(false, false, VerifierMode::kComputeSourceHash));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootVerifyHashFailTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(false, true, VerifierMode::kVerifyTargetHash));
+}
+
+TEST_F(FilesystemVerifierActionTest, RunAsRootTerminateEarlyTest) {
+ ASSERT_EQ(0, getuid());
+ EXPECT_TRUE(DoTest(true, false, VerifierMode::kVerifyTargetHash));
+ // TerminateEarlyTest may leak some null callbacks from the Stream class.
+ while (loop_.RunOnce(false)) {}
+}
+
+// Test that the rootfs and kernel size used for hashing in delta payloads for
+// major version 1 is properly read.
+TEST_F(FilesystemVerifierActionTest, RunAsRootDetermineLegacySizeTest) {
+ string img;
+ EXPECT_TRUE(utils::MakeTempFile("img.XXXXXX", &img, nullptr));
+ ScopedPathUnlinker img_unlinker(img);
+ test_utils::CreateExtImageAtPath(img, nullptr);
+ // Extend the "partition" holding the file system from 10MiB to 20MiB.
+ EXPECT_EQ(0, truncate(img.c_str(), 20 * 1024 * 1024));
+
+ InstallPlan install_plan;
+ install_plan.source_slot = 1;
+
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameRoot, install_plan.source_slot, img);
+ fake_boot_control_.SetPartitionDevice(
+ kLegacyPartitionNameKernel, install_plan.source_slot, img);
+ FilesystemVerifierAction action(&fake_boot_control_,
+ VerifierMode::kComputeSourceHash);
+
+ ObjectFeederAction<InstallPlan> feeder_action;
+ feeder_action.set_obj(install_plan);
+
+ ObjectCollectorAction<InstallPlan> collector_action;
+
+ BondActions(&feeder_action, &action);
+ BondActions(&action, &collector_action);
+ ActionProcessor processor;
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&action);
+ processor.EnqueueAction(&collector_action);
+
+ loop_.PostTask(FROM_HERE,
+ base::Bind([&processor]{ processor.StartProcessing(); }));
+ loop_.Run();
+ install_plan = collector_action.object();
+
+ ASSERT_EQ(2, install_plan.partitions.size());
+ // When computing the size of the rootfs on legacy delta updates we use the
+ // size of the filesystem, but when updating the kernel we use the whole
+ // partition.
+ EXPECT_EQ(10 * 1024 * 1024, install_plan.partitions[0].source_size);
+ EXPECT_EQ(kLegacyPartitionNameRoot, install_plan.partitions[0].name);
+ EXPECT_EQ(20 * 1024 * 1024, install_plan.partitions[1].source_size);
+ EXPECT_EQ(kLegacyPartitionNameKernel, install_plan.partitions[1].name);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
new file mode 100644
index 0000000..f404c20
--- /dev/null
+++ b/payload_consumer/install_plan.cc
@@ -0,0 +1,124 @@
+//
+// Copyright (C) 2013 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/install_plan.h"
+
+#include <base/format_macros.h>
+#include <base/logging.h>
+#include <base/strings/stringprintf.h>
+
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_constants.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+InstallPlan::InstallPlan(bool is_resume,
+ bool is_full_update,
+ const string& url,
+ uint64_t payload_size,
+ const string& payload_hash,
+ uint64_t metadata_size,
+ const string& metadata_signature,
+ const string& public_key_rsa)
+ : is_resume(is_resume),
+ is_full_update(is_full_update),
+ download_url(url),
+ payload_size(payload_size),
+ payload_hash(payload_hash),
+ metadata_size(metadata_size),
+ metadata_signature(metadata_signature),
+ hash_checks_mandatory(false),
+ powerwash_required(false),
+ public_key_rsa(public_key_rsa) {}
+
+
+bool InstallPlan::operator==(const InstallPlan& that) const {
+ return ((is_resume == that.is_resume) &&
+ (is_full_update == that.is_full_update) &&
+ (download_url == that.download_url) &&
+ (payload_size == that.payload_size) &&
+ (payload_hash == that.payload_hash) &&
+ (metadata_size == that.metadata_size) &&
+ (metadata_signature == that.metadata_signature) &&
+ (source_slot == that.source_slot) &&
+ (target_slot == that.target_slot) &&
+ (partitions == that.partitions));
+}
+
+bool InstallPlan::operator!=(const InstallPlan& that) const {
+ return !((*this) == that);
+}
+
+void InstallPlan::Dump() const {
+ string partitions_str;
+ for (const auto& partition : partitions) {
+ partitions_str += base::StringPrintf(
+ ", part: %s (source_size: %" PRIu64 ", target_size %" PRIu64 ")",
+ partition.name.c_str(), partition.source_size, partition.target_size);
+ }
+
+ LOG(INFO) << "InstallPlan: "
+ << (is_resume ? "resume" : "new_update")
+ << ", payload type: " << (is_full_update ? "full" : "delta")
+ << ", source_slot: " << BootControlInterface::SlotName(source_slot)
+ << ", target_slot: " << BootControlInterface::SlotName(target_slot)
+ << ", url: " << download_url
+ << ", payload size: " << payload_size
+ << ", payload hash: " << payload_hash
+ << ", metadata size: " << metadata_size
+ << ", metadata signature: " << metadata_signature
+ << partitions_str
+ << ", hash_checks_mandatory: " << utils::ToString(
+ hash_checks_mandatory)
+ << ", powerwash_required: " << utils::ToString(
+ powerwash_required);
+}
+
+bool InstallPlan::LoadPartitionsFromSlots(SystemState* system_state) {
+ bool result = true;
+ for (Partition& partition : partitions) {
+ if (source_slot != BootControlInterface::kInvalidSlot) {
+ result = system_state->boot_control()->GetPartitionDevice(
+ partition.name, source_slot, &partition.source_path) && result;
+ } else {
+ partition.source_path.clear();
+ }
+
+ if (target_slot != BootControlInterface::kInvalidSlot) {
+ result = system_state->boot_control()->GetPartitionDevice(
+ partition.name, target_slot, &partition.target_path) && result;
+ } else {
+ partition.target_path.clear();
+ }
+ }
+ return result;
+}
+
+bool InstallPlan::Partition::operator==(
+ const InstallPlan::Partition& that) const {
+ return (name == that.name &&
+ source_path == that.source_path &&
+ source_size == that.source_size &&
+ source_hash == that.source_hash &&
+ target_path == that.target_path &&
+ target_size == that.target_size &&
+ target_hash == that.target_hash &&
+ run_postinstall == that.run_postinstall);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
new file mode 100644
index 0000000..f412499
--- /dev/null
+++ b/payload_consumer/install_plan.h
@@ -0,0 +1,157 @@
+//
+// Copyright (C) 2011 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/system_state.h"
+
+// InstallPlan is a simple struct that contains relevant info for many
+// parts of the update system about the install that should happen.
+namespace chromeos_update_engine {
+
+struct InstallPlan {
+ InstallPlan(bool is_resume,
+ bool is_full_update,
+ const std::string& url,
+ uint64_t payload_size,
+ const std::string& payload_hash,
+ uint64_t metadata_size,
+ const std::string& metadata_signature,
+ const std::string& public_key_rsa);
+
+ // Default constructor.
+ InstallPlan() = default;
+
+ bool operator==(const InstallPlan& that) const;
+ bool operator!=(const InstallPlan& that) const;
+
+ void Dump() const;
+
+ // Load the |source_path| and |target_path| of all |partitions| based on the
+ // |source_slot| and |target_slot| if available. Returns whether it succeeded
+ // to load all the partitions for the valid slots.
+ bool LoadPartitionsFromSlots(SystemState* system_state);
+
+ bool is_resume{false};
+ bool is_full_update{false};
+ std::string download_url; // url to download from
+ std::string version; // version we are installing.
+
+ uint64_t payload_size{0}; // size of the payload
+ std::string payload_hash; // SHA256 hash of the payload
+ uint64_t metadata_size{0}; // size of the metadata
+ std::string metadata_signature; // signature of the metadata
+
+ // The partition slots used for the update.
+ BootControlInterface::Slot source_slot{BootControlInterface::kInvalidSlot};
+ BootControlInterface::Slot target_slot{BootControlInterface::kInvalidSlot};
+
+ // The vector below is used for partition verification. The flow is:
+ //
+ // 1. FilesystemVerifierAction computes and fills in the source partition
+ // hash based on the guessed source size for delta major version 1 updates.
+ //
+ // 2. DownloadAction verifies the source partition sizes and hashes against
+ // the expected values transmitted in the update manifest. It fills in the
+ // expected target partition sizes and hashes based on the manifest.
+ //
+ // 3. FilesystemVerifierAction computes and verifies the applied partition
+ // sizes and hashes against the expected values in target_partition_hashes.
+ struct Partition {
+ bool operator==(const Partition& that) const;
+
+ // The name of the partition.
+ std::string name;
+
+ std::string source_path;
+ uint64_t source_size{0};
+ brillo::Blob source_hash;
+
+ std::string target_path;
+ uint64_t target_size{0};
+ brillo::Blob target_hash;
+
+ // Whether we should run the postinstall script from this partition.
+ bool run_postinstall{false};
+ };
+ std::vector<Partition> partitions;
+
+ // True if payload hash checks are mandatory based on the system state and
+ // the Omaha response.
+ bool hash_checks_mandatory{false};
+
+ // True if Powerwash is required on reboot after applying the payload.
+ // False otherwise.
+ bool powerwash_required{false};
+
+ // If not blank, a base-64 encoded representation of the PEM-encoded
+ // public key in the response.
+ std::string public_key_rsa;
+};
+
+class InstallPlanAction;
+
+template<>
+class ActionTraits<InstallPlanAction> {
+ public:
+ // Takes the install plan as input
+ typedef InstallPlan InputObjectType;
+ // Passes the install plan as output
+ typedef InstallPlan OutputObjectType;
+};
+
+// Basic action that only receives and sends Install Plans.
+// Can be used to construct an Install Plan to send to any other Action that
+// accept an InstallPlan.
+class InstallPlanAction : public Action<InstallPlanAction> {
+ public:
+ InstallPlanAction() {}
+ explicit InstallPlanAction(const InstallPlan& install_plan):
+ install_plan_(install_plan) {}
+
+ void PerformAction() override {
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+ processor_->ActionComplete(this, ErrorCode::kSuccess);
+ }
+
+ InstallPlan* install_plan() { return &install_plan_; }
+
+ static std::string StaticType() { return "InstallPlanAction"; }
+ std::string Type() const override { return StaticType(); }
+
+ typedef ActionTraits<InstallPlanAction>::InputObjectType InputObjectType;
+ typedef ActionTraits<InstallPlanAction>::OutputObjectType OutputObjectType;
+
+ private:
+ InstallPlan install_plan_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstallPlanAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_INSTALL_PLAN_H_
diff --git a/payload_consumer/mtd_file_descriptor.cc b/payload_consumer/mtd_file_descriptor.cc
new file mode 100644
index 0000000..6f4fae8
--- /dev/null
+++ b/payload_consumer/mtd_file_descriptor.cc
@@ -0,0 +1,265 @@
+//
+// Copyright (C) 2014 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/mtd_file_descriptor.h"
+
+#include <fcntl.h>
+#include <mtd/ubi-user.h>
+#include <string>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include <base/files/file_path.h>
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <update_engine/subprocess.h>
+
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+static const char kSysfsClassUbi[] = "/sys/class/ubi/";
+static const char kUsableEbSize[] = "/usable_eb_size";
+static const char kReservedEbs[] = "/reserved_ebs";
+
+using chromeos_update_engine::UbiVolumeInfo;
+using chromeos_update_engine::utils::ReadFile;
+
+// Return a UbiVolumeInfo pointer if |path| is a UBI volume. Otherwise, return
+// a null unique pointer.
+std::unique_ptr<UbiVolumeInfo> GetUbiVolumeInfo(const string& path) {
+ base::FilePath device_node(path);
+ base::FilePath ubi_name(device_node.BaseName());
+
+ string sysfs_node(kSysfsClassUbi);
+ sysfs_node.append(ubi_name.MaybeAsASCII());
+
+ std::unique_ptr<UbiVolumeInfo> ret;
+
+ // Obtain volume info from sysfs.
+ string s_reserved_ebs;
+ if (!ReadFile(sysfs_node + kReservedEbs, &s_reserved_ebs)) {
+ LOG(ERROR) << "Cannot read " << sysfs_node + kReservedEbs;
+ return ret;
+ }
+ string s_eb_size;
+ if (!ReadFile(sysfs_node + kUsableEbSize, &s_eb_size)) {
+ LOG(ERROR) << "Cannot read " << sysfs_node + kUsableEbSize;
+ return ret;
+ }
+
+ base::TrimWhitespaceASCII(s_reserved_ebs,
+ base::TRIM_TRAILING,
+ &s_reserved_ebs);
+ base::TrimWhitespaceASCII(s_eb_size, base::TRIM_TRAILING, &s_eb_size);
+
+ uint64_t reserved_ebs, eb_size;
+ if (!base::StringToUint64(s_reserved_ebs, &reserved_ebs)) {
+ LOG(ERROR) << "Cannot parse reserved_ebs: " << s_reserved_ebs;
+ return ret;
+ }
+ if (!base::StringToUint64(s_eb_size, &eb_size)) {
+ LOG(ERROR) << "Cannot parse usable_eb_size: " << s_eb_size;
+ return ret;
+ }
+
+ ret.reset(new UbiVolumeInfo);
+ ret->reserved_ebs = reserved_ebs;
+ ret->eraseblock_size = eb_size;
+ return ret;
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+
+MtdFileDescriptor::MtdFileDescriptor()
+ : read_ctx_(nullptr, &mtd_read_close),
+ write_ctx_(nullptr, &mtd_write_close) {}
+
+bool MtdFileDescriptor::IsMtd(const char* path) {
+ uint64_t size;
+ return mtd_node_info(path, &size, nullptr, nullptr) == 0;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ // This File Descriptor does not support read and write.
+ TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+ // But we need to open the underlying file descriptor in O_RDWR mode because
+ // during write, we need to read back to verify the write actually sticks or
+ // we have to skip the block. That job is done by mtdutils library.
+ if ((flags & O_ACCMODE) == O_WRONLY) {
+ flags &= ~O_ACCMODE;
+ flags |= O_RDWR;
+ }
+ TEST_AND_RETURN_FALSE(
+ EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+ if ((flags & O_ACCMODE) == O_RDWR) {
+ write_ctx_.reset(mtd_write_descriptor(fd_, path));
+ nr_written_ = 0;
+ } else {
+ read_ctx_.reset(mtd_read_descriptor(fd_, path));
+ }
+
+ if (!read_ctx_ && !write_ctx_) {
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+bool MtdFileDescriptor::Open(const char* path, int flags) {
+ mode_t cur = umask(022);
+ umask(cur);
+ return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t MtdFileDescriptor::Read(void* buf, size_t count) {
+ CHECK(read_ctx_);
+ return mtd_read_data(read_ctx_.get(), static_cast<char*>(buf), count);
+}
+
+ssize_t MtdFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK(write_ctx_);
+ ssize_t result = mtd_write_data(write_ctx_.get(),
+ static_cast<const char*>(buf),
+ count);
+ if (result > 0) {
+ nr_written_ += result;
+ }
+ return result;
+}
+
+off64_t MtdFileDescriptor::Seek(off64_t offset, int whence) {
+ if (write_ctx_) {
+ // Ignore seek in write mode.
+ return nr_written_;
+ }
+ return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool MtdFileDescriptor::Close() {
+ read_ctx_.reset();
+ write_ctx_.reset();
+ return EintrSafeFileDescriptor::Close();
+}
+
+bool UbiFileDescriptor::IsUbi(const char* path) {
+ base::FilePath device_node(path);
+ base::FilePath ubi_name(device_node.BaseName());
+ TEST_AND_RETURN_FALSE(
+ base::StartsWithASCII(ubi_name.MaybeAsASCII(), "ubi", true));
+
+ return static_cast<bool>(GetUbiVolumeInfo(path));
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags, mode_t mode) {
+ std::unique_ptr<UbiVolumeInfo> info = GetUbiVolumeInfo(path);
+ if (!info) {
+ return false;
+ }
+
+ // This File Descriptor does not support read and write.
+ TEST_AND_RETURN_FALSE((flags & O_ACCMODE) != O_RDWR);
+ TEST_AND_RETURN_FALSE(
+ EintrSafeFileDescriptor::Open(path, flags | O_CLOEXEC, mode));
+
+ usable_eb_blocks_ = info->reserved_ebs;
+ eraseblock_size_ = info->eraseblock_size;
+ volume_size_ = usable_eb_blocks_ * eraseblock_size_;
+
+ if ((flags & O_ACCMODE) == O_WRONLY) {
+ // It's best to use volume update ioctl so that UBI layer will mark the
+ // volume as being updated, and only clear that mark if the update is
+ // successful. We will need to pad to the whole volume size at close.
+ uint64_t vsize = volume_size_;
+ if (ioctl(fd_, UBI_IOCVOLUP, &vsize) != 0) {
+ PLOG(ERROR) << "Cannot issue volume update ioctl";
+ EintrSafeFileDescriptor::Close();
+ return false;
+ }
+ mode_ = kWriteOnly;
+ nr_written_ = 0;
+ } else {
+ mode_ = kReadOnly;
+ }
+
+ return true;
+}
+
+bool UbiFileDescriptor::Open(const char* path, int flags) {
+ mode_t cur = umask(022);
+ umask(cur);
+ return Open(path, flags, 0777 & ~cur);
+}
+
+ssize_t UbiFileDescriptor::Read(void* buf, size_t count) {
+ CHECK(mode_ == kReadOnly);
+ return EintrSafeFileDescriptor::Read(buf, count);
+}
+
+ssize_t UbiFileDescriptor::Write(const void* buf, size_t count) {
+ CHECK(mode_ == kWriteOnly);
+ ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, count);
+ if (nr_chunk >= 0) {
+ nr_written_ += nr_chunk;
+ }
+ return nr_chunk;
+}
+
+off64_t UbiFileDescriptor::Seek(off64_t offset, int whence) {
+ if (mode_ == kWriteOnly) {
+ // Ignore seek in write mode.
+ return nr_written_;
+ }
+ return EintrSafeFileDescriptor::Seek(offset, whence);
+}
+
+bool UbiFileDescriptor::Close() {
+ bool pad_ok = true;
+ if (IsOpen() && mode_ == kWriteOnly) {
+ char buf[1024];
+ memset(buf, 0xFF, sizeof(buf));
+ while (nr_written_ < volume_size_) {
+ // We have written less than the whole volume. In order for us to clear
+ // the update marker, we need to fill the rest. It is recommended to fill
+ // UBI writes with 0xFF.
+ uint64_t to_write = volume_size_ - nr_written_;
+ if (to_write > sizeof(buf)) {
+ to_write = sizeof(buf);
+ }
+ ssize_t nr_chunk = EintrSafeFileDescriptor::Write(buf, to_write);
+ if (nr_chunk < 0) {
+ LOG(ERROR) << "Cannot 0xFF-pad before closing.";
+ // There is an error, but we can't really do any meaningful thing here.
+ pad_ok = false;
+ break;
+ }
+ nr_written_ += nr_chunk;
+ }
+ }
+ return EintrSafeFileDescriptor::Close() && pad_ok;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/mtd_file_descriptor.h b/payload_consumer/mtd_file_descriptor.h
new file mode 100644
index 0000000..9ac1ec1
--- /dev/null
+++ b/payload_consumer/mtd_file_descriptor.h
@@ -0,0 +1,102 @@
+//
+// Copyright (C) 2014 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
+
+// This module defines file descriptors that deal with NAND media. We are
+// concerned with raw NAND access (as MTD device), and through UBI layer.
+
+#include <mtdutils.h>
+
+#include "update_engine/payload_consumer/file_descriptor.h"
+
+namespace chromeos_update_engine {
+
+// A class defining the file descriptor API for raw MTD device. This file
+// descriptor supports either random read, or sequential write but not both at
+// once.
+class MtdFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+ MtdFileDescriptor();
+
+ static bool IsMtd(const char* path);
+
+ bool Open(const char* path, int flags, mode_t mode) override;
+ bool Open(const char* path, int flags) override;
+ ssize_t Read(void* buf, size_t count) override;
+ ssize_t Write(const void* buf, size_t count) override;
+ off64_t Seek(off64_t offset, int whence) override;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override {
+ return false;
+ }
+ bool Close() override;
+
+ private:
+ std::unique_ptr<MtdReadContext, decltype(&mtd_read_close)> read_ctx_;
+ std::unique_ptr<MtdWriteContext, decltype(&mtd_write_close)> write_ctx_;
+ uint64_t nr_written_;
+};
+
+struct UbiVolumeInfo {
+ // Number of eraseblocks.
+ uint64_t reserved_ebs;
+ // Size of each eraseblock.
+ uint64_t eraseblock_size;
+};
+
+// A file descriptor to update a UBI volume, similar to MtdFileDescriptor.
+// Once the file descriptor is opened for write, the volume is marked as being
+// updated. The volume will not be usable until an update is completed. See
+// UBI_IOCVOLUP ioctl operation.
+class UbiFileDescriptor : public EintrSafeFileDescriptor {
+ public:
+ // Perform some queries about |path| to see if it is a UBI volume.
+ static bool IsUbi(const char* path);
+
+ bool Open(const char* path, int flags, mode_t mode) override;
+ bool Open(const char* path, int flags) override;
+ ssize_t Read(void* buf, size_t count) override;
+ ssize_t Write(const void* buf, size_t count) override;
+ off64_t Seek(off64_t offset, int whence) override;
+ bool BlkIoctl(int request,
+ uint64_t start,
+ uint64_t length,
+ int* result) override {
+ return false;
+ }
+ bool Close() override;
+
+ private:
+ enum Mode {
+ kReadOnly,
+ kWriteOnly
+ };
+
+ uint64_t usable_eb_blocks_;
+ uint64_t eraseblock_size_;
+ uint64_t volume_size_;
+ uint64_t nr_written_;
+
+ Mode mode_;
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_MTD_FILE_DESCRIPTOR_H_
diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc
new file mode 100644
index 0000000..72abf8c
--- /dev/null
+++ b/payload_consumer/payload_constants.cc
@@ -0,0 +1,59 @@
+//
+// Copyright (C) 2014 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/payload_constants.h"
+
+namespace chromeos_update_engine {
+
+const uint64_t kChromeOSMajorPayloadVersion = 1;
+const uint64_t kBrilloMajorPayloadVersion = 2;
+
+const uint32_t kFullPayloadMinorVersion = 0;
+const uint32_t kInPlaceMinorPayloadVersion = 1;
+const uint32_t kSourceMinorPayloadVersion = 2;
+const uint32_t kOpSrcHashMinorPayloadVersion = 3;
+
+const char kLegacyPartitionNameKernel[] = "boot";
+const char kLegacyPartitionNameRoot[] = "system";
+
+const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'};
+const char kBspatchPath[] = "bspatch";
+
+const char* InstallOperationTypeName(InstallOperation_Type op_type) {
+ switch (op_type) {
+ case InstallOperation::BSDIFF:
+ return "BSDIFF";
+ case InstallOperation::MOVE:
+ return "MOVE";
+ case InstallOperation::REPLACE:
+ return "REPLACE";
+ case InstallOperation::REPLACE_BZ:
+ return "REPLACE_BZ";
+ case InstallOperation::SOURCE_COPY:
+ return "SOURCE_COPY";
+ case InstallOperation::SOURCE_BSDIFF:
+ return "SOURCE_BSDIFF";
+ case InstallOperation::ZERO:
+ return "ZERO";
+ case InstallOperation::DISCARD:
+ return "DISCARD";
+ case InstallOperation::REPLACE_XZ:
+ return "REPLACE_XZ";
+ }
+ return "<unknown_op>";
+}
+
+}; // namespace chromeos_update_engine
diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h
new file mode 100644
index 0000000..c3cd363
--- /dev/null
+++ b/payload_consumer/payload_constants.h
@@ -0,0 +1,65 @@
+//
+// Copyright (C) 2014 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
+
+#include <stdint.h>
+
+#include <limits>
+
+#include "update_engine/update_metadata.pb.h"
+
+namespace chromeos_update_engine {
+
+// The major version used by Chrome OS.
+extern const uint64_t kChromeOSMajorPayloadVersion;
+
+// The major version used by Brillo.
+extern const uint64_t kBrilloMajorPayloadVersion;
+
+// The minor version used for all full payloads.
+extern const uint32_t kFullPayloadMinorVersion;
+
+// The minor version used by the in-place delta generator algorithm.
+extern const uint32_t kInPlaceMinorPayloadVersion;
+
+// The minor version used by the A to B delta generator algorithm.
+extern const uint32_t kSourceMinorPayloadVersion;
+
+// The minor version that allows per-operation source hash.
+extern const uint32_t kOpSrcHashMinorPayloadVersion;
+
+
+// The kernel and rootfs partition names used by the BootControlInterface when
+// handling update payloads with a major version 1. The names of the updated
+// partitions are include in the payload itself for major version 2.
+extern const char kLegacyPartitionNameKernel[];
+extern const char kLegacyPartitionNameRoot[];
+
+extern const char kBspatchPath[];
+extern const char kDeltaMagic[4];
+
+// A block number denoting a hole on a sparse file. Used on Extents to refer to
+// section of blocks not present on disk on a sparse file.
+const uint64_t kSparseHole = std::numeric_limits<uint64_t>::max();
+
+// Return the name of the operation type.
+const char* InstallOperationTypeName(InstallOperation_Type op_type);
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_CONSTANTS_H_
diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc
new file mode 100644
index 0000000..ab5238c
--- /dev/null
+++ b/payload_consumer/payload_verifier.cc
@@ -0,0 +1,183 @@
+//
+// Copyright (C) 2014 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/payload_verifier.h"
+
+#include <base/logging.h>
+#include <openssl/pem.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/update_metadata.pb.h"
+
+using std::string;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+// The following is a standard PKCS1-v1_5 padding for SHA256 signatures, as
+// defined in RFC3447. It is prepended to the actual signature (32 bytes) to
+// form a sequence of 256 bytes (2048 bits) that is amenable to RSA signing. The
+// padded hash will look as follows:
+//
+// 0x00 0x01 0xff ... 0xff 0x00 ASN1HEADER SHA256HASH
+// |--------------205-----------||----19----||----32----|
+//
+// where ASN1HEADER is the ASN.1 description of the signed data. The complete 51
+// bytes of actual data (i.e. the ASN.1 header complete with the hash) are
+// packed as follows:
+//
+// SEQUENCE(2+49) {
+// SEQUENCE(2+13) {
+// OBJECT(2+9) id-sha256
+// NULL(2+0)
+// }
+// OCTET STRING(2+32) <actual signature bytes...>
+// }
+const uint8_t kRSA2048SHA256Padding[] = {
+ // PKCS1-v1_5 padding
+ 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0x00,
+ // ASN.1 header
+ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
+ 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
+ 0x00, 0x04, 0x20,
+};
+
+} // namespace
+
+bool PayloadVerifier::VerifySignature(const brillo::Blob& signature_blob,
+ const string& public_key_path,
+ const brillo::Blob& hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+ Signatures signatures;
+ LOG(INFO) << "signature blob size = " << signature_blob.size();
+ TEST_AND_RETURN_FALSE(signatures.ParseFromArray(signature_blob.data(),
+ signature_blob.size()));
+
+ if (!signatures.signatures_size()) {
+ LOG(ERROR) << "No signatures stored in the blob.";
+ return false;
+ }
+
+ std::vector<brillo::Blob> tested_hashes;
+ // Tries every signature in the signature blob.
+ for (int i = 0; i < signatures.signatures_size(); i++) {
+ const Signatures_Signature& signature = signatures.signatures(i);
+ brillo::Blob sig_data(signature.data().begin(), signature.data().end());
+ brillo::Blob sig_hash_data;
+ if (!GetRawHashFromSignature(sig_data, public_key_path, &sig_hash_data))
+ continue;
+
+ if (hash_data == sig_hash_data) {
+ LOG(INFO) << "Verified correct signature " << i + 1 << " out of "
+ << signatures.signatures_size() << " signatures.";
+ return true;
+ }
+ tested_hashes.push_back(sig_hash_data);
+ }
+ LOG(ERROR) << "None of the " << signatures.signatures_size()
+ << " signatures is correct. Expected:";
+ utils::HexDumpVector(hash_data);
+ LOG(ERROR) << "But found decrypted hashes:";
+ for (const auto& sig_hash_data : tested_hashes) {
+ utils::HexDumpVector(sig_hash_data);
+ }
+ return false;
+}
+
+
+bool PayloadVerifier::GetRawHashFromSignature(
+ const brillo::Blob& sig_data,
+ const string& public_key_path,
+ brillo::Blob* out_hash_data) {
+ TEST_AND_RETURN_FALSE(!public_key_path.empty());
+
+ // The code below executes the equivalent of:
+ //
+ // openssl rsautl -verify -pubin -inkey |public_key_path|
+ // -in |sig_data| -out |out_hash_data|
+
+ // Loads the public key.
+ FILE* fpubkey = fopen(public_key_path.c_str(), "rb");
+ if (!fpubkey) {
+ LOG(ERROR) << "Unable to open public key file: " << public_key_path;
+ return false;
+ }
+
+ char dummy_password[] = { ' ', 0 }; // Ensure no password is read from stdin.
+ RSA* rsa = PEM_read_RSA_PUBKEY(fpubkey, nullptr, nullptr, dummy_password);
+ fclose(fpubkey);
+ TEST_AND_RETURN_FALSE(rsa != nullptr);
+ unsigned int keysize = RSA_size(rsa);
+ if (sig_data.size() > 2 * keysize) {
+ LOG(ERROR) << "Signature size is too big for public key size.";
+ RSA_free(rsa);
+ return false;
+ }
+
+ // Decrypts the signature.
+ brillo::Blob hash_data(keysize);
+ int decrypt_size = RSA_public_decrypt(sig_data.size(),
+ sig_data.data(),
+ hash_data.data(),
+ rsa,
+ RSA_NO_PADDING);
+ RSA_free(rsa);
+ TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
+ decrypt_size <= static_cast<int>(hash_data.size()));
+ hash_data.resize(decrypt_size);
+ out_hash_data->swap(hash_data);
+ return true;
+}
+
+bool PayloadVerifier::PadRSA2048SHA256Hash(brillo::Blob* hash) {
+ TEST_AND_RETURN_FALSE(hash->size() == 32);
+ hash->insert(hash->begin(),
+ reinterpret_cast<const char*>(kRSA2048SHA256Padding),
+ reinterpret_cast<const char*>(kRSA2048SHA256Padding +
+ sizeof(kRSA2048SHA256Padding)));
+ TEST_AND_RETURN_FALSE(hash->size() == 256);
+ return true;
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/payload_verifier.h b/payload_consumer/payload_verifier.h
new file mode 100644
index 0000000..22ced40
--- /dev/null
+++ b/payload_consumer/payload_verifier.h
@@ -0,0 +1,65 @@
+//
+// Copyright (C) 2014 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+#include <brillo/secure_blob.h>
+
+#include "update_engine/update_metadata.pb.h"
+
+// This class encapsulates methods used for payload signature verification.
+// See payload_generator/payload_signer.h for payload signing.
+
+namespace chromeos_update_engine {
+
+class PayloadVerifier {
+ public:
+ // Interprets |signature_blob| as a protocol buffer containing the Signatures
+ // message and decrypts each signature data using the |public_key_path|.
+ // Returns whether *any* of the decrypted hashes matches the |hash_data|.
+ // In case of any error parsing the signatures or the public key, returns
+ // false.
+ static bool VerifySignature(const brillo::Blob& signature_blob,
+ const std::string& public_key_path,
+ const brillo::Blob& hash_data);
+
+ // Decrypts sig_data with the given public_key_path and populates
+ // out_hash_data with the decoded raw hash. Returns true if successful,
+ // false otherwise.
+ static bool GetRawHashFromSignature(const brillo::Blob& sig_data,
+ const std::string& public_key_path,
+ brillo::Blob* out_hash_data);
+
+ // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048
+ // using the PKCS#1 v1.5 scheme.
+ // hash should be a pointer to vector of exactly 256 bits. The vector
+ // will be modified in place and will result in having a length of
+ // 2048 bits. Returns true on success, false otherwise.
+ static bool PadRSA2048SHA256Hash(brillo::Blob* hash);
+
+ private:
+ // This should never be constructed
+ DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
new file mode 100644
index 0000000..33bbf5b
--- /dev/null
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -0,0 +1,187 @@
+//
+// Copyright (C) 2011 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/postinstall_runner_action.h"
+
+#include <stdlib.h>
+#include <sys/mount.h>
+#include <vector>
+
+#include <base/bind.h>
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+
+#include "update_engine/common/action_processor.h"
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+namespace chromeos_update_engine {
+
+using std::string;
+using std::vector;
+
+namespace {
+// The absolute path to the post install command.
+const char kPostinstallScript[] = "/postinst";
+
+// Path to the binary file used by kPostinstallScript. Used to get and log the
+// file format of the binary to debug issues when the ELF format on the update
+// doesn't match the one on the current system. This path is not executed.
+const char kDebugPostinstallBinaryPath[] = "/usr/bin/cros_installer";
+}
+
+void PostinstallRunnerAction::PerformAction() {
+ CHECK(HasInputObject());
+ install_plan_ = GetInputObject();
+
+ if (install_plan_.powerwash_required) {
+ if (utils::CreatePowerwashMarkerFile(powerwash_marker_file_)) {
+ powerwash_marker_created_ = true;
+ } else {
+ return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
+ }
+ }
+
+ PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::PerformPartitionPostinstall() {
+ // Skip all the partitions that don't have a post-install step.
+ while (current_partition_ < install_plan_.partitions.size() &&
+ !install_plan_.partitions[current_partition_].run_postinstall) {
+ VLOG(1) << "Skipping post-install on partition "
+ << install_plan_.partitions[current_partition_].name;
+ current_partition_++;
+ }
+ if (current_partition_ == install_plan_.partitions.size())
+ return CompletePostinstall(ErrorCode::kSuccess);
+
+ const InstallPlan::Partition& partition =
+ install_plan_.partitions[current_partition_];
+
+ const string mountable_device =
+ utils::MakePartitionNameForMount(partition.target_path);
+ if (mountable_device.empty()) {
+ LOG(ERROR) << "Cannot make mountable device from " << partition.target_path;
+ return CompletePostinstall(ErrorCode::kPostinstallRunnerError);
+ }
+
+ // Perform post-install for the current_partition_ partition. At this point we
+ // need to call CompletePartitionPostinstall to complete the operation and
+ // cleanup.
+ TEST_AND_RETURN(
+ utils::MakeTempDirectory("au_postint_mount.XXXXXX", &temp_rootfs_dir_));
+
+ if (!utils::MountFilesystem(mountable_device, temp_rootfs_dir_, MS_RDONLY)) {
+ return CompletePartitionPostinstall(
+ 1, "Error mounting the device " + mountable_device);
+ }
+
+ LOG(INFO) << "Performing postinst (" << kPostinstallScript
+ << ") installed on device " << partition.target_path
+ << " and mountable device " << mountable_device;
+
+ // Logs the file format of the postinstall script we are about to run. This
+ // will help debug when the postinstall script doesn't match the architecture
+ // of our build.
+ LOG(INFO) << "Format file for new " << kPostinstallScript << " is: "
+ << utils::GetFileFormat(temp_rootfs_dir_ + kPostinstallScript);
+ LOG(INFO) << "Format file for new " << kDebugPostinstallBinaryPath << " is: "
+ << utils::GetFileFormat(
+ temp_rootfs_dir_ + kDebugPostinstallBinaryPath);
+
+ // Runs the postinstall script asynchronously to free up the main loop while
+ // it's running.
+ vector<string> command;
+ if (!install_plan_.download_url.empty()) {
+ command.push_back(temp_rootfs_dir_ + kPostinstallScript);
+ } else {
+ // TODO(sosa): crbug.com/366207.
+ // If we're doing a rollback, just run our own postinstall.
+ command.push_back(kPostinstallScript);
+ }
+ command.push_back(partition.target_path);
+ if (!Subprocess::Get().Exec(
+ command,
+ base::Bind(
+ &PostinstallRunnerAction::CompletePartitionPostinstall,
+ base::Unretained(this)))) {
+ CompletePartitionPostinstall(1, "Postinstall didn't launch");
+ }
+}
+
+void PostinstallRunnerAction::CompletePartitionPostinstall(
+ int return_code,
+ const string& output) {
+ utils::UnmountFilesystem(temp_rootfs_dir_);
+ if (!base::DeleteFile(base::FilePath(temp_rootfs_dir_), false)) {
+ PLOG(WARNING) << "Not removing mountpoint " << temp_rootfs_dir_;
+ }
+ temp_rootfs_dir_.clear();
+
+ if (return_code != 0) {
+ LOG(ERROR) << "Postinst command failed with code: " << return_code;
+ ErrorCode error_code = ErrorCode::kPostinstallRunnerError;
+
+ if (return_code == 3) {
+ // This special return code means that we tried to update firmware,
+ // but couldn't because we booted from FW B, and we need to reboot
+ // to get back to FW A.
+ error_code = ErrorCode::kPostinstallBootedFromFirmwareB;
+ }
+
+ if (return_code == 4) {
+ // This special return code means that we tried to update firmware,
+ // but couldn't because we booted from FW B, and we need to reboot
+ // to get back to FW A.
+ error_code = ErrorCode::kPostinstallFirmwareRONotUpdatable;
+ }
+ return CompletePostinstall(error_code);
+ }
+ current_partition_++;
+ PerformPartitionPostinstall();
+}
+
+void PostinstallRunnerAction::CompletePostinstall(ErrorCode error_code) {
+ // We only attempt to mark the new slot as active if all the postinstall
+ // steps succeeded.
+ if (error_code == ErrorCode::kSuccess &&
+ !system_state_->boot_control()->SetActiveBootSlot(
+ install_plan_.target_slot)) {
+ error_code = ErrorCode::kPostinstallRunnerError;
+ }
+
+ ScopedActionCompleter completer(processor_, this);
+
+ if (error_code != ErrorCode::kSuccess) {
+ LOG(ERROR) << "Postinstall action failed.";
+
+ // Undo any changes done to trigger Powerwash using clobber-state.
+ if (powerwash_marker_created_)
+ utils::DeletePowerwashMarkerFile(powerwash_marker_file_);
+
+ return;
+ }
+
+ LOG(INFO) << "All post-install commands succeeded";
+ if (HasOutputPipe()) {
+ SetOutputObject(install_plan_);
+ }
+
+ completer.set_code(ErrorCode::kSuccess);
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/postinstall_runner_action.h b/payload_consumer/postinstall_runner_action.h
new file mode 100644
index 0000000..de19c0c
--- /dev/null
+++ b/payload_consumer/postinstall_runner_action.h
@@ -0,0 +1,86 @@
+//
+// Copyright (C) 2010 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
+
+#include <string>
+
+#include "update_engine/common/action.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/system_state.h"
+
+// The Postinstall Runner Action is responsible for running the postinstall
+// script of a successfully downloaded update.
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerAction : public InstallPlanAction {
+ public:
+ explicit PostinstallRunnerAction(SystemState* system_state)
+ : PostinstallRunnerAction(system_state, nullptr) {}
+
+ void PerformAction();
+
+ // Note that there's no support for terminating this action currently.
+ void TerminateProcessing() { CHECK(false); }
+
+ // Debugging/logging
+ static std::string StaticType() { return "PostinstallRunnerAction"; }
+ std::string Type() const { return StaticType(); }
+
+ private:
+ friend class PostinstallRunnerActionTest;
+
+ // Special constructor used for testing purposes.
+ PostinstallRunnerAction(SystemState* system_state,
+ const char* powerwash_marker_file)
+ : system_state_(system_state),
+ powerwash_marker_file_(powerwash_marker_file) {}
+
+ void PerformPartitionPostinstall();
+
+ // Subprocess::Exec callback.
+ void CompletePartitionPostinstall(int return_code,
+ const std::string& output);
+
+ //
+ void CompletePostinstall(ErrorCode error_code);
+
+ InstallPlan install_plan_;
+ std::string temp_rootfs_dir_;
+
+ // The partition being processed on the list of partitions specified in the
+ // InstallPlan.
+ size_t current_partition_{0};
+
+ // The main SystemState singleton.
+ SystemState* system_state_;
+
+ // True if Powerwash Marker was created before invoking post-install script.
+ // False otherwise. Used for cleaning up if post-install fails.
+ bool powerwash_marker_created_{false};
+
+ // Non-null value will cause post-install to override the default marker
+ // file name; used for testing.
+ const char* powerwash_marker_file_;
+
+ DISALLOW_COPY_AND_ASSIGN(PostinstallRunnerAction);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_POSTINSTALL_RUNNER_ACTION_H_
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
new file mode 100644
index 0000000..c54ace8
--- /dev/null
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -0,0 +1,260 @@
+//
+// 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/postinstall_runner_action.h"
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/message_loop/message_loop.h>
+#include <base/strings/string_util.h>
+#include <base/strings/stringprintf.h>
+#include <brillo/bind_lambda.h>
+#include <brillo/message_loops/base_message_loop.h>
+#include <brillo/message_loops/message_loop_utils.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/fake_system_state.h"
+
+using brillo::MessageLoop;
+using chromeos_update_engine::test_utils::System;
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+using std::unique_ptr;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+class PostinstallRunnerActionTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ loop_.SetAsCurrent();
+ async_signal_handler_.Init();
+ subprocess_.Init(&async_signal_handler_);
+ }
+
+ // DoTest with various combinations of do_losetup, err_code and
+ // powerwash_required.
+ void DoTest(bool do_losetup, int err_code, bool powerwash_required);
+
+ protected:
+ static const char* kImageMountPointTemplate;
+
+ base::MessageLoopForIO base_loop_;
+ brillo::BaseMessageLoop loop_{&base_loop_};
+ brillo::AsynchronousSignalHandler async_signal_handler_;
+ Subprocess subprocess_;
+ FakeSystemState fake_system_state_;
+};
+
+class PostinstActionProcessorDelegate : public ActionProcessorDelegate {
+ public:
+ PostinstActionProcessorDelegate()
+ : code_(ErrorCode::kError),
+ code_set_(false) {}
+ void ProcessingDone(const ActionProcessor* processor,
+ ErrorCode code) {
+ MessageLoop::current()->BreakLoop();
+ }
+ void ActionCompleted(ActionProcessor* processor,
+ AbstractAction* action,
+ ErrorCode code) {
+ if (action->Type() == PostinstallRunnerAction::StaticType()) {
+ code_ = code;
+ code_set_ = true;
+ }
+ }
+ ErrorCode code_;
+ bool code_set_;
+};
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
+ DoTest(true, 0, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
+ DoTest(true, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
+ DoTest(false, 0, true);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
+ DoTest(true, 1, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
+ DoTest(true, 3, false);
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareROErrScriptTest) {
+ DoTest(true, 4, false);
+}
+
+const char* PostinstallRunnerActionTest::kImageMountPointTemplate =
+ "au_destination-XXXXXX";
+
+void PostinstallRunnerActionTest::DoTest(
+ bool do_losetup,
+ int err_code,
+ bool powerwash_required) {
+ ASSERT_EQ(0, getuid()) << "Run me as root. Ideally don't run other tests "
+ << "as root, tho.";
+ // True if the post-install action is expected to succeed.
+ bool should_succeed = do_losetup && !err_code;
+
+ string orig_cwd;
+ {
+ vector<char> buf(1000);
+ ASSERT_EQ(buf.data(), getcwd(buf.data(), buf.size()));
+ orig_cwd = string(buf.data(), strlen(buf.data()));
+ }
+
+ // Create a unique named working directory and chdir into it.
+ string cwd;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ "postinstall_runner_action_unittest-XXXXXX",
+ &cwd));
+ ASSERT_EQ(0, test_utils::Chdir(cwd));
+
+ // Create a 10MiB sparse file to be used as image; format it as ext2.
+ ASSERT_EQ(0, System(
+ "dd if=/dev/zero of=image.dat seek=10485759 bs=1 count=1 "
+ "status=none"));
+ ASSERT_EQ(0, System("mkfs.ext2 -F image.dat"));
+
+ // Create a uniquely named image mount point, mount the image.
+ ASSERT_EQ(0, System(string("mkdir -p ") + kStatefulPartition));
+ string mountpoint;
+ ASSERT_TRUE(utils::MakeTempDirectory(
+ string(kStatefulPartition) + "/" + kImageMountPointTemplate,
+ &mountpoint));
+ ASSERT_EQ(0, System(string("mount -o loop image.dat ") + mountpoint));
+
+ // Generate a fake postinst script inside the image.
+ string script = (err_code ?
+ base::StringPrintf("#!/bin/bash\nexit %d", err_code) :
+ base::StringPrintf(
+ "#!/bin/bash\n"
+ "mount | grep au_postint_mount | grep ext2\n"
+ "if [ $? -eq 0 ]; then\n"
+ " touch %s/postinst_called\n"
+ "fi\n",
+ cwd.c_str()));
+ const string script_file_name = mountpoint + "/postinst";
+ ASSERT_TRUE(WriteFileString(script_file_name, script));
+ ASSERT_EQ(0, System(string("chmod a+x ") + script_file_name));
+
+ // Unmount image; do not remove the uniquely named directory as it will be
+ // reused during the test.
+ ASSERT_TRUE(utils::UnmountFilesystem(mountpoint));
+
+ // get a loop device we can use for the install device
+ string dev = "/dev/null";
+
+ unique_ptr<test_utils::ScopedLoopbackDeviceBinder> loop_releaser;
+ if (do_losetup) {
+ loop_releaser.reset(new test_utils::ScopedLoopbackDeviceBinder(
+ cwd + "/image.dat", &dev));
+ }
+
+ // We use a test-specific powerwash marker file, to avoid race conditions.
+ string powerwash_marker_file = mountpoint + "/factory_install_reset";
+ LOG(INFO) << ">>> powerwash_marker_file=" << powerwash_marker_file;
+
+ ActionProcessor processor;
+ ObjectFeederAction<InstallPlan> feeder_action;
+ InstallPlan::Partition part;
+ part.name = "part";
+ part.target_path = dev;
+ part.run_postinstall = true;
+ InstallPlan install_plan;
+ install_plan.partitions = {part};
+ install_plan.download_url = "http://devserver:8080/update";
+ install_plan.powerwash_required = powerwash_required;
+ feeder_action.set_obj(install_plan);
+ PostinstallRunnerAction runner_action(&fake_system_state_,
+ powerwash_marker_file.c_str());
+ BondActions(&feeder_action, &runner_action);
+ ObjectCollectorAction<InstallPlan> collector_action;
+ BondActions(&runner_action, &collector_action);
+ PostinstActionProcessorDelegate delegate;
+ processor.EnqueueAction(&feeder_action);
+ processor.EnqueueAction(&runner_action);
+ processor.EnqueueAction(&collector_action);
+ processor.set_delegate(&delegate);
+
+ loop_.PostTask(FROM_HERE,
+ base::Bind([&processor] { processor.StartProcessing(); }));
+ loop_.Run();
+ ASSERT_FALSE(processor.IsRunning());
+
+ EXPECT_TRUE(delegate.code_set_);
+ EXPECT_EQ(should_succeed, delegate.code_ == ErrorCode::kSuccess);
+ if (should_succeed)
+ EXPECT_TRUE(install_plan == collector_action.object());
+
+ const base::FilePath kPowerwashMarkerPath(powerwash_marker_file);
+ string actual_cmd;
+ if (should_succeed && powerwash_required) {
+ EXPECT_TRUE(base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+ EXPECT_EQ(kPowerwashCommand, actual_cmd);
+ } else {
+ EXPECT_FALSE(
+ base::ReadFileToString(kPowerwashMarkerPath, &actual_cmd));
+ }
+
+ if (err_code == 2)
+ EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB, delegate.code_);
+
+ struct stat stbuf;
+ int rc = lstat((string(cwd) + "/postinst_called").c_str(), &stbuf);
+ if (should_succeed)
+ ASSERT_EQ(0, rc);
+ else
+ ASSERT_LT(rc, 0);
+
+ if (do_losetup) {
+ loop_releaser.reset(nullptr);
+ }
+
+ // Remove unique stateful directory.
+ ASSERT_EQ(0, System(string("rm -fr ") + mountpoint));
+
+ // Remove the temporary work directory.
+ ASSERT_EQ(0, test_utils::Chdir(orig_cwd));
+ ASSERT_EQ(0, System(string("rm -fr ") + cwd));
+}
+
+// Death tests don't seem to be working on Hardy
+TEST_F(PostinstallRunnerActionTest, DISABLED_RunAsRootDeathTest) {
+ ASSERT_EQ(0, getuid());
+ PostinstallRunnerAction runner_action(&fake_system_state_);
+ ASSERT_DEATH({ runner_action.TerminateProcessing(); },
+ "postinstall_runner_action.h:.*] Check failed");
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/xz_extent_writer.cc b/payload_consumer/xz_extent_writer.cc
new file mode 100644
index 0000000..4bd893d
--- /dev/null
+++ b/payload_consumer/xz_extent_writer.cc
@@ -0,0 +1,118 @@
+//
+// Copyright (C) 2015 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/xz_extent_writer.h"
+
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+const brillo::Blob::size_type kOutputBufferLength = 16 * 1024;
+
+// xz uses a variable dictionary size which impacts on the compression ratio
+// and is required to be reconstructed in RAM during decompression. While we
+// control the required memory from the compressor side, the decompressor allows
+// to set a limit on this dictionary size, rejecting compressed streams that
+// require more than that. "xz -9" requires up to 64 MiB, so a 64 MiB limit
+// will allow compressed streams up to -9, the maximum compression setting.
+const uint32_t kXzMaxDictSize = 64 * 1024 * 1024;
+
+const char* XzErrorString(enum xz_ret error) {
+ #define __XZ_ERROR_STRING_CASE(code) case code: return #code;
+ switch (error) {
+ __XZ_ERROR_STRING_CASE(XZ_OK)
+ __XZ_ERROR_STRING_CASE(XZ_STREAM_END)
+ __XZ_ERROR_STRING_CASE(XZ_UNSUPPORTED_CHECK)
+ __XZ_ERROR_STRING_CASE(XZ_MEM_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_MEMLIMIT_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_FORMAT_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_OPTIONS_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_DATA_ERROR)
+ __XZ_ERROR_STRING_CASE(XZ_BUF_ERROR)
+ default:
+ return "<unknown xz error>";
+ }
+ #undef __XZ_ERROR_STRING_CASE
+};
+} // namespace
+
+XzExtentWriter::~XzExtentWriter() {
+ xz_dec_end(stream_);
+}
+
+bool XzExtentWriter::Init(FileDescriptorPtr fd,
+ const vector<Extent>& extents,
+ uint32_t block_size) {
+ stream_ = xz_dec_init(XZ_DYNALLOC, kXzMaxDictSize);
+ TEST_AND_RETURN_FALSE(stream_ != nullptr);
+ return underlying_writer_->Init(fd, extents, block_size);
+}
+
+bool XzExtentWriter::Write(const void* bytes, size_t count) {
+ // Copy the input data into |input_buffer_| only if |input_buffer_| already
+ // contains unconsumed data. Otherwise, process the data directly from the
+ // source.
+ const uint8_t* input = reinterpret_cast<const uint8_t*>(bytes);
+ if (!input_buffer_.empty()) {
+ input_buffer_.insert(input_buffer_.end(), input, input + count);
+ input = input_buffer_.data();
+ count = input_buffer_.size();
+ }
+
+ xz_buf request;
+ request.in = input;
+ request.in_pos = 0;
+ request.in_size = count;
+
+ brillo::Blob output_buffer(kOutputBufferLength);
+ request.out = output_buffer.data();
+ request.out_size = output_buffer.size();
+ for (;;) {
+ request.out_pos = 0;
+
+ xz_ret ret = xz_dec_run(stream_, &request);
+ if (ret != XZ_OK && ret != XZ_STREAM_END) {
+ LOG(ERROR) << "xz_dec_run returned " << XzErrorString(ret);
+ return false;
+ }
+
+ if (request.out_pos == 0)
+ break;
+
+ TEST_AND_RETURN_FALSE(
+ underlying_writer_->Write(output_buffer.data(), request.out_pos));
+ if (ret == XZ_STREAM_END)
+ CHECK_EQ(request.in_size, request.in_pos);
+ if (request.in_size == request.in_pos)
+ break; // No more input to process.
+ }
+ output_buffer.clear();
+
+ // Store unconsumed data (if any) in |input_buffer_|. Since |input| can point
+ // to the existing |input_buffer_| we create a new one before assigning it.
+ brillo::Blob new_input_buffer(request.in + request.in_pos,
+ request.in + request.in_size);
+ input_buffer_ = std::move(new_input_buffer);
+ return true;
+}
+
+bool XzExtentWriter::EndImpl() {
+ TEST_AND_RETURN_FALSE(input_buffer_.empty());
+ return underlying_writer_->End();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/xz_extent_writer.h b/payload_consumer/xz_extent_writer.h
new file mode 100644
index 0000000..a6b3257
--- /dev/null
+++ b/payload_consumer/xz_extent_writer.h
@@ -0,0 +1,60 @@
+//
+// Copyright (C) 2015 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.
+//
+
+#ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
+#define UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
+
+#include <xz.h>
+
+#include <memory>
+#include <vector>
+
+#include <brillo/secure_blob.h>
+
+#include "update_engine/payload_consumer/extent_writer.h"
+
+// XzExtentWriter is a concrete ExtentWriter subclass that xz-decompresses
+// what it's given in Write using xz-embedded. Note that xz-embedded only
+// supports files with either no CRC or CRC-32. It passes the decompressed data
+// to an underlying ExtentWriter.
+
+namespace chromeos_update_engine {
+
+class XzExtentWriter : public ExtentWriter {
+ public:
+ explicit XzExtentWriter(std::unique_ptr<ExtentWriter> underlying_writer)
+ : underlying_writer_(std::move(underlying_writer)) {}
+ ~XzExtentWriter() override;
+
+ bool Init(FileDescriptorPtr fd,
+ const std::vector<Extent>& extents,
+ uint32_t block_size) override;
+ bool Write(const void* bytes, size_t count) override;
+ bool EndImpl() override;
+
+ private:
+ // The underlying ExtentWriter.
+ std::unique_ptr<ExtentWriter> underlying_writer_;
+ // The opaque xz decompressor struct.
+ xz_dec* stream_{nullptr};
+ brillo::Blob input_buffer_;
+
+ DISALLOW_COPY_AND_ASSIGN(XzExtentWriter);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_PAYLOAD_CONSUMER_XZ_EXTENT_WRITER_H_
diff --git a/payload_consumer/xz_extent_writer_unittest.cc b/payload_consumer/xz_extent_writer_unittest.cc
new file mode 100644
index 0000000..fb8bb40
--- /dev/null
+++ b/payload_consumer/xz_extent_writer_unittest.cc
@@ -0,0 +1,165 @@
+//
+// Copyright (C) 2015 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/xz_extent_writer.h"
+
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include <brillo/make_unique_ptr.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/fake_extent_writer.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+namespace {
+
+const char kSampleData[] = "Redundaaaaaaaaaaaaaant\n";
+
+// Compressed data with CRC-32 check, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=crc32 |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataCRC32[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+ 0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+ 0x68, 0xfc, 0x7b, 0x25, 0x00, 0x01, 0x28, 0x17, 0x46, 0x9e, 0x08, 0xfe,
+ 0x90, 0x42, 0x99, 0x0d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+// Compressed data without checksum, generated with:
+// echo "Redundaaaaaaaaaaaaaant" | xz -9 --check=none |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressedDataNoCheck[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x00, 0xff, 0x12, 0xd9, 0x41,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x00, 0x16, 0x00, 0x10, 0x5d, 0x00, 0x29, 0x19, 0x48, 0x87, 0x88,
+ 0xec, 0x49, 0x88, 0x73, 0x8b, 0x5d, 0xa6, 0x46, 0xb4, 0x00, 0x00, 0x00,
+ 0x00, 0x01, 0x24, 0x17, 0x4a, 0xd1, 0xbd, 0x52, 0x06, 0x72, 0x9e, 0x7a,
+ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x5a,
+};
+
+// Highly redundant data bigger than the internal buffer, generated with:
+// dd if=/dev/zero bs=30K count=1 | tr '\0' 'a' | xz -9 --check=crc32 |
+// hexdump -v -e '" " 12/1 "0x%02x, " "\n"'
+const uint8_t kCompressed30KiBofA[] = {
+ 0xfd, 0x37, 0x7a, 0x58, 0x5a, 0x00, 0x00, 0x01, 0x69, 0x22, 0xde, 0x36,
+ 0x02, 0x00, 0x21, 0x01, 0x1c, 0x00, 0x00, 0x00, 0x10, 0xcf, 0x58, 0xcc,
+ 0xe0, 0x77, 0xff, 0x00, 0x41, 0x5d, 0x00, 0x30, 0xef, 0xfb, 0xbf, 0xfe,
+ 0xa3, 0xb1, 0x5e, 0xe5, 0xf8, 0x3f, 0xb2, 0xaa, 0x26, 0x55, 0xf8, 0x68,
+ 0x70, 0x41, 0x70, 0x15, 0x0f, 0x8d, 0xfd, 0x1e, 0x4c, 0x1b, 0x8a, 0x42,
+ 0xb7, 0x19, 0xf4, 0x69, 0x18, 0x71, 0xae, 0x66, 0x23, 0x8a, 0x8a, 0x4d,
+ 0x2f, 0xa3, 0x0d, 0xd9, 0x7f, 0xa6, 0xe3, 0x8c, 0x23, 0x11, 0x53, 0xe0,
+ 0x59, 0x18, 0xc5, 0x75, 0x8a, 0xe2, 0x76, 0x4c, 0xee, 0x30, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xf9, 0x47, 0xb5, 0xee, 0x00, 0x01, 0x59, 0x80,
+ 0xf0, 0x01, 0x00, 0x00, 0xe0, 0x41, 0x96, 0xde, 0x3e, 0x30, 0x0d, 0x8b,
+ 0x02, 0x00, 0x00, 0x00, 0x00, 0x01, 0x59, 0x5a,
+};
+
+} // namespace
+
+class XzExtentWriterTest : public ::testing::Test {
+ protected:
+ void SetUp() override {
+ fake_extent_writer_ = new FakeExtentWriter();
+ xz_writer_.reset(
+ new XzExtentWriter(brillo::make_unique_ptr(fake_extent_writer_)));
+ }
+
+ void WriteAll(const brillo::Blob& compressed) {
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ EXPECT_TRUE(xz_writer_->Write(compressed.data(), compressed.size()));
+ EXPECT_TRUE(xz_writer_->End());
+
+ EXPECT_TRUE(fake_extent_writer_->InitCalled());
+ EXPECT_TRUE(fake_extent_writer_->EndCalled());
+ }
+
+ // Owned by |xz_writer_|. This object is invalidated after |xz_writer_| is
+ // deleted.
+ FakeExtentWriter* fake_extent_writer_{nullptr};
+ std::unique_ptr<XzExtentWriter> xz_writer_;
+
+ const brillo::Blob sample_data_{
+ std::begin(kSampleData),
+ std::begin(kSampleData) + strlen(kSampleData)};
+ FileDescriptorPtr fd_;
+};
+
+TEST_F(XzExtentWriterTest, CreateAndDestroy) {
+ // Test that no Init() or End() called doesn't crash the program.
+ EXPECT_FALSE(fake_extent_writer_->InitCalled());
+ EXPECT_FALSE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleData) {
+ WriteAll(brillo::Blob(std::begin(kCompressedDataNoCheck),
+ std::end(kCompressedDataNoCheck)));
+ EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedSampleDataWithCrc) {
+ WriteAll(brillo::Blob(std::begin(kCompressedDataCRC32),
+ std::end(kCompressedDataCRC32)));
+ EXPECT_EQ(sample_data_, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, CompressedDataBiggerThanTheBuffer) {
+ // Test that even if the output data is bigger than the internal buffer, all
+ // the data is written.
+ WriteAll(brillo::Blob(std::begin(kCompressed30KiBofA),
+ std::end(kCompressed30KiBofA)));
+ brillo::Blob expected_data(30 * 1024, 'a');
+ EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+TEST_F(XzExtentWriterTest, GarbageDataRejected) {
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ // The sample_data_ is an uncompressed string.
+ EXPECT_FALSE(xz_writer_->Write(sample_data_.data(), sample_data_.size()));
+ EXPECT_TRUE(xz_writer_->End());
+
+ EXPECT_TRUE(fake_extent_writer_->EndCalled());
+}
+
+TEST_F(XzExtentWriterTest, PartialDataIsKept) {
+ brillo::Blob compressed(std::begin(kCompressed30KiBofA),
+ std::end(kCompressed30KiBofA));
+ EXPECT_TRUE(xz_writer_->Init(fd_, {}, 1024));
+ for (uint8_t byte : compressed) {
+ EXPECT_TRUE(xz_writer_->Write(&byte, 1));
+ }
+ EXPECT_TRUE(xz_writer_->End());
+
+ // The sample_data_ is an uncompressed string.
+ brillo::Blob expected_data(30 * 1024, 'a');
+ EXPECT_EQ(expected_data, fake_extent_writer_->WrittenData());
+}
+
+} // namespace chromeos_update_engine