ext2_filesystem.cc revision 3f39d5cc753905874d8d93bef94f857b8808f19e
1// 2// Copyright (C) 2015 The Android Open Source Project 3// 4// Licensed under the Apache License, Version 2.0 (the "License"); 5// you may not use this file except in compliance with the License. 6// You may obtain a copy of the License at 7// 8// http://www.apache.org/licenses/LICENSE-2.0 9// 10// Unless required by applicable law or agreed to in writing, software 11// distributed under the License is distributed on an "AS IS" BASIS, 12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13// See the License for the specific language governing permissions and 14// limitations under the License. 15// 16 17#include "update_engine/payload_generator/ext2_filesystem.h" 18 19#include <et/com_err.h> 20#include <ext2fs/ext2_io.h> 21#include <ext2fs/ext2fs.h> 22 23#include <map> 24#include <set> 25 26#include <base/logging.h> 27#include <base/strings/stringprintf.h> 28 29#include "update_engine/payload_generator/extent_ranges.h" 30#include "update_engine/payload_generator/extent_utils.h" 31#include "update_engine/update_metadata.pb.h" 32#include "update_engine/utils.h" 33 34using std::set; 35using std::string; 36using std::unique_ptr; 37using std::vector; 38 39namespace chromeos_update_engine { 40 41namespace { 42// Processes all blocks belonging to an inode and adds them to the extent list. 43// This function should match the prototype expected by ext2fs_block_iterate2(). 44int ProcessInodeAllBlocks(ext2_filsys fs, 45 blk_t* blocknr, 46 e2_blkcnt_t blockcnt, 47 blk_t ref_blk, 48 int ref_offset, 49 void* priv) { 50 vector<Extent>* extents = static_cast<vector<Extent>*>(priv); 51 AppendBlockToExtents(extents, *blocknr); 52 return 0; 53} 54 55// Processes only indirect, double indirect or triple indirect metadata 56// blocks belonging to an inode. This function should match the prototype of 57// ext2fs_block_iterate2(). 58int AddMetadataBlocks(ext2_filsys fs, 59 blk_t* blocknr, 60 e2_blkcnt_t blockcnt, 61 blk_t ref_blk, 62 int ref_offset, 63 void* priv) { 64 set<uint64_t>* blocks = static_cast<set<uint64_t>*>(priv); 65 // If |blockcnt| is non-negative, |blocknr| points to the physical block 66 // number. 67 // If |blockcnt| is negative, it is one of the values: BLOCK_COUNT_IND, 68 // BLOCK_COUNT_DIND, BLOCK_COUNT_TIND or BLOCK_COUNT_TRANSLATOR and 69 // |blocknr| points to a block in the first three cases. The last case is 70 // only used by GNU Hurd, so we shouldn't see those cases here. 71 if (blockcnt == BLOCK_COUNT_IND || blockcnt == BLOCK_COUNT_DIND || 72 blockcnt == BLOCK_COUNT_TIND) { 73 blocks->insert(*blocknr); 74 } 75 return 0; 76} 77 78struct UpdateFileAndAppendState { 79 std::map<ext2_ino_t, FilesystemInterface::File>* inodes = nullptr; 80 set<ext2_ino_t>* used_inodes = nullptr; 81 vector<FilesystemInterface::File>* files = nullptr; 82 ext2_filsys filsys; 83}; 84 85int UpdateFileAndAppend(ext2_ino_t dir, 86 int entry, 87 struct ext2_dir_entry *dirent, 88 int offset, 89 int blocksize, 90 char *buf, 91 void *priv_data) { 92 UpdateFileAndAppendState* state = 93 static_cast<UpdateFileAndAppendState*>(priv_data); 94 uint32_t file_type = dirent->name_len >> 8; 95 // Directories can't have hard links, and they are added from the outer loop. 96 if (file_type == EXT2_FT_DIR) 97 return 0; 98 99 auto ino_file = state->inodes->find(dirent->inode); 100 if (ino_file == state->inodes->end()) 101 return 0; 102 auto dir_file = state->inodes->find(dir); 103 if (dir_file == state->inodes->end()) 104 return 0; 105 string basename(dirent->name, dirent->name_len & 0xff); 106 ino_file->second.name = dir_file->second.name; 107 if (dir_file->second.name != "/") 108 ino_file->second.name += "/"; 109 ino_file->second.name += basename; 110 111 // Append this file to the output. If the file has a hard link, it will be 112 // added twice to the output, but with different names, which is ok. That will 113 // help identify all the versions of the same file. 114 state->files->push_back(ino_file->second); 115 state->used_inodes->insert(dirent->inode); 116 return 0; 117} 118 119} // namespace 120 121unique_ptr<Ext2Filesystem> Ext2Filesystem::CreateFromFile( 122 const string& filename) { 123 if (filename.empty()) 124 return nullptr; 125 unique_ptr<Ext2Filesystem> result(new Ext2Filesystem()); 126 result->filename_ = filename; 127 128 errcode_t err = ext2fs_open(filename.c_str(), 129 0, // flags (read only) 130 0, // superblock block number 131 0, // block_size (autodetect) 132 unix_io_manager, 133 &result->filsys_); 134 if (err) { 135 LOG(ERROR) << "Opening ext2fs " << filename; 136 return nullptr; 137 } 138 return result; 139} 140 141Ext2Filesystem::~Ext2Filesystem() { 142 ext2fs_free(filsys_); 143} 144 145size_t Ext2Filesystem::GetBlockSize() const { 146 return filsys_->blocksize; 147} 148 149size_t Ext2Filesystem::GetBlockCount() const { 150 return ext2fs_blocks_count(filsys_->super); 151} 152 153bool Ext2Filesystem::GetFiles(vector<File>* files) const { 154 TEST_AND_RETURN_FALSE_ERRCODE(ext2fs_read_inode_bitmap(filsys_)); 155 156 ext2_inode_scan iscan; 157 TEST_AND_RETURN_FALSE_ERRCODE( 158 ext2fs_open_inode_scan(filsys_, 0 /* buffer_blocks */, &iscan)); 159 160 std::map<ext2_ino_t, File> inodes; 161 162 // List of directories. We need to first parse all the files in a directory 163 // to later fix the absolute paths. 164 vector<ext2_ino_t> directories; 165 166 set<uint64_t> inode_blocks; 167 168 // Iterator 169 ext2_ino_t it_ino; 170 ext2_inode it_inode; 171 172 bool ok = true; 173 while (true) { 174 errcode_t error = ext2fs_get_next_inode(iscan, &it_ino, &it_inode); 175 if (error) { 176 LOG(ERROR) << "Failed to retrieve next inode (" << error << ")"; 177 ok = false; 178 break; 179 } 180 if (it_ino == 0) 181 break; 182 183 // Skip inodes that are not in use. 184 if (!ext2fs_test_inode_bitmap(filsys_->inode_map, it_ino)) 185 continue; 186 187 File& file = inodes[it_ino]; 188 if (it_ino == EXT2_RESIZE_INO) { 189 file.name = "<group-descriptors>"; 190 } else { 191 file.name = base::StringPrintf("<inode-%u>", it_ino); 192 } 193 194 memset(&file.file_stat, 0, sizeof(file.file_stat)); 195 file.file_stat.st_ino = it_ino; 196 file.file_stat.st_mode = it_inode.i_mode; 197 file.file_stat.st_nlink = it_inode.i_links_count; 198 file.file_stat.st_uid = it_inode.i_uid; 199 file.file_stat.st_gid = it_inode.i_gid; 200 file.file_stat.st_size = it_inode.i_size; 201 file.file_stat.st_blksize = filsys_->blocksize; 202 file.file_stat.st_blocks = it_inode.i_blocks; 203 file.file_stat.st_atime = it_inode.i_atime; 204 file.file_stat.st_mtime = it_inode.i_mtime; 205 file.file_stat.st_ctime = it_inode.i_ctime; 206 207 bool is_dir = (ext2fs_check_directory(filsys_, it_ino) == 0); 208 if (is_dir) 209 directories.push_back(it_ino); 210 211 if (!ext2fs_inode_has_valid_blocks(&it_inode)) 212 continue; 213 214 // Process the inode data and metadata blocks. 215 // For normal files, inode blocks are indirect, double indirect 216 // and triple indirect blocks (no data blocks). For directories and 217 // the journal, all blocks are considered metadata blocks. 218 int flags = it_ino < EXT2_GOOD_OLD_FIRST_INO ? 0 : BLOCK_FLAG_DATA_ONLY; 219 error = ext2fs_block_iterate2(filsys_, it_ino, flags, 220 nullptr, // block_buf 221 ProcessInodeAllBlocks, 222 &file.extents); 223 224 if (error) { 225 LOG(ERROR) << "Failed to enumerate inode " << it_ino 226 << " blocks (" << error << ")"; 227 continue; 228 } 229 if (it_ino >= EXT2_GOOD_OLD_FIRST_INO) { 230 ext2fs_block_iterate2(filsys_, it_ino, 0, nullptr, 231 AddMetadataBlocks, 232 &inode_blocks); 233 } 234 } 235 ext2fs_close_inode_scan(iscan); 236 if (!ok) 237 return false; 238 239 // The set of inodes already added to the output. There can be less elements 240 // here than in files since the later can contain repeated inodes due to 241 // hardlink files. 242 set<ext2_ino_t> used_inodes; 243 244 UpdateFileAndAppendState priv_data; 245 priv_data.inodes = &inodes; 246 priv_data.used_inodes = &used_inodes; 247 priv_data.files = files; 248 priv_data.filsys = filsys_; 249 250 files->clear(); 251 // Iterate over all the files of each directory to update the name and add it. 252 for (ext2_ino_t dir_ino : directories) { 253 char* dir_name = nullptr; 254 errcode_t error = ext2fs_get_pathname(filsys_, dir_ino, 0, &dir_name); 255 if (error) { 256 // Not being able to read a directory name is not a fatal error, it is 257 // just skiped. 258 LOG(WARNING) << "Reading directory name on inode " << dir_ino 259 << " (error " << error << ")"; 260 inodes[dir_ino].name = base::StringPrintf("<dir-%u>", dir_ino); 261 } else { 262 inodes[dir_ino].name = dir_name; 263 files->push_back(inodes[dir_ino]); 264 used_inodes.insert(dir_ino); 265 } 266 ext2fs_free_mem(&dir_name); 267 268 error = ext2fs_dir_iterate2( 269 filsys_, dir_ino, 0, nullptr /* block_buf */, 270 UpdateFileAndAppend, &priv_data); 271 if (error) { 272 LOG(WARNING) << "Failed to enumerate files in directory " 273 << inodes[dir_ino].name << " (error " << error << ")"; 274 } 275 } 276 277 // Add <inode-blocks> file with the blocks that hold inodes. 278 File inode_file; 279 inode_file.name = "<inode-blocks>"; 280 for (uint64_t block : inode_blocks) { 281 AppendBlockToExtents(&inode_file.extents, block); 282 } 283 files->push_back(inode_file); 284 285 // Add <free-spacce> blocs. 286 errcode_t error = ext2fs_read_block_bitmap(filsys_); 287 if (error) { 288 LOG(ERROR) << "Reading the blocks bitmap (error " << error << ")"; 289 } else { 290 File free_space; 291 free_space.name = "<free-space>"; 292 blk64_t blk_start = ext2fs_get_block_bitmap_start2(filsys_->block_map); 293 blk64_t blk_end = ext2fs_get_block_bitmap_end2(filsys_->block_map); 294 for (blk64_t block = blk_start; block < blk_end; block++) { 295 if (!ext2fs_test_block_bitmap2(filsys_->block_map, block)) 296 AppendBlockToExtents(&free_space.extents, block); 297 } 298 files->push_back(free_space); 299 } 300 301 // Add all the unreachable files plus the pseudo-files with an inode. Since 302 // these inodes aren't files in the filesystem, ignore the empty ones. 303 for (const auto& ino_file : inodes) { 304 if (used_inodes.find(ino_file.first) != used_inodes.end()) 305 continue; 306 if (ino_file.second.extents.empty()) 307 continue; 308 309 File file = ino_file.second; 310 ExtentRanges ranges; 311 ranges.AddExtents(file.extents); 312 file.extents = ranges.GetExtentsForBlockCount(ranges.blocks()); 313 314 files->push_back(file); 315 } 316 317 return true; 318} 319 320bool Ext2Filesystem::LoadSettings(brillo::KeyValueStore* store) const { 321 // First search for the settings inode following symlinks if we find some. 322 ext2_ino_t ino_num = 0; 323 errcode_t err = ext2fs_namei_follow( 324 filsys_, EXT2_ROOT_INO /* root */, EXT2_ROOT_INO /* cwd */, 325 "/etc/update_engine.conf", &ino_num); 326 if (err != 0) 327 return false; 328 329 ext2_inode ino_data; 330 if (ext2fs_read_inode(filsys_, ino_num, &ino_data) != 0) 331 return false; 332 333 // Load the list of blocks and then the contents of the inodes. 334 vector<Extent> extents; 335 err = ext2fs_block_iterate2(filsys_, ino_num, BLOCK_FLAG_DATA_ONLY, 336 nullptr, // block_buf 337 ProcessInodeAllBlocks, 338 &extents); 339 if (err != 0) 340 return false; 341 342 brillo::Blob blob; 343 uint64_t physical_size = BlocksInExtents(extents) * filsys_->blocksize; 344 // Sparse holes in the settings file are not supported. 345 if (EXT2_I_SIZE(&ino_data) > physical_size) 346 return false; 347 if (!utils::ReadExtents(filename_, extents, &blob, physical_size, 348 filsys_->blocksize)) 349 return false; 350 351 string text(blob.begin(), blob.begin() + EXT2_I_SIZE(&ino_data)); 352 return store->LoadFromString(text); 353} 354 355} // namespace chromeos_update_engine 356