blob: d0ab82c97b06f0d881f5db3aa77ca090d690dc2b [file] [log] [blame]
Kees Cookf4aa1b12012-02-15 16:09:10 -08001// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Thieu Le5c7d9752010-12-15 16:09:28 -08002// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <algorithm>
6#include <string>
7#include <vector>
8
Alex Vakulenko75039d72014-03-25 12:36:28 -07009#include <base/strings/string_util.h>
10#include <base/strings/stringprintf.h>
Thieu Le5c7d9752010-12-15 16:09:28 -080011#include <et/com_err.h>
12#include <ext2fs/ext2_io.h>
13#include <ext2fs/ext2fs.h>
14
15#include "update_engine/bzip.h"
Thieu Le5c7d9752010-12-15 16:09:28 -080016#include "update_engine/extent_ranges.h"
Alex Deymo161c4a12014-05-16 15:56:21 -070017#include "update_engine/payload_generator/delta_diff_generator.h"
18#include "update_engine/payload_generator/graph_utils.h"
19#include "update_engine/payload_generator/metadata.h"
Thieu Le5c7d9752010-12-15 16:09:28 -080020#include "update_engine/utils.h"
21
Alex Deymo161c4a12014-05-16 15:56:21 -070022using base::StringPrintf;
Thieu Le5c7d9752010-12-15 16:09:28 -080023using std::min;
24using std::string;
25using std::vector;
26
27namespace chromeos_update_engine {
28
29namespace {
30const size_t kBlockSize = 4096;
31
32typedef DeltaDiffGenerator::Block Block;
33
34// Read data from the specified extents.
35bool ReadExtentsData(const ext2_filsys fs,
36 const vector<Extent>& extents,
37 vector<char>* data) {
38 // Resize the data buffer to hold all data in the extents
39 size_t num_data_blocks = 0;
40 for (vector<Extent>::const_iterator it = extents.begin();
41 it != extents.end(); it++) {
42 num_data_blocks += it->num_blocks();
43 }
44
45 data->resize(num_data_blocks * kBlockSize);
46
47 // Read in the data blocks
48 const size_t kMaxReadBlocks = 256;
49 vector<Block>::size_type blocks_copied_count = 0;
50 for (vector<Extent>::const_iterator it = extents.begin();
51 it != extents.end(); it++) {
52 vector<Block>::size_type blocks_read = 0;
53 while (blocks_read < it->num_blocks()) {
54 const int copy_block_cnt =
55 min(kMaxReadBlocks,
56 static_cast<size_t>(
57 it->num_blocks() - blocks_read));
58 TEST_AND_RETURN_FALSE_ERRCODE(
59 io_channel_read_blk(fs->io,
60 it->start_block() + blocks_read,
61 copy_block_cnt,
62 &(*data)[blocks_copied_count * kBlockSize]));
63 blocks_read += copy_block_cnt;
64 blocks_copied_count += copy_block_cnt;
65 }
66 }
67
68 return true;
69}
70
71// Compute the bsdiff between two metadata blobs.
72bool ComputeMetadataBsdiff(const vector<char>& old_metadata,
73 const vector<char>& new_metadata,
74 vector<char>* bsdiff_delta) {
Gilad Arnolda6742b32014-01-11 00:18:34 -080075 const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
Thieu Le5c7d9752010-12-15 16:09:28 -080076
77 // Write the metadata buffers to temporary files
78 int old_fd;
79 string temp_old_file_path;
80 TEST_AND_RETURN_FALSE(
81 utils::MakeTempFile(kTempFileTemplate, &temp_old_file_path, &old_fd));
82 TEST_AND_RETURN_FALSE(old_fd >= 0);
83 ScopedPathUnlinker temp_old_file_path_unlinker(temp_old_file_path);
84 ScopedFdCloser old_fd_closer(&old_fd);
85 TEST_AND_RETURN_FALSE(utils::WriteAll(old_fd,
86 &old_metadata[0],
87 old_metadata.size()));
88
89 int new_fd;
90 string temp_new_file_path;
91 TEST_AND_RETURN_FALSE(
92 utils::MakeTempFile(kTempFileTemplate, &temp_new_file_path, &new_fd));
93 TEST_AND_RETURN_FALSE(new_fd >= 0);
94 ScopedPathUnlinker temp_new_file_path_unlinker(temp_new_file_path);
95 ScopedFdCloser new_fd_closer(&new_fd);
96 TEST_AND_RETURN_FALSE(utils::WriteAll(new_fd,
97 &new_metadata[0],
98 new_metadata.size()));
99
100 // Perform bsdiff on these files
101 TEST_AND_RETURN_FALSE(
102 DeltaDiffGenerator::BsdiffFiles(temp_old_file_path,
103 temp_new_file_path,
104 bsdiff_delta));
105
106 return true;
107}
108
109// Add the specified metadata extents to the graph and blocks vector.
110bool AddMetadataExtents(Graph* graph,
111 vector<Block>* blocks,
112 const ext2_filsys fs_old,
113 const ext2_filsys fs_new,
114 const string& metadata_name,
115 const vector<Extent>& extents,
116 int data_fd,
117 off_t* data_file_size) {
118 vector<char> data; // Data blob that will be written to delta file.
119 DeltaArchiveManifest_InstallOperation op;
120
121 {
122 // Read in the metadata blocks from the old and new image.
123 vector<char> old_data;
124 TEST_AND_RETURN_FALSE(ReadExtentsData(fs_old, extents, &old_data));
125
126 vector<char> new_data;
127 TEST_AND_RETURN_FALSE(ReadExtentsData(fs_new, extents, &new_data));
128
129 // Determine the best way to compress this.
130 vector<char> new_data_bz;
131 TEST_AND_RETURN_FALSE(BzipCompress(new_data, &new_data_bz));
132 CHECK(!new_data_bz.empty());
133
134 size_t current_best_size = 0;
135 if (new_data.size() <= new_data_bz.size()) {
136 op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE);
137 current_best_size = new_data.size();
138 data = new_data;
139 } else {
140 op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ);
141 current_best_size = new_data_bz.size();
142 data = new_data_bz;
143 }
144
145 if (old_data == new_data) {
146 // No change in data.
147 op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE);
148 current_best_size = 0;
149 data.clear();
150 } else {
151 // Try bsdiff of old to new data
152 vector<char> bsdiff_delta;
153 TEST_AND_RETURN_FALSE(ComputeMetadataBsdiff(old_data,
154 new_data,
155 &bsdiff_delta));
Mike Frysinger0f9547d2012-02-16 12:11:37 -0500156 CHECK_GT(bsdiff_delta.size(), static_cast<vector<char>::size_type>(0));
Thieu Le5c7d9752010-12-15 16:09:28 -0800157
158 if (bsdiff_delta.size() < current_best_size) {
159 op.set_type(DeltaArchiveManifest_InstallOperation_Type_BSDIFF);
160 current_best_size = bsdiff_delta.size();
161 data = bsdiff_delta;
162 }
163 }
164
165 CHECK_EQ(data.size(), current_best_size);
166
167 // Set the source and dest extents to be the same since the filesystem
168 // structures are identical
169 if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE ||
170 op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF) {
171 DeltaDiffGenerator::StoreExtents(extents, op.mutable_src_extents());
172 op.set_src_length(old_data.size());
173 }
174
175 DeltaDiffGenerator::StoreExtents(extents, op.mutable_dst_extents());
176 op.set_dst_length(new_data.size());
177 }
178
179 // Write data to output file
180 if (op.type() != DeltaArchiveManifest_InstallOperation_Type_MOVE) {
181 op.set_data_offset(*data_file_size);
182 op.set_data_length(data.size());
183 }
184
185 TEST_AND_RETURN_FALSE(utils::WriteAll(data_fd, &data[0], data.size()));
186 *data_file_size += data.size();
187
188 // Now, insert into graph and blocks vector
189 graph->resize(graph->size() + 1);
190 Vertex::Index vertex = graph->size() - 1;
191 (*graph)[vertex].op = op;
192 CHECK((*graph)[vertex].op.has_type());
193 (*graph)[vertex].file_name = metadata_name;
194
195 TEST_AND_RETURN_FALSE(DeltaDiffGenerator::AddInstallOpToBlocksVector(
196 (*graph)[vertex].op,
197 *graph,
198 vertex,
199 blocks));
200
201 return true;
202}
203
204// Reads the file system metadata extents.
205bool ReadFilesystemMetadata(Graph* graph,
206 vector<Block>* blocks,
207 const ext2_filsys fs_old,
208 const ext2_filsys fs_new,
209 int data_fd,
210 off_t* data_file_size) {
211 LOG(INFO) << "Processing <rootfs-metadata>";
212
213 // Read all the extents that belong to the main file system metadata.
214 // The metadata blocks are at the start of each block group and goes
215 // until the end of the inode table.
216 for (dgrp_t bg = 0; bg < fs_old->group_desc_count; bg++) {
Kees Cookf4aa1b12012-02-15 16:09:10 -0800217 struct ext2_group_desc* group_desc = ext2fs_group_desc(fs_old,
218 fs_old->group_desc,
219 bg);
Thieu Le5c7d9752010-12-15 16:09:28 -0800220 __u32 num_metadata_blocks = (group_desc->bg_inode_table +
221 fs_old->inode_blocks_per_group) -
222 (bg * fs_old->super->s_blocks_per_group);
223 __u32 bg_start_block = bg * fs_old->super->s_blocks_per_group;
224
225 // Due to bsdiff slowness, we're going to break each block group down
226 // into metadata chunks and feed them to bsdiff.
Thieu Lef6502192011-01-05 14:34:22 -0800227 __u32 num_chunks = 10;
Thieu Le5c7d9752010-12-15 16:09:28 -0800228 __u32 blocks_per_chunk = num_metadata_blocks / num_chunks;
229 __u32 curr_block = bg_start_block;
230 for (__u32 chunk = 0; chunk < num_chunks; chunk++) {
231 Extent extent;
232 if (chunk < num_chunks - 1) {
233 extent = ExtentForRange(curr_block, blocks_per_chunk);
234 } else {
235 extent = ExtentForRange(curr_block,
236 bg_start_block + num_metadata_blocks -
237 curr_block);
238 }
239
240 vector<Extent> extents;
241 extents.push_back(extent);
242
243 string metadata_name = StringPrintf("<rootfs-bg-%d-%d-metadata>",
244 bg, chunk);
245
246 LOG(INFO) << "Processing " << metadata_name;
247
248 TEST_AND_RETURN_FALSE(AddMetadataExtents(graph,
249 blocks,
250 fs_old,
251 fs_new,
252 metadata_name,
253 extents,
254 data_fd,
255 data_file_size));
256
257 curr_block += blocks_per_chunk;
258 }
259 }
260
261 return true;
262}
263
264// Processes all blocks belonging to an inode
265int ProcessInodeAllBlocks(ext2_filsys fs,
266 blk_t* blocknr,
267 e2_blkcnt_t blockcnt,
268 blk_t ref_blk,
269 int ref_offset,
270 void* priv) {
271 vector<Extent>* extents = static_cast<vector<Extent>*>(priv);
272 graph_utils::AppendBlockToExtents(extents, *blocknr);
273 return 0;
274}
275
276// Processes only indirect, double indirect or triple indirect metadata
277// blocks belonging to an inode
278int ProcessInodeMetadataBlocks(ext2_filsys fs,
279 blk_t* blocknr,
280 e2_blkcnt_t blockcnt,
281 blk_t ref_blk,
282 int ref_offset,
283 void* priv) {
284 vector<Extent>* extents = static_cast<vector<Extent>*>(priv);
285 if (blockcnt < 0) {
286 graph_utils::AppendBlockToExtents(extents, *blocknr);
287 }
288 return 0;
289}
290
291// Read inode metadata blocks.
292bool ReadInodeMetadata(Graph* graph,
293 vector<Block>* blocks,
294 const ext2_filsys fs_old,
295 const ext2_filsys fs_new,
296 int data_fd,
297 off_t* data_file_size) {
298 TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(fs_old));
299 TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(fs_new));
300
301 ext2_inode_scan iscan;
302 TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_open_inode_scan(fs_old, 0, &iscan));
303
304 ext2_ino_t ino;
305 ext2_inode old_inode;
306 while (true) {
307 // Get the next inode on both file systems
308 errcode_t error = ext2fs_get_next_inode(iscan, &ino, &old_inode);
309
310 // If we get an error enumerating the inodes, we'll just log the error
311 // and exit from our loop which will eventually return a success code
312 // back to the caller. The inode blocks that we cannot account for will
313 // be handled by DeltaDiffGenerator::ReadUnwrittenBlocks().
314 if (error) {
315 LOG(ERROR) << "Failed to retrieve next inode (" << error << ")";
316 break;
317 }
318
319 if (ino == 0) {
320 break;
321 }
322
323 if (ino == EXT2_RESIZE_INO) {
324 continue;
325 }
326
327 ext2_inode new_inode;
328 error = ext2fs_read_inode(fs_new, ino, &new_inode);
329 if (error) {
330 LOG(ERROR) << "Failed to retrieve new inode (" << error << ")";
331 continue;
332 }
333
334 // Skip inodes that are not in use
335 if (!ext2fs_test_inode_bitmap(fs_old->inode_map, ino) ||
336 !ext2fs_test_inode_bitmap(fs_new->inode_map, ino)) {
337 continue;
338 }
339
340 // Skip inodes that have no data blocks
341 if (old_inode.i_blocks == 0 || new_inode.i_blocks == 0) {
342 continue;
343 }
344
345 // Skip inodes that are not the same type
346 bool is_old_dir = (ext2fs_check_directory(fs_old, ino) == 0);
347 bool is_new_dir = (ext2fs_check_directory(fs_new, ino) == 0);
348 if (is_old_dir != is_new_dir) {
349 continue;
350 }
351
352 // Process the inodes metadata blocks
353 // For normal files, metadata blocks are indirect, double indirect
354 // and triple indirect blocks (no data blocks). For directories and
355 // the journal, all blocks are considered metadata blocks.
356 LOG(INFO) << "Processing inode " << ino << " metadata";
357
358 bool all_blocks = ((ino == EXT2_JOURNAL_INO) || is_old_dir || is_new_dir);
359
360 vector<Extent> old_extents;
361 error = ext2fs_block_iterate2(fs_old, ino, 0, NULL,
362 all_blocks ? ProcessInodeAllBlocks :
363 ProcessInodeMetadataBlocks,
364 &old_extents);
365 if (error) {
366 LOG(ERROR) << "Failed to enumerate old inode " << ino
367 << " blocks (" << error << ")";
368 continue;
369 }
370
371 vector<Extent> new_extents;
372 error = ext2fs_block_iterate2(fs_new, ino, 0, NULL,
373 all_blocks ? ProcessInodeAllBlocks :
374 ProcessInodeMetadataBlocks,
375 &new_extents);
376 if (error) {
377 LOG(ERROR) << "Failed to enumerate new inode " << ino
378 << " blocks (" << error << ")";
379 continue;
380 }
381
382 // Skip inode if there are no metadata blocks
383 if (old_extents.size() == 0 || new_extents.size() == 0) {
384 continue;
385 }
386
387 // Make sure the two inodes have the same metadata blocks
388 if (old_extents.size() != new_extents.size()) {
389 continue;
390 }
391
392 bool same_metadata_extents = true;
393 vector<Extent>::iterator it_old;
394 vector<Extent>::iterator it_new;
395 for (it_old = old_extents.begin(),
396 it_new = new_extents.begin();
397 it_old != old_extents.end() && it_new != new_extents.end();
398 it_old++, it_new++) {
399 if (it_old->start_block() != it_new->start_block() ||
400 it_old->num_blocks() != it_new->num_blocks()) {
401 same_metadata_extents = false;
402 break;
403 }
404 }
405
406 if (!same_metadata_extents) {
407 continue;
408 }
409
410 // We have identical inode metadata blocks, we can now add them to
411 // our graph and blocks vector
412 string metadata_name = StringPrintf("<rootfs-inode-%d-metadata>", ino);
413 TEST_AND_RETURN_FALSE(AddMetadataExtents(graph,
414 blocks,
415 fs_old,
416 fs_new,
417 metadata_name,
418 old_extents,
419 data_fd,
420 data_file_size));
421 }
422
423 ext2fs_close_inode_scan(iscan);
424
425 return true;
426}
427
Alex Vakulenkod2779df2014-06-16 13:19:00 -0700428} // namespace
Thieu Le5c7d9752010-12-15 16:09:28 -0800429
430// Reads metadata from old image and new image and determines
431// the smallest way to encode the metadata for the diff.
432// If there's no change in the metadata, it creates a MOVE
433// operation. If there is a change, the smallest of REPLACE, REPLACE_BZ,
434// or BSDIFF wins. It writes the diff to data_fd and updates data_file_size
435// accordingly. It also adds the required operation to the graph and adds the
436// metadata extents to blocks.
437// Returns true on success.
438bool Metadata::DeltaReadMetadata(Graph* graph,
439 vector<Block>* blocks,
440 const string& old_image,
441 const string& new_image,
442 int data_fd,
443 off_t* data_file_size) {
444 // Open the two file systems.
445 ext2_filsys fs_old;
446 TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_open(old_image.c_str(), 0, 0, 0,
447 unix_io_manager, &fs_old));
448 ScopedExt2fsCloser fs_old_closer(fs_old);
449
450 ext2_filsys fs_new;
451 TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_open(new_image.c_str(), 0, 0, 0,
452 unix_io_manager, &fs_new));
453 ScopedExt2fsCloser fs_new_closer(fs_new);
454
455 // Make sure these two file systems are the same.
456 // If they are not the same, the metadata blocks will be packaged up in its
457 // entirety by ReadUnwrittenBlocks().
458 if (fs_old->blocksize != fs_new->blocksize ||
459 fs_old->fragsize != fs_new->fragsize ||
460 fs_old->group_desc_count != fs_new->group_desc_count ||
461 fs_old->inode_blocks_per_group != fs_new->inode_blocks_per_group ||
462 fs_old->super->s_inodes_count != fs_new->super->s_inodes_count ||
463 fs_old->super->s_blocks_count != fs_new->super->s_blocks_count) {
464 return true;
465 }
466
467 // Process the main file system metadata (superblock, inode tables, etc)
468 TEST_AND_RETURN_FALSE(ReadFilesystemMetadata(graph,
469 blocks,
470 fs_old,
471 fs_new,
472 data_fd,
473 data_file_size));
474
475 // Process each inode metadata blocks.
476 TEST_AND_RETURN_FALSE(ReadInodeMetadata(graph,
477 blocks,
478 fs_old,
479 fs_new,
480 data_fd,
481 data_file_size));
482
483 return true;
484}
485
486}; // namespace chromeos_update_engine