Index: metadata.cc |
diff --git a/metadata.cc b/metadata.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..20c0a0d9b69bf2eb69a0f78b423fa5c69380abdc |
--- /dev/null |
+++ b/metadata.cc |
@@ -0,0 +1,482 @@ |
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <algorithm> |
+#include <string> |
+#include <vector> |
+ |
+#include <base/string_util.h> |
+#include <et/com_err.h> |
+#include <ext2fs/ext2_io.h> |
+#include <ext2fs/ext2fs.h> |
+ |
+#include "update_engine/bzip.h" |
+#include "update_engine/delta_diff_generator.h" |
+#include "update_engine/extent_ranges.h" |
+#include "update_engine/graph_utils.h" |
+#include "update_engine/metadata.h" |
+#include "update_engine/utils.h" |
+ |
+using std::min; |
+using std::string; |
+using std::vector; |
+ |
+namespace chromeos_update_engine { |
+ |
+namespace { |
+const size_t kBlockSize = 4096; |
+ |
+typedef DeltaDiffGenerator::Block Block; |
+ |
+// Read data from the specified extents. |
+bool ReadExtentsData(const ext2_filsys fs, |
+ const vector<Extent>& extents, |
+ vector<char>* data) { |
+ // Resize the data buffer to hold all data in the extents |
+ size_t num_data_blocks = 0; |
+ for (vector<Extent>::const_iterator it = extents.begin(); |
+ it != extents.end(); it++) { |
+ num_data_blocks += it->num_blocks(); |
+ } |
+ |
+ data->resize(num_data_blocks * kBlockSize); |
+ |
+ // Read in the data blocks |
+ const size_t kMaxReadBlocks = 256; |
+ vector<Block>::size_type blocks_copied_count = 0; |
+ for (vector<Extent>::const_iterator it = extents.begin(); |
+ it != extents.end(); it++) { |
+ vector<Block>::size_type blocks_read = 0; |
+ while (blocks_read < it->num_blocks()) { |
+ const int copy_block_cnt = |
+ min(kMaxReadBlocks, |
+ static_cast<size_t>( |
+ it->num_blocks() - blocks_read)); |
+ TEST_AND_RETURN_FALSE_ERRCODE( |
+ io_channel_read_blk(fs->io, |
+ it->start_block() + blocks_read, |
+ copy_block_cnt, |
+ &(*data)[blocks_copied_count * kBlockSize])); |
+ blocks_read += copy_block_cnt; |
+ blocks_copied_count += copy_block_cnt; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+// Compute the bsdiff between two metadata blobs. |
+bool ComputeMetadataBsdiff(const vector<char>& old_metadata, |
+ const vector<char>& new_metadata, |
+ vector<char>* bsdiff_delta) { |
+ const string kTempFileTemplate("/tmp/CrAU_temp_data.XXXXXX"); |
+ |
+ // Write the metadata buffers to temporary files |
+ int old_fd; |
+ string temp_old_file_path; |
+ TEST_AND_RETURN_FALSE( |
+ utils::MakeTempFile(kTempFileTemplate, &temp_old_file_path, &old_fd)); |
+ TEST_AND_RETURN_FALSE(old_fd >= 0); |
+ ScopedPathUnlinker temp_old_file_path_unlinker(temp_old_file_path); |
+ ScopedFdCloser old_fd_closer(&old_fd); |
+ TEST_AND_RETURN_FALSE(utils::WriteAll(old_fd, |
+ &old_metadata[0], |
+ old_metadata.size())); |
+ |
+ int new_fd; |
+ string temp_new_file_path; |
+ TEST_AND_RETURN_FALSE( |
+ utils::MakeTempFile(kTempFileTemplate, &temp_new_file_path, &new_fd)); |
+ TEST_AND_RETURN_FALSE(new_fd >= 0); |
+ ScopedPathUnlinker temp_new_file_path_unlinker(temp_new_file_path); |
+ ScopedFdCloser new_fd_closer(&new_fd); |
+ TEST_AND_RETURN_FALSE(utils::WriteAll(new_fd, |
+ &new_metadata[0], |
+ new_metadata.size())); |
+ |
+ // Perform bsdiff on these files |
+ TEST_AND_RETURN_FALSE( |
+ DeltaDiffGenerator::BsdiffFiles(temp_old_file_path, |
+ temp_new_file_path, |
+ bsdiff_delta)); |
+ |
+ return true; |
+} |
+ |
+// Add the specified metadata extents to the graph and blocks vector. |
+bool AddMetadataExtents(Graph* graph, |
+ vector<Block>* blocks, |
+ const ext2_filsys fs_old, |
+ const ext2_filsys fs_new, |
+ const string& metadata_name, |
+ const vector<Extent>& extents, |
+ int data_fd, |
+ off_t* data_file_size) { |
+ vector<char> data; // Data blob that will be written to delta file. |
+ DeltaArchiveManifest_InstallOperation op; |
+ |
+ { |
+ // Read in the metadata blocks from the old and new image. |
+ vector<char> old_data; |
+ TEST_AND_RETURN_FALSE(ReadExtentsData(fs_old, extents, &old_data)); |
+ |
+ vector<char> new_data; |
+ TEST_AND_RETURN_FALSE(ReadExtentsData(fs_new, extents, &new_data)); |
+ |
+ // Determine the best way to compress this. |
+ vector<char> new_data_bz; |
+ TEST_AND_RETURN_FALSE(BzipCompress(new_data, &new_data_bz)); |
+ CHECK(!new_data_bz.empty()); |
+ |
+ size_t current_best_size = 0; |
+ if (new_data.size() <= new_data_bz.size()) { |
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE); |
+ current_best_size = new_data.size(); |
+ data = new_data; |
+ } else { |
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_REPLACE_BZ); |
+ current_best_size = new_data_bz.size(); |
+ data = new_data_bz; |
+ } |
+ |
+ if (old_data == new_data) { |
+ // No change in data. |
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_MOVE); |
+ current_best_size = 0; |
+ data.clear(); |
+ } else { |
+ // Try bsdiff of old to new data |
+ vector<char> bsdiff_delta; |
+ TEST_AND_RETURN_FALSE(ComputeMetadataBsdiff(old_data, |
+ new_data, |
+ &bsdiff_delta)); |
+ CHECK_GT(bsdiff_delta.size(), 0); |
+ |
+ if (bsdiff_delta.size() < current_best_size) { |
+ op.set_type(DeltaArchiveManifest_InstallOperation_Type_BSDIFF); |
+ current_best_size = bsdiff_delta.size(); |
+ data = bsdiff_delta; |
+ } |
+ } |
+ |
+ CHECK_EQ(data.size(), current_best_size); |
+ |
+ // Set the source and dest extents to be the same since the filesystem |
+ // structures are identical |
+ if (op.type() == DeltaArchiveManifest_InstallOperation_Type_MOVE || |
+ op.type() == DeltaArchiveManifest_InstallOperation_Type_BSDIFF) { |
+ DeltaDiffGenerator::StoreExtents(extents, op.mutable_src_extents()); |
+ op.set_src_length(old_data.size()); |
+ } |
+ |
+ DeltaDiffGenerator::StoreExtents(extents, op.mutable_dst_extents()); |
+ op.set_dst_length(new_data.size()); |
+ } |
+ |
+ // Write data to output file |
+ if (op.type() != DeltaArchiveManifest_InstallOperation_Type_MOVE) { |
+ op.set_data_offset(*data_file_size); |
+ op.set_data_length(data.size()); |
+ } |
+ |
+ TEST_AND_RETURN_FALSE(utils::WriteAll(data_fd, &data[0], data.size())); |
+ *data_file_size += data.size(); |
+ |
+ // Now, insert into graph and blocks vector |
+ graph->resize(graph->size() + 1); |
+ Vertex::Index vertex = graph->size() - 1; |
+ (*graph)[vertex].op = op; |
+ CHECK((*graph)[vertex].op.has_type()); |
+ (*graph)[vertex].file_name = metadata_name; |
+ |
+ TEST_AND_RETURN_FALSE(DeltaDiffGenerator::AddInstallOpToBlocksVector( |
+ (*graph)[vertex].op, |
+ *graph, |
+ vertex, |
+ blocks)); |
+ |
+ return true; |
+} |
+ |
+// Reads the file system metadata extents. |
+bool ReadFilesystemMetadata(Graph* graph, |
+ vector<Block>* blocks, |
+ const ext2_filsys fs_old, |
+ const ext2_filsys fs_new, |
+ int data_fd, |
+ off_t* data_file_size) { |
+ LOG(INFO) << "Processing <rootfs-metadata>"; |
+ |
+ // Read all the extents that belong to the main file system metadata. |
+ // The metadata blocks are at the start of each block group and goes |
+ // until the end of the inode table. |
+ for (dgrp_t bg = 0; bg < fs_old->group_desc_count; bg++) { |
+ struct ext2_group_desc* group_desc = &fs_old->group_desc[bg]; |
+ __u32 num_metadata_blocks = (group_desc->bg_inode_table + |
+ fs_old->inode_blocks_per_group) - |
+ (bg * fs_old->super->s_blocks_per_group); |
+ __u32 bg_start_block = bg * fs_old->super->s_blocks_per_group; |
+ |
+ // Due to bsdiff slowness, we're going to break each block group down |
+ // into metadata chunks and feed them to bsdiff. |
+ __u32 num_chunks = 4; |
+ __u32 blocks_per_chunk = num_metadata_blocks / num_chunks; |
+ __u32 curr_block = bg_start_block; |
+ for (__u32 chunk = 0; chunk < num_chunks; chunk++) { |
+ Extent extent; |
+ if (chunk < num_chunks - 1) { |
+ extent = ExtentForRange(curr_block, blocks_per_chunk); |
+ } else { |
+ extent = ExtentForRange(curr_block, |
+ bg_start_block + num_metadata_blocks - |
+ curr_block); |
+ } |
+ |
+ vector<Extent> extents; |
+ extents.push_back(extent); |
+ |
+ string metadata_name = StringPrintf("<rootfs-bg-%d-%d-metadata>", |
+ bg, chunk); |
+ |
+ LOG(INFO) << "Processing " << metadata_name; |
+ |
+ TEST_AND_RETURN_FALSE(AddMetadataExtents(graph, |
+ blocks, |
+ fs_old, |
+ fs_new, |
+ metadata_name, |
+ extents, |
+ data_fd, |
+ data_file_size)); |
+ |
+ curr_block += blocks_per_chunk; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+// Processes all blocks belonging to an inode |
+int ProcessInodeAllBlocks(ext2_filsys fs, |
+ blk_t* blocknr, |
+ e2_blkcnt_t blockcnt, |
+ blk_t ref_blk, |
+ int ref_offset, |
+ void* priv) { |
+ vector<Extent>* extents = static_cast<vector<Extent>*>(priv); |
+ graph_utils::AppendBlockToExtents(extents, *blocknr); |
+ return 0; |
+} |
+ |
+// Processes only indirect, double indirect or triple indirect metadata |
+// blocks belonging to an inode |
+int ProcessInodeMetadataBlocks(ext2_filsys fs, |
+ blk_t* blocknr, |
+ e2_blkcnt_t blockcnt, |
+ blk_t ref_blk, |
+ int ref_offset, |
+ void* priv) { |
+ vector<Extent>* extents = static_cast<vector<Extent>*>(priv); |
+ if (blockcnt < 0) { |
+ graph_utils::AppendBlockToExtents(extents, *blocknr); |
+ } |
+ return 0; |
+} |
+ |
+// Read inode metadata blocks. |
+bool ReadInodeMetadata(Graph* graph, |
+ vector<Block>* blocks, |
+ const ext2_filsys fs_old, |
+ const ext2_filsys fs_new, |
+ int data_fd, |
+ off_t* data_file_size) { |
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(fs_old)); |
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(fs_new)); |
+ |
+ ext2_inode_scan iscan; |
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_open_inode_scan(fs_old, 0, &iscan)); |
+ |
+ ext2_ino_t ino; |
+ ext2_inode old_inode; |
+ while (true) { |
+ // Get the next inode on both file systems |
+ errcode_t error = ext2fs_get_next_inode(iscan, &ino, &old_inode); |
+ |
+ // If we get an error enumerating the inodes, we'll just log the error |
+ // and exit from our loop which will eventually return a success code |
+ // back to the caller. The inode blocks that we cannot account for will |
+ // be handled by DeltaDiffGenerator::ReadUnwrittenBlocks(). |
+ if (error) { |
+ LOG(ERROR) << "Failed to retrieve next inode (" << error << ")"; |
+ break; |
+ } |
+ |
+ if (ino == 0) { |
+ break; |
+ } |
+ |
+ if (ino == EXT2_RESIZE_INO) { |
+ continue; |
+ } |
+ |
+ ext2_inode new_inode; |
+ error = ext2fs_read_inode(fs_new, ino, &new_inode); |
+ if (error) { |
+ LOG(ERROR) << "Failed to retrieve new inode (" << error << ")"; |
+ continue; |
+ } |
+ |
+ // Skip inodes that are not in use |
+ if (!ext2fs_test_inode_bitmap(fs_old->inode_map, ino) || |
+ !ext2fs_test_inode_bitmap(fs_new->inode_map, ino)) { |
+ continue; |
+ } |
+ |
+ // Skip inodes that have no data blocks |
+ if (old_inode.i_blocks == 0 || new_inode.i_blocks == 0) { |
+ continue; |
+ } |
+ |
+ // Skip inodes that are not the same type |
+ bool is_old_dir = (ext2fs_check_directory(fs_old, ino) == 0); |
+ bool is_new_dir = (ext2fs_check_directory(fs_new, ino) == 0); |
+ if (is_old_dir != is_new_dir) { |
+ continue; |
+ } |
+ |
+ // Process the inodes metadata blocks |
+ // For normal files, metadata blocks are indirect, double indirect |
+ // and triple indirect blocks (no data blocks). For directories and |
+ // the journal, all blocks are considered metadata blocks. |
+ LOG(INFO) << "Processing inode " << ino << " metadata"; |
+ |
+ bool all_blocks = ((ino == EXT2_JOURNAL_INO) || is_old_dir || is_new_dir); |
+ |
+ vector<Extent> old_extents; |
+ error = ext2fs_block_iterate2(fs_old, ino, 0, NULL, |
+ all_blocks ? ProcessInodeAllBlocks : |
+ ProcessInodeMetadataBlocks, |
+ &old_extents); |
+ if (error) { |
+ LOG(ERROR) << "Failed to enumerate old inode " << ino |
+ << " blocks (" << error << ")"; |
+ continue; |
+ } |
+ |
+ vector<Extent> new_extents; |
+ error = ext2fs_block_iterate2(fs_new, ino, 0, NULL, |
+ all_blocks ? ProcessInodeAllBlocks : |
+ ProcessInodeMetadataBlocks, |
+ &new_extents); |
+ if (error) { |
+ LOG(ERROR) << "Failed to enumerate new inode " << ino |
+ << " blocks (" << error << ")"; |
+ continue; |
+ } |
+ |
+ // Skip inode if there are no metadata blocks |
+ if (old_extents.size() == 0 || new_extents.size() == 0) { |
+ continue; |
+ } |
+ |
+ // Make sure the two inodes have the same metadata blocks |
+ if (old_extents.size() != new_extents.size()) { |
+ continue; |
+ } |
+ |
+ bool same_metadata_extents = true; |
+ vector<Extent>::iterator it_old; |
+ vector<Extent>::iterator it_new; |
+ for (it_old = old_extents.begin(), |
+ it_new = new_extents.begin(); |
+ it_old != old_extents.end() && it_new != new_extents.end(); |
+ it_old++, it_new++) { |
+ if (it_old->start_block() != it_new->start_block() || |
+ it_old->num_blocks() != it_new->num_blocks()) { |
+ same_metadata_extents = false; |
+ break; |
+ } |
+ } |
+ |
+ if (!same_metadata_extents) { |
+ continue; |
+ } |
+ |
+ // We have identical inode metadata blocks, we can now add them to |
+ // our graph and blocks vector |
+ string metadata_name = StringPrintf("<rootfs-inode-%d-metadata>", ino); |
+ TEST_AND_RETURN_FALSE(AddMetadataExtents(graph, |
+ blocks, |
+ fs_old, |
+ fs_new, |
+ metadata_name, |
+ old_extents, |
+ data_fd, |
+ data_file_size)); |
+ } |
+ |
+ ext2fs_close_inode_scan(iscan); |
+ |
+ return true; |
+} |
+ |
+} // namespace {} |
+ |
+// Reads metadata from old image and new image and determines |
+// the smallest way to encode the metadata for the diff. |
+// If there's no change in the metadata, it creates a MOVE |
+// operation. If there is a change, the smallest of REPLACE, REPLACE_BZ, |
+// or BSDIFF wins. It writes the diff to data_fd and updates data_file_size |
+// accordingly. It also adds the required operation to the graph and adds the |
+// metadata extents to blocks. |
+// Returns true on success. |
+bool Metadata::DeltaReadMetadata(Graph* graph, |
+ vector<Block>* blocks, |
+ const string& old_image, |
+ const string& new_image, |
+ int data_fd, |
+ off_t* data_file_size) { |
+ // Open the two file systems. |
+ ext2_filsys fs_old; |
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_open(old_image.c_str(), 0, 0, 0, |
+ unix_io_manager, &fs_old)); |
+ ScopedExt2fsCloser fs_old_closer(fs_old); |
+ |
+ ext2_filsys fs_new; |
+ TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_open(new_image.c_str(), 0, 0, 0, |
+ unix_io_manager, &fs_new)); |
+ ScopedExt2fsCloser fs_new_closer(fs_new); |
+ |
+ // Make sure these two file systems are the same. |
+ // If they are not the same, the metadata blocks will be packaged up in its |
+ // entirety by ReadUnwrittenBlocks(). |
+ if (fs_old->blocksize != fs_new->blocksize || |
+ fs_old->fragsize != fs_new->fragsize || |
+ fs_old->group_desc_count != fs_new->group_desc_count || |
+ fs_old->inode_blocks_per_group != fs_new->inode_blocks_per_group || |
+ fs_old->super->s_inodes_count != fs_new->super->s_inodes_count || |
+ fs_old->super->s_blocks_count != fs_new->super->s_blocks_count) { |
+ return true; |
+ } |
+ |
+ // Process the main file system metadata (superblock, inode tables, etc) |
+ TEST_AND_RETURN_FALSE(ReadFilesystemMetadata(graph, |
+ blocks, |
+ fs_old, |
+ fs_new, |
+ data_fd, |
+ data_file_size)); |
+ |
+ // Process each inode metadata blocks. |
+ TEST_AND_RETURN_FALSE(ReadInodeMetadata(graph, |
+ blocks, |
+ fs_old, |
+ fs_new, |
+ data_fd, |
+ data_file_size)); |
+ |
+ return true; |
+} |
+ |
+}; // namespace chromeos_update_engine |