| Index: src/platform/update_engine/delta_performer.cc
|
| diff --git a/src/platform/update_engine/delta_performer.cc b/src/platform/update_engine/delta_performer.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..23e1c934b3a873244d39d431cc1cdfcbed4a0c37
|
| --- /dev/null
|
| +++ b/src/platform/update_engine/delta_performer.cc
|
| @@ -0,0 +1,377 @@
|
| +// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "update_engine/delta_performer.h"
|
| +#include <endian.h>
|
| +#include <errno.h>
|
| +#include <string.h>
|
| +#include <algorithm>
|
| +#include <string>
|
| +#include <vector>
|
| +
|
| +#include "base/scoped_ptr.h"
|
| +#include "base/string_util.h"
|
| +
|
| +#include <google/protobuf/repeated_field.h>
|
| +
|
| +#include "update_engine/bzip_extent_writer.h"
|
| +#include "update_engine/delta_diff_generator.h"
|
| +#include "update_engine/extent_writer.h"
|
| +#include "update_engine/graph_types.h"
|
| +#include "update_engine/subprocess.h"
|
| +
|
| +using std::min;
|
| +using std::string;
|
| +using std::vector;
|
| +using google::protobuf::RepeatedPtrField;
|
| +
|
| +namespace chromeos_update_engine {
|
| +
|
| +namespace {
|
| +
|
| +const int kDeltaVersionLength = 8;
|
| +const int kDeltaProtobufLengthLength = 8;
|
| +
|
| +// Remove count bytes from the beginning of *buffer.
|
| +void RemoveBufferHeadBytes(vector<char>* buffer, size_t count) {
|
| + buffer->erase(buffer->begin(), buffer->begin() + count);
|
| +}
|
| +
|
| +// Converts extents to a human-readable string, for use by DumpUpdateProto().
|
| +string ExtentsToString(const RepeatedPtrField<Extent>& extents) {
|
| + string ret;
|
| + for (int i = 0; i < extents.size(); i++) {
|
| + const Extent& extent = extents.Get(i);
|
| + if (extent.start_block() == kSparseHole) {
|
| + ret += StringPrintf("{kSparseHole, %" PRIu64 "}, ", extent.num_blocks());
|
| + } else {
|
| + ret += StringPrintf("{%" PRIu64 ", %" PRIu64 "}, ",
|
| + extent.start_block(), extent.num_blocks());
|
| + }
|
| + }
|
| + if (!ret.empty()) {
|
| + DCHECK_GT(ret.size(), static_cast<size_t>(1));
|
| + ret.resize(ret.size() - 2);
|
| + }
|
| + return ret;
|
| +}
|
| +
|
| +// LOGs a DeltaArchiveManifest object. Useful for debugging.
|
| +void DumpUpdateProto(const DeltaArchiveManifest& manifest) {
|
| + LOG(INFO) << "Update Proto:";
|
| + LOG(INFO) << " src_checksum: " << manifest.src_checksum();
|
| + LOG(INFO) << " dst_checksum: " << manifest.dst_checksum();
|
| + LOG(INFO) << " block_size: " << manifest.block_size();
|
| + for (int i = 0; i < manifest.install_operations_size(); i++) {
|
| + const DeltaArchiveManifest_InstallOperation& op =
|
| + manifest.install_operations(i);
|
| + LOG(INFO) << " operation(" << i << ")";
|
| + LOG(INFO) << " type: "
|
| + << DeltaArchiveManifest_InstallOperation_Type_Name(op.type());
|
| + if (op.has_data_offset())
|
| + LOG(INFO) << " data_offset: " << op.data_offset();
|
| + if (op.has_data_length())
|
| + LOG(INFO) << " data_length: " << op.data_length();
|
| + LOG(INFO) << " src_extents: " << ExtentsToString(op.src_extents());
|
| + if (op.has_src_length())
|
| + LOG(INFO) << " src_length: " << op.src_length();
|
| + LOG(INFO) << " dst_extents: " << ExtentsToString(op.dst_extents());
|
| + if (op.has_dst_length())
|
| + LOG(INFO) << " dst_length: " << op.dst_length();
|
| + }
|
| +}
|
| +} // namespace {}
|
| +
|
| +int DeltaPerformer::Open(const char* path, int flags, mode_t mode) {
|
| + if (fd_ != -1) {
|
| + LOG(ERROR) << "Can't Open(), fd_ != -1 (it's " << fd_ << ")";
|
| + return -EINVAL;
|
| + }
|
| + path_ = path;
|
| + fd_ = open(path, O_RDWR, 000);
|
| + if (fd_ < 0)
|
| + return -errno;
|
| + return 0;
|
| +}
|
| +
|
| +int DeltaPerformer::Close() {
|
| + if (!buffer_.empty()) {
|
| + LOG(ERROR) << "Called Close() while buffer not empty!";
|
| + return -1;
|
| + }
|
| + if (close(fd_) == -1)
|
| + return -errno;
|
| + fd_ = -2; // Set so that isn't not valid AND calls to Open() will fail.
|
| + path_ = "";
|
| + return 0;
|
| +}
|
| +
|
| +// Wrapper around write. Returns bytes written on success or
|
| +// -errno on error.
|
| +// This function performs as many actions as it can, given the amount of
|
| +// data received thus far.
|
| +int DeltaPerformer::Write(const void* bytes, size_t count) {
|
| + const char* c_bytes = reinterpret_cast<const char*>(bytes);
|
| + buffer_.insert(buffer_.end(), c_bytes, c_bytes + count);
|
| +
|
| + if (!manifest_valid_) {
|
| + // See if we have enough bytes for the manifest yet
|
| + if (buffer_.size() < strlen(kDeltaMagic) +
|
| + kDeltaVersionLength + kDeltaProtobufLengthLength) {
|
| + // Don't have enough bytes to even know the protobuf length
|
| + return count;
|
| + }
|
| + uint64_t protobuf_length;
|
| + COMPILE_ASSERT(sizeof(protobuf_length) == kDeltaProtobufLengthLength,
|
| + protobuf_length_size_mismatch);
|
| + memcpy(&protobuf_length,
|
| + &buffer_[strlen(kDeltaMagic) + kDeltaVersionLength],
|
| + kDeltaProtobufLengthLength);
|
| + protobuf_length = be64toh(protobuf_length); // switch big endian to host
|
| + if (buffer_.size() < strlen(kDeltaMagic) + kDeltaVersionLength +
|
| + kDeltaProtobufLengthLength + protobuf_length) {
|
| + return count;
|
| + }
|
| + // We have the full proto buffer in buffer_. Parse it.
|
| + const int offset = strlen(kDeltaMagic) + kDeltaVersionLength +
|
| + kDeltaProtobufLengthLength;
|
| + if (!manifest_.ParseFromArray(&buffer_[offset], protobuf_length)) {
|
| + LOG(ERROR) << "Unable to parse manifest in update file.";
|
| + return -EINVAL;
|
| + }
|
| + // Remove protobuf and header info from buffer_, so buffer_ contains
|
| + // just data blobs
|
| + RemoveBufferHeadBytes(&buffer_,
|
| + strlen(kDeltaMagic) +
|
| + kDeltaVersionLength +
|
| + kDeltaProtobufLengthLength + protobuf_length);
|
| + manifest_valid_ = true;
|
| + block_size_ = manifest_.block_size();
|
| + }
|
| + while (next_operation_ < manifest_.install_operations_size() &&
|
| + CanPerformInstallOperation(
|
| + manifest_.install_operations(next_operation_))) {
|
| + const DeltaArchiveManifest_InstallOperation &op =
|
| + manifest_.install_operations(next_operation_);
|
| + if (op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
|
| + op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
|
| + if (!PerformReplaceOperation(op)) {
|
| + LOG(ERROR) << "Failed to perform replace operation " << next_operation_;
|
| + return -EINVAL;
|
| + }
|
| + } else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
|
| + if (!PerformMoveOperation(op)) {
|
| + LOG(ERROR) << "Failed to perform move operation " << next_operation_;
|
| + return -EINVAL;
|
| + }
|
| + } else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF) {
|
| + if (!PerformBsdiffOperation(op)) {
|
| + LOG(ERROR) << "Failed to perform bsdiff operation " << next_operation_;
|
| + return -EINVAL;
|
| + }
|
| + }
|
| + next_operation_++;
|
| + }
|
| + return count;
|
| +}
|
| +
|
| +bool DeltaPerformer::CanPerformInstallOperation(
|
| + const chromeos_update_engine::DeltaArchiveManifest_InstallOperation&
|
| + operation) {
|
| + // Move operations don't require any data blob, so they can always
|
| + // be performed
|
| + if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE)
|
| + 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 DeltaArchiveManifest_InstallOperation& operation) {
|
| + CHECK(operation.type() == \
|
| + DeltaArchiveManifest_InstallOperation_Type_REPLACE || \
|
| + operation.type() == \
|
| + DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
|
| +
|
| + // 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.
|
| + CHECK_EQ(buffer_offset_, operation.data_offset());
|
| + CHECK_GE(buffer_.size(), operation.data_length());
|
| +
|
| + DirectExtentWriter direct_writer;
|
| + ZeroPadExtentWriter zero_pad_writer(&direct_writer);
|
| + scoped_ptr<BzipExtentWriter> bzip_writer;
|
| +
|
| + // Since bzip decompression is optional, we have a variable writer that will
|
| + // point to one of the ExtentWriter objects above.
|
| + ExtentWriter* writer = NULL;
|
| + if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
|
| + writer = &zero_pad_writer;
|
| + } else if (operation.type() ==
|
| + DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
|
| + bzip_writer.reset(new BzipExtentWriter(&zero_pad_writer));
|
| + writer = bzip_writer.get();
|
| + } else {
|
| + NOTREACHED();
|
| + }
|
| +
|
| + // 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(fd_, extents, block_size_));
|
| + TEST_AND_RETURN_FALSE(writer->Write(&buffer_[0], operation.data_length()));
|
| + TEST_AND_RETURN_FALSE(writer->End());
|
| +
|
| + // Update buffer
|
| + buffer_offset_ += operation.data_length();
|
| + RemoveBufferHeadBytes(&buffer_, operation.data_length());
|
| + return true;
|
| +}
|
| +
|
| +bool DeltaPerformer::PerformMoveOperation(
|
| + const DeltaArchiveManifest_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);
|
| + vector<char> 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);
|
| + TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
|
| + &buf[bytes_read],
|
| + extent.num_blocks() * block_size_,
|
| + extent.start_block() * block_size_,
|
| + &bytes_read_this_iteration));
|
| + TEST_AND_RETURN_FALSE(
|
| + bytes_read_this_iteration ==
|
| + static_cast<ssize_t>(extent.num_blocks() * block_size_));
|
| + 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);
|
| + TEST_AND_RETURN_FALSE(utils::PWriteAll(fd_,
|
| + &buf[bytes_written],
|
| + extent.num_blocks() * block_size_,
|
| + extent.start_block() * block_size_));
|
| + bytes_written += extent.num_blocks() * block_size_;
|
| + }
|
| + DCHECK_EQ(bytes_written, bytes_read);
|
| + DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.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();
|
| + uint64_t this_length = min(full_length - length,
|
| + extent.num_blocks() * block_size);
|
| + if (start == static_cast<int64_t>(kSparseHole))
|
| + start = -1;
|
| + else
|
| + start *= block_size;
|
| + ret += 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 DeltaArchiveManifest_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.
|
| + CHECK_EQ(buffer_offset_, operation.data_offset());
|
| + CHECK_GE(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("/tmp/au_patch.XXXXXX",
|
| + &temp_filename,
|
| + NULL));
|
| + 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_[0], operation.data_length()));
|
| + }
|
| + vector<string> cmd;
|
| + cmd.push_back(kBspatchPath);
|
| + cmd.push_back(path_);
|
| + cmd.push_back(path_);
|
| + cmd.push_back(temp_filename);
|
| + cmd.push_back(input_positions);
|
| + cmd.push_back(output_positions);
|
| + int return_code = 0;
|
| + TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code));
|
| + 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_);
|
| + vector<char> zeros(end_byte - begin_byte);
|
| + TEST_AND_RETURN_FALSE(
|
| + utils::PWriteAll(fd_, &zeros[0], end_byte - begin_byte, begin_byte));
|
| + }
|
| +
|
| + // Update buffer.
|
| + buffer_offset_ += operation.data_length();
|
| + RemoveBufferHeadBytes(&buffer_, operation.data_length());
|
| + return true;
|
| +}
|
| +
|
| +} // namespace chromeos_update_engine
|
|
|