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