200 lines
6.8 KiB
C++
200 lines
6.8 KiB
C++
//
|
|
// 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_generator/full_update_generator.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <inttypes.h>
|
|
|
|
#include <algorithm>
|
|
#include <deque>
|
|
#include <memory>
|
|
|
|
#include <base/format_macros.h>
|
|
#include <base/strings/string_util.h>
|
|
#include <base/strings/stringprintf.h>
|
|
#include <base/synchronization/lock.h>
|
|
#include <base/threading/simple_thread.h>
|
|
#include <brillo/secure_blob.h>
|
|
|
|
#include "update_engine/common/utils.h"
|
|
#include "update_engine/payload_generator/delta_diff_utils.h"
|
|
|
|
using std::vector;
|
|
|
|
namespace chromeos_update_engine {
|
|
|
|
namespace {
|
|
|
|
const size_t kDefaultFullChunkSize = 1024 * 1024; // 1 MiB
|
|
|
|
// This class encapsulates a full update chunk processing thread work. The
|
|
// processor reads a chunk of data from the input file descriptor and compresses
|
|
// it. The processor will destroy itself when the work is done.
|
|
class ChunkProcessor : public base::DelegateSimpleThread::Delegate {
|
|
public:
|
|
// Read a chunk of |size| bytes from |fd| starting at offset |offset|.
|
|
ChunkProcessor(const PayloadVersion& version,
|
|
int fd,
|
|
off_t offset,
|
|
size_t size,
|
|
BlobFileWriter* blob_file,
|
|
AnnotatedOperation* aop)
|
|
: version_(version),
|
|
fd_(fd),
|
|
offset_(offset),
|
|
size_(size),
|
|
blob_file_(blob_file),
|
|
aop_(aop) {}
|
|
// We use a default move constructor since all the data members are POD types.
|
|
ChunkProcessor(ChunkProcessor&&) = default;
|
|
~ChunkProcessor() override = default;
|
|
|
|
// Overrides DelegateSimpleThread::Delegate.
|
|
// Run() handles the read from |fd| in a thread-safe way, and stores the
|
|
// new operation to generate the region starting at |offset| of size |size|
|
|
// in the output operation |aop|. The associated blob data is stored in
|
|
// |blob_fd| and |blob_file_size| is updated.
|
|
void Run() override;
|
|
|
|
private:
|
|
bool ProcessChunk();
|
|
|
|
// Work parameters.
|
|
const PayloadVersion& version_;
|
|
int fd_;
|
|
off_t offset_;
|
|
size_t size_;
|
|
BlobFileWriter* blob_file_;
|
|
AnnotatedOperation* aop_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ChunkProcessor);
|
|
};
|
|
|
|
void ChunkProcessor::Run() {
|
|
if (!ProcessChunk()) {
|
|
LOG(ERROR) << "Error processing region at " << offset_ << " of size "
|
|
<< size_;
|
|
}
|
|
}
|
|
|
|
bool ChunkProcessor::ProcessChunk() {
|
|
brillo::Blob buffer_in_(size_);
|
|
brillo::Blob op_blob;
|
|
ssize_t bytes_read = -1;
|
|
TEST_AND_RETURN_FALSE(utils::PReadAll(
|
|
fd_, buffer_in_.data(), buffer_in_.size(), offset_, &bytes_read));
|
|
TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(size_));
|
|
|
|
InstallOperation::Type op_type;
|
|
TEST_AND_RETURN_FALSE(diff_utils::GenerateBestFullOperation(
|
|
buffer_in_, version_, &op_blob, &op_type));
|
|
|
|
aop_->op.set_type(op_type);
|
|
TEST_AND_RETURN_FALSE(aop_->SetOperationBlob(op_blob, blob_file_));
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool FullUpdateGenerator::GenerateOperations(
|
|
const PayloadGenerationConfig& config,
|
|
const PartitionConfig& old_part,
|
|
const PartitionConfig& new_part,
|
|
BlobFileWriter* blob_file,
|
|
vector<AnnotatedOperation>* aops) {
|
|
TEST_AND_RETURN_FALSE(new_part.ValidateExists());
|
|
|
|
// FullUpdateGenerator requires a positive chunk_size, otherwise there will
|
|
// be only one operation with the whole partition which should not be allowed.
|
|
// For performance reasons, we force a small default hard limit of 1 MiB. This
|
|
// limit can be changed in the config, and we will use the smaller of the two
|
|
// soft/hard limits.
|
|
size_t full_chunk_size;
|
|
if (config.hard_chunk_size >= 0) {
|
|
full_chunk_size = std::min(static_cast<size_t>(config.hard_chunk_size),
|
|
config.soft_chunk_size);
|
|
} else {
|
|
full_chunk_size = std::min(kDefaultFullChunkSize, config.soft_chunk_size);
|
|
LOG(INFO) << "No chunk_size provided, using the default chunk_size for the "
|
|
<< "full operations: " << full_chunk_size << " bytes.";
|
|
}
|
|
TEST_AND_RETURN_FALSE(full_chunk_size > 0);
|
|
TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0);
|
|
|
|
size_t chunk_blocks = full_chunk_size / config.block_size;
|
|
size_t max_threads = diff_utils::GetMaxThreads();
|
|
LOG(INFO) << "Compressing partition " << new_part.name << " from "
|
|
<< new_part.path << " splitting in chunks of " << chunk_blocks
|
|
<< " blocks (" << config.block_size << " bytes each) using "
|
|
<< max_threads << " threads";
|
|
|
|
int in_fd = open(new_part.path.c_str(), O_RDONLY, 0);
|
|
TEST_AND_RETURN_FALSE(in_fd >= 0);
|
|
ScopedFdCloser in_fd_closer(&in_fd);
|
|
|
|
// We potentially have all the ChunkProcessors in memory but only
|
|
// |max_threads| will actually hold a block in memory while we process.
|
|
size_t partition_blocks = new_part.size / config.block_size;
|
|
size_t num_chunks = utils::DivRoundUp(partition_blocks, chunk_blocks);
|
|
aops->resize(num_chunks);
|
|
vector<ChunkProcessor> chunk_processors;
|
|
chunk_processors.reserve(num_chunks);
|
|
blob_file->IncTotalBlobs(num_chunks);
|
|
|
|
for (size_t i = 0; i < num_chunks; ++i) {
|
|
size_t start_block = i * chunk_blocks;
|
|
// The last chunk could be smaller.
|
|
size_t num_blocks =
|
|
std::min(chunk_blocks, partition_blocks - i * chunk_blocks);
|
|
|
|
// Preset all the static information about the operations. The
|
|
// ChunkProcessor will set the rest.
|
|
AnnotatedOperation* aop = aops->data() + i;
|
|
aop->name = base::StringPrintf(
|
|
"<%s-operation-%" PRIuS ">", new_part.name.c_str(), i);
|
|
Extent* dst_extent = aop->op.add_dst_extents();
|
|
dst_extent->set_start_block(start_block);
|
|
dst_extent->set_num_blocks(num_blocks);
|
|
|
|
chunk_processors.emplace_back(
|
|
config.version,
|
|
in_fd,
|
|
static_cast<off_t>(start_block) * config.block_size,
|
|
num_blocks * config.block_size,
|
|
blob_file,
|
|
aop);
|
|
}
|
|
|
|
// Thread pool used for worker threads.
|
|
base::DelegateSimpleThreadPool thread_pool("full-update-generator",
|
|
max_threads);
|
|
thread_pool.Start();
|
|
for (ChunkProcessor& processor : chunk_processors)
|
|
thread_pool.AddWork(&processor);
|
|
thread_pool.JoinAll();
|
|
|
|
// All the operations must have a type set at this point. Otherwise, a
|
|
// ChunkProcessor failed to complete.
|
|
for (const AnnotatedOperation& aop : *aops) {
|
|
if (!aop.op.has_type())
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace chromeos_update_engine
|