full_update_generator.cc revision 8cc502dacbccdab96824d42287f230ce04004784
1// Copyright (c) 2012 The Chromium OS Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "update_engine/payload_generator/full_update_generator.h" 6 7#include <fcntl.h> 8#include <inttypes.h> 9 10#include <algorithm> 11#include <deque> 12#include <memory> 13 14#include <base/format_macros.h> 15#include <base/strings/string_util.h> 16#include <base/strings/stringprintf.h> 17#include <base/synchronization/lock.h> 18#include <base/threading/simple_thread.h> 19#include <chromeos/secure_blob.h> 20 21#include "update_engine/bzip.h" 22#include "update_engine/utils.h" 23 24using std::string; 25using std::vector; 26 27namespace chromeos_update_engine { 28 29namespace { 30 31const size_t kDefaultFullChunkSize = 1024 * 1024; // 1 MiB 32 33// This class encapsulates a full update chunk processing thread work. The 34// processor reads a chunk of data from the input file descriptor and compresses 35// it. The processor will destroy itself when the work is done. 36class ChunkProcessor : public base::DelegateSimpleThread::Delegate { 37 public: 38 // Read a chunk of |size| bytes from |fd| starting at offset |offset|. 39 ChunkProcessor(int fd, off_t offset, size_t size, 40 BlobFileWriter* blob_file, AnnotatedOperation* aop) 41 : fd_(fd), 42 offset_(offset), 43 size_(size), 44 blob_file_(blob_file), 45 aop_(aop) {} 46 // We use a default move constructor since all the data members are POD types. 47 ChunkProcessor(ChunkProcessor&&) = default; 48 ~ChunkProcessor() override = default; 49 50 // Overrides DelegateSimpleThread::Delegate. 51 // Run() handles the read from |fd| in a thread-safe way, and stores the 52 // new operation to generate the region starting at |offset| of size |size| 53 // in the output operation |aop|. The associated blob data is stored in 54 // |blob_fd| and |blob_file_size| is updated. 55 void Run() override; 56 57 private: 58 bool ProcessChunk(); 59 60 // Stores the operation blob in the |blob_fd_| and updates the 61 // |blob_file_size| accordingly. 62 // This method is thread-safe since it uses a mutex to access the file. 63 // Returns the data offset where the data was written to. 64 off_t StoreBlob(const chromeos::Blob& blob); 65 66 // Work parameters. 67 int fd_; 68 off_t offset_; 69 size_t size_; 70 BlobFileWriter* blob_file_; 71 AnnotatedOperation* aop_; 72 73 DISALLOW_COPY_AND_ASSIGN(ChunkProcessor); 74}; 75 76void ChunkProcessor::Run() { 77 if (!ProcessChunk()) { 78 LOG(ERROR) << "Error processing region at " << offset_ << " of size " 79 << size_; 80 } 81} 82 83bool ChunkProcessor::ProcessChunk() { 84 chromeos::Blob buffer_in_(size_); 85 chromeos::Blob op_blob; 86 ssize_t bytes_read = -1; 87 TEST_AND_RETURN_FALSE(utils::PReadAll(fd_, 88 buffer_in_.data(), 89 buffer_in_.size(), 90 offset_, 91 &bytes_read)); 92 TEST_AND_RETURN_FALSE(bytes_read == static_cast<ssize_t>(size_)); 93 TEST_AND_RETURN_FALSE(BzipCompress(buffer_in_, &op_blob)); 94 95 DeltaArchiveManifest_InstallOperation_Type op_type = 96 DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ; 97 98 if (op_blob.size() >= buffer_in_.size()) { 99 // A REPLACE is cheaper than a REPLACE_BZ in this case. 100 op_blob = std::move(buffer_in_); 101 op_type = DeltaArchiveManifest_InstallOperation_Type_REPLACE; 102 } 103 104 off_t op_offset = blob_file_->StoreBlob(op_blob); 105 TEST_AND_RETURN_FALSE(op_offset >= 0); 106 aop_->op.set_data_offset(op_offset); 107 aop_->op.set_data_length(op_blob.size()); 108 aop_->op.set_type(op_type); 109 return true; 110} 111 112} // namespace 113 114bool FullUpdateGenerator::GenerateOperations( 115 const PayloadGenerationConfig& config, 116 BlobFileWriter* blob_file, 117 vector<AnnotatedOperation>* rootfs_ops, 118 vector<AnnotatedOperation>* kernel_ops) { 119 TEST_AND_RETURN_FALSE(config.Validate()); 120 121 // FullUpdateGenerator requires a positive chunk_size, otherwise there will 122 // be only one operation with the whole partition which should not be allowed. 123 // For performance reasons, we force a small default hard limit of 1 MiB. This 124 // limit can be changed in the config, and we will use the smaller of the two 125 // soft/hard limits. 126 size_t full_chunk_size; 127 if (config.hard_chunk_size >= 0) { 128 full_chunk_size = std::min(static_cast<size_t>(config.hard_chunk_size), 129 config.soft_chunk_size); 130 } else { 131 full_chunk_size = std::min(kDefaultFullChunkSize, config.soft_chunk_size); 132 LOG(INFO) << "No chunk_size provided, using the default chunk_size for the " 133 << "full operations: " << full_chunk_size << " bytes."; 134 } 135 TEST_AND_RETURN_FALSE(full_chunk_size > 0); 136 TEST_AND_RETURN_FALSE(full_chunk_size % config.block_size == 0); 137 138 TEST_AND_RETURN_FALSE(GenerateOperationsForPartition( 139 config.target.rootfs, 140 config.block_size, 141 full_chunk_size / config.block_size, 142 blob_file, 143 rootfs_ops)); 144 TEST_AND_RETURN_FALSE(GenerateOperationsForPartition( 145 config.target.kernel, 146 config.block_size, 147 full_chunk_size / config.block_size, 148 blob_file, 149 kernel_ops)); 150 return true; 151} 152 153bool FullUpdateGenerator::GenerateOperationsForPartition( 154 const PartitionConfig& new_part, 155 size_t block_size, 156 size_t chunk_blocks, 157 BlobFileWriter* blob_file, 158 vector<AnnotatedOperation>* aops) { 159 size_t max_threads = std::max(sysconf(_SC_NPROCESSORS_ONLN), 4L); 160 LOG(INFO) << "Compressing partition " << PartitionNameString(new_part.name) 161 << " from " << new_part.path << " splitting in chunks of " 162 << chunk_blocks << " blocks (" << block_size 163 << " bytes each) using " << max_threads << " threads"; 164 165 int in_fd = open(new_part.path.c_str(), O_RDONLY, 0); 166 TEST_AND_RETURN_FALSE(in_fd >= 0); 167 ScopedFdCloser in_fd_closer(&in_fd); 168 169 // We potentially have all the ChunkProcessors in memory but only 170 // |max_threads| will actually hold a block in memory while we process. 171 size_t partition_blocks = new_part.size / block_size; 172 size_t num_chunks = (partition_blocks + chunk_blocks - 1) / chunk_blocks; 173 aops->resize(num_chunks); 174 vector<ChunkProcessor> chunk_processors; 175 chunk_processors.reserve(num_chunks); 176 blob_file->SetTotalBlobs(num_chunks); 177 178 const string part_name_str = PartitionNameString(new_part.name); 179 180 for (size_t i = 0; i < num_chunks; ++i) { 181 size_t start_block = i * chunk_blocks; 182 // The last chunk could be smaller. 183 size_t num_blocks = std::min(chunk_blocks, 184 partition_blocks - i * chunk_blocks); 185 186 // Preset all the static information about the operations. The 187 // ChunkProcessor will set the rest. 188 AnnotatedOperation* aop = aops->data() + i; 189 aop->name = base::StringPrintf("<%s-operation-%" PRIuS ">", 190 part_name_str.c_str(), i); 191 Extent* dst_extent = aop->op.add_dst_extents(); 192 dst_extent->set_start_block(start_block); 193 dst_extent->set_num_blocks(num_blocks); 194 195 chunk_processors.emplace_back( 196 in_fd, 197 static_cast<off_t>(start_block) * block_size, 198 num_blocks * block_size, 199 blob_file, 200 aop); 201 } 202 203 // Thread pool used for worker threads. 204 base::DelegateSimpleThreadPool thread_pool("full-update-generator", 205 max_threads); 206 thread_pool.Start(); 207 for (ChunkProcessor& processor : chunk_processors) 208 thread_pool.AddWork(&processor); 209 thread_pool.JoinAll(); 210 // All the operations must have a type set at this point. Otherwise, a 211 // ChunkProcessor failed to complete. 212 for (const AnnotatedOperation& aop : *aops) { 213 if (!aop.op.has_type()) 214 return false; 215 } 216 return true; 217} 218 219} // namespace chromeos_update_engine 220