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