Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(771)

Side by Side Diff: src/platform/update_engine/delta_performer.cc

Issue 1718001: AU: Class to perform delta updates. (Closed)
Patch Set: fixes for review Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2010 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/delta_performer.h"
6 #include <endian.h>
7 #include <errno.h>
8 #include <string.h>
9 #include <algorithm>
10 #include <string>
11 #include <vector>
12
13 #include "base/scoped_ptr.h"
14 #include "base/string_util.h"
15
16 #include <google/protobuf/repeated_field.h>
17
18 #include "update_engine/bzip_extent_writer.h"
19 #include "update_engine/delta_diff_generator.h"
20 #include "update_engine/extent_writer.h"
21 #include "update_engine/graph_types.h"
22 #include "update_engine/subprocess.h"
23
24 using std::min;
25 using std::string;
26 using std::vector;
27 using google::protobuf::RepeatedPtrField;
28
29 namespace chromeos_update_engine {
30
31 namespace {
32
33 const int kDeltaVersionLength = 8;
34 const int kDeltaProtobufLengthLength = 8;
35
36 // Remove count bytes from the beginning of *buffer.
37 void RemoveBufferHeadBytes(vector<char>* buffer, size_t count) {
38 buffer->erase(buffer->begin(), buffer->begin() + count);
39 }
40
41 // Converts extents to a human-readable string, for use by DumpUpdateProto().
42 string ExtentsToString(const RepeatedPtrField<Extent>& extents) {
43 string ret;
44 for (int i = 0; i < extents.size(); i++) {
45 const Extent& extent = extents.Get(i);
46 if (extent.start_block() == kSparseHole) {
47 ret += StringPrintf("{kSparseHole, %" PRIu64 "}, ", extent.num_blocks());
48 } else {
49 ret += StringPrintf("{%" PRIu64 ", %" PRIu64 "}, ",
50 extent.start_block(), extent.num_blocks());
51 }
52 }
53 if (!ret.empty()) {
54 DCHECK_GT(ret.size(), static_cast<size_t>(1));
55 ret.resize(ret.size() - 2);
56 }
57 return ret;
58 }
59
60 // LOGs a DeltaArchiveManifest object. Useful for debugging.
61 void DumpUpdateProto(const DeltaArchiveManifest& manifest) {
62 LOG(INFO) << "Update Proto:";
63 LOG(INFO) << " src_checksum: " << manifest.src_checksum();
64 LOG(INFO) << " dst_checksum: " << manifest.dst_checksum();
65 LOG(INFO) << " block_size: " << manifest.block_size();
66 for (int i = 0; i < manifest.install_operations_size(); i++) {
67 const DeltaArchiveManifest_InstallOperation& op =
68 manifest.install_operations(i);
69 LOG(INFO) << " operation(" << i << ")";
70 LOG(INFO) << " type: "
71 << DeltaArchiveManifest_InstallOperation_Type_Name(op.type());
72 if (op.has_data_offset())
73 LOG(INFO) << " data_offset: " << op.data_offset();
74 if (op.has_data_length())
75 LOG(INFO) << " data_length: " << op.data_length();
76 LOG(INFO) << " src_extents: " << ExtentsToString(op.src_extents());
77 if (op.has_src_length())
78 LOG(INFO) << " src_length: " << op.src_length();
79 LOG(INFO) << " dst_extents: " << ExtentsToString(op.dst_extents());
80 if (op.has_dst_length())
81 LOG(INFO) << " dst_length: " << op.dst_length();
82 }
83 }
84 } // namespace {}
85
86 int DeltaPerformer::Open(const char* path, int flags, mode_t mode) {
87 if (fd_ != -1) {
88 LOG(ERROR) << "Can't Open(), fd_ != -1 (it's " << fd_ << ")";
89 return -EINVAL;
90 }
91 path_ = path;
92 fd_ = open(path, O_RDWR, 000);
93 if (fd_ < 0)
94 return -errno;
95 return 0;
96 }
97
98 int DeltaPerformer::Close() {
99 if (!buffer_.empty()) {
100 LOG(ERROR) << "Called Close() while buffer not empty!";
101 return -1;
102 }
103 if (close(fd_) == -1)
104 return -errno;
105 fd_ = -2; // Set so that isn't not valid AND calls to Open() will fail.
106 path_ = "";
107 return 0;
108 }
109
110 // Wrapper around write. Returns bytes written on success or
111 // -errno on error.
112 // This function performs as many actions as it can, given the amount of
113 // data received thus far.
114 int DeltaPerformer::Write(const void* bytes, size_t count) {
115 const char* c_bytes = reinterpret_cast<const char*>(bytes);
116 buffer_.insert(buffer_.end(), c_bytes, c_bytes + count);
117
118 if (!manifest_valid_) {
119 // See if we have enough bytes for the manifest yet
120 if (buffer_.size() < strlen(kDeltaMagic) +
121 kDeltaVersionLength + kDeltaProtobufLengthLength) {
122 // Don't have enough bytes to even know the protobuf length
123 return count;
124 }
125 uint64_t protobuf_length;
126 COMPILE_ASSERT(sizeof(protobuf_length) == kDeltaProtobufLengthLength,
127 protobuf_length_size_mismatch);
128 memcpy(&protobuf_length,
129 &buffer_[strlen(kDeltaMagic) + kDeltaVersionLength],
130 kDeltaProtobufLengthLength);
131 protobuf_length = be64toh(protobuf_length); // switch big endian to host
132 if (buffer_.size() < strlen(kDeltaMagic) + kDeltaVersionLength +
133 kDeltaProtobufLengthLength + protobuf_length) {
134 return count;
135 }
136 // We have the full proto buffer in buffer_. Parse it.
137 const int offset = strlen(kDeltaMagic) + kDeltaVersionLength +
138 kDeltaProtobufLengthLength;
139 if (!manifest_.ParseFromArray(&buffer_[offset], protobuf_length)) {
140 LOG(ERROR) << "Unable to parse manifest in update file.";
141 return -EINVAL;
142 }
143 // Remove protobuf and header info from buffer_, so buffer_ contains
144 // just data blobs
145 RemoveBufferHeadBytes(&buffer_,
146 strlen(kDeltaMagic) +
147 kDeltaVersionLength +
148 kDeltaProtobufLengthLength + protobuf_length);
149 manifest_valid_ = true;
150 block_size_ = manifest_.block_size();
151 }
152 while (next_operation_ < manifest_.install_operations_size() &&
153 CanPerformInstallOperation(
154 manifest_.install_operations(next_operation_))) {
155 const DeltaArchiveManifest_InstallOperation &op =
156 manifest_.install_operations(next_operation_);
157 if (op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE ||
158 op.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
159 if (!PerformReplaceOperation(op)) {
160 LOG(ERROR) << "Failed to perform replace operation " << next_operation_;
161 return -EINVAL;
162 }
163 } else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE) {
164 if (!PerformMoveOperation(op)) {
165 LOG(ERROR) << "Failed to perform move operation " << next_operation_;
166 return -EINVAL;
167 }
168 } else if (op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF) {
169 if (!PerformBsdiffOperation(op)) {
170 LOG(ERROR) << "Failed to perform bsdiff operation " << next_operation_;
171 return -EINVAL;
172 }
173 }
174 next_operation_++;
175 }
176 return count;
177 }
178
179 bool DeltaPerformer::CanPerformInstallOperation(
180 const chromeos_update_engine::DeltaArchiveManifest_InstallOperation&
181 operation) {
182 // Move operations don't require any data blob, so they can always
183 // be performed
184 if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE)
185 return true;
186
187 // See if we have the entire data blob in the buffer
188 if (operation.data_offset() < buffer_offset_) {
189 LOG(ERROR) << "we threw away data it seems?";
190 return false;
191 }
192
193 return (operation.data_offset() + operation.data_length()) <=
194 (buffer_offset_ + buffer_.size());
195 }
196
197 bool DeltaPerformer::PerformReplaceOperation(
198 const DeltaArchiveManifest_InstallOperation& operation) {
199 CHECK(operation.type() == \
200 DeltaArchiveManifest_InstallOperation_Type_REPLACE || \
201 operation.type() == \
202 DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
203
204 // Since we delete data off the beginning of the buffer as we use it,
205 // the data we need should be exactly at the beginning of the buffer.
206 CHECK_EQ(buffer_offset_, operation.data_offset());
207 CHECK_GE(buffer_.size(), operation.data_length());
208
209 DirectExtentWriter direct_writer;
210 ZeroPadExtentWriter zero_pad_writer(&direct_writer);
211 scoped_ptr<BzipExtentWriter> bzip_writer;
212
213 // Since bzip decompression is optional, we have a variable writer that will
214 // point to one of the ExtentWriter objects above.
215 ExtentWriter* writer = NULL;
216 if (operation.type() == DeltaArchiveManifest_InstallOperation_Type_REPLACE) {
217 writer = &zero_pad_writer;
218 } else if (operation.type() ==
219 DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ) {
220 bzip_writer.reset(new BzipExtentWriter(&zero_pad_writer));
221 writer = bzip_writer.get();
222 } else {
223 NOTREACHED();
224 }
225
226 // Create a vector of extents to pass to the ExtentWriter.
227 vector<Extent> extents;
228 for (int i = 0; i < operation.dst_extents_size(); i++) {
229 extents.push_back(operation.dst_extents(i));
230 }
231
232 TEST_AND_RETURN_FALSE(writer->Init(fd_, extents, block_size_));
233 TEST_AND_RETURN_FALSE(writer->Write(&buffer_[0], operation.data_length()));
234 TEST_AND_RETURN_FALSE(writer->End());
235
236 // Update buffer
237 buffer_offset_ += operation.data_length();
238 RemoveBufferHeadBytes(&buffer_, operation.data_length());
239 return true;
240 }
241
242 bool DeltaPerformer::PerformMoveOperation(
243 const DeltaArchiveManifest_InstallOperation& operation) {
244 // Calculate buffer size. Note, this function doesn't do a sliding
245 // window to copy in case the source and destination blocks overlap.
246 // If we wanted to do a sliding window, we could program the server
247 // to generate deltas that effectively did a sliding window.
248
249 uint64_t blocks_to_read = 0;
250 for (int i = 0; i < operation.src_extents_size(); i++)
251 blocks_to_read += operation.src_extents(i).num_blocks();
252
253 uint64_t blocks_to_write = 0;
254 for (int i = 0; i < operation.dst_extents_size(); i++)
255 blocks_to_write += operation.dst_extents(i).num_blocks();
256
257 DCHECK_EQ(blocks_to_write, blocks_to_read);
258 vector<char> buf(blocks_to_write * block_size_);
259
260 // Read in bytes.
261 ssize_t bytes_read = 0;
262 for (int i = 0; i < operation.src_extents_size(); i++) {
263 ssize_t bytes_read_this_iteration = 0;
264 const Extent& extent = operation.src_extents(i);
265 TEST_AND_RETURN_FALSE(utils::PReadAll(fd_,
266 &buf[bytes_read],
267 extent.num_blocks() * block_size_,
268 extent.start_block() * block_size_,
269 &bytes_read_this_iteration));
270 TEST_AND_RETURN_FALSE(
271 bytes_read_this_iteration ==
272 static_cast<ssize_t>(extent.num_blocks() * block_size_));
273 bytes_read += bytes_read_this_iteration;
274 }
275
276 // Write bytes out.
277 ssize_t bytes_written = 0;
278 for (int i = 0; i < operation.dst_extents_size(); i++) {
279 const Extent& extent = operation.dst_extents(i);
280 TEST_AND_RETURN_FALSE(utils::PWriteAll(fd_,
281 &buf[bytes_written],
282 extent.num_blocks() * block_size_,
283 extent.start_block() * block_size_));
284 bytes_written += extent.num_blocks() * block_size_;
285 }
286 DCHECK_EQ(bytes_written, bytes_read);
287 DCHECK_EQ(bytes_written, static_cast<ssize_t>(buf.size()));
288 return true;
289 }
290
291 bool DeltaPerformer::ExtentsToBsdiffPositionsString(
292 const RepeatedPtrField<Extent>& extents,
293 uint64_t block_size,
294 uint64_t full_length,
295 string* positions_string) {
296 string ret;
297 uint64_t length = 0;
298 for (int i = 0; i < extents.size(); i++) {
299 Extent extent = extents.Get(i);
300 int64_t start = extent.start_block();
301 uint64_t this_length = min(full_length - length,
302 extent.num_blocks() * block_size);
303 if (start == static_cast<int64_t>(kSparseHole))
304 start = -1;
305 else
306 start *= block_size;
307 ret += StringPrintf("%" PRIi64 ":%" PRIu64 ",", start, this_length);
308 length += this_length;
309 }
310 TEST_AND_RETURN_FALSE(length == full_length);
311 if (!ret.empty())
312 ret.resize(ret.size() - 1); // Strip trailing comma off
313 *positions_string = ret;
314 return true;
315 }
316
317 bool DeltaPerformer::PerformBsdiffOperation(
318 const DeltaArchiveManifest_InstallOperation& operation) {
319 // Since we delete data off the beginning of the buffer as we use it,
320 // the data we need should be exactly at the beginning of the buffer.
321 CHECK_EQ(buffer_offset_, operation.data_offset());
322 CHECK_GE(buffer_.size(), operation.data_length());
323
324 string input_positions;
325 TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.src_extents(),
326 block_size_,
327 operation.src_length(),
328 &input_positions));
329 string output_positions;
330 TEST_AND_RETURN_FALSE(ExtentsToBsdiffPositionsString(operation.dst_extents(),
331 block_size_,
332 operation.dst_length(),
333 &output_positions));
334
335 string temp_filename;
336 TEST_AND_RETURN_FALSE(utils::MakeTempFile("/tmp/au_patch.XXXXXX",
337 &temp_filename,
338 NULL));
339 ScopedPathUnlinker path_unlinker(temp_filename);
340 {
341 int fd = open(temp_filename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
342 ScopedFdCloser fd_closer(&fd);
343 TEST_AND_RETURN_FALSE(
344 utils::WriteAll(fd, &buffer_[0], operation.data_length()));
345 }
346 vector<string> cmd;
347 cmd.push_back(kBspatchPath);
348 cmd.push_back(path_);
349 cmd.push_back(path_);
350 cmd.push_back(temp_filename);
351 cmd.push_back(input_positions);
352 cmd.push_back(output_positions);
353 int return_code = 0;
354 TEST_AND_RETURN_FALSE(Subprocess::SynchronousExec(cmd, &return_code));
355 TEST_AND_RETURN_FALSE(return_code == 0);
356
357 if (operation.dst_length() % block_size_) {
358 // Zero out rest of final block.
359 // TODO(adlr): build this into bspatch; it's more efficient that way.
360 const Extent& last_extent =
361 operation.dst_extents(operation.dst_extents_size() - 1);
362 const uint64_t end_byte =
363 (last_extent.start_block() + last_extent.num_blocks()) * block_size_;
364 const uint64_t begin_byte =
365 end_byte - (block_size_ - operation.dst_length() % block_size_);
366 vector<char> zeros(end_byte - begin_byte);
367 TEST_AND_RETURN_FALSE(
368 utils::PWriteAll(fd_, &zeros[0], end_byte - begin_byte, begin_byte));
369 }
370
371 // Update buffer.
372 buffer_offset_ += operation.data_length();
373 RemoveBufferHeadBytes(&buffer_, operation.data_length());
374 return true;
375 }
376
377 } // namespace chromeos_update_engine
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698