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