/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless requied by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * These file system recovery tests ensure the ability to recover from * filesystem crashes in key blocks (e.g. superblock). */ #include #include #include #include #include #include #include #include #include #include "cutils/properties.h" #include #include #define LOG_TAG "fsRecoveryTest" #include #include #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) #define SB_OFFSET 1024 static char UMOUNT_BIN[] = "/system/bin/umount"; static char VDC_BIN[] = "/system/bin/vdc"; enum Fs_Type { FS_UNKNOWN, FS_EXT4, FS_F2FS }; namespace android { class DataFileVerifier { public: explicit DataFileVerifier(const char* file_name) { strncpy(test_file_, file_name, FILENAME_MAX); } void verify_write() { int write_fd = open(test_file_, O_CREAT | O_WRONLY, 0666); ASSERT_TRUE(write_fd); ASSERT_EQ(write(write_fd, "TEST", 4), 4); close(write_fd); } void verify_read() { char read_buff[4]; int read_fd = open(test_file_, O_RDONLY); ASSERT_TRUE(read_fd); ASSERT_EQ(read(read_fd, read_buff, sizeof(read_buff)), 4); ASSERT_FALSE(strncmp(read_buff, "TEST", 4)); close(read_fd); } ~DataFileVerifier() { unlink(test_file_); } private: char test_file_[FILENAME_MAX]; }; namespace ext4 { bool getSuperBlock(const int blk_fd, struct ext4_super_block* sb) { if (lseek(blk_fd, SB_OFFSET, SEEK_SET) == -1) { testPrintE("Cannot lseek to ext4 superblock to read"); return false; } if (read(blk_fd, sb, sizeof(*sb)) != sizeof(*sb)) { testPrintE("Cannot read ext4 superblock"); return false; } if (sb->s_magic != 0xEF53) { testPrintE("Invalid ext4 superblock magic"); return false; } return true; } bool setSbErrorBit(const int blk_fd) { // Read super block. struct ext4_super_block sb; if (!getSuperBlock(blk_fd, &sb)) { return false; } // Check that the detected errors bit is not set. if (sb.s_state & 0x2) { testPrintE("Ext4 superblock already corrupted"); return false; } // Set the detected errors bit. sb.s_state |= 0x2; // Write superblock. if (lseek(blk_fd, SB_OFFSET, SEEK_SET) == -1) { testPrintE("Cannot lseek to superblock to write\n"); return false; } if (write(blk_fd, &sb, sizeof(sb)) != sizeof(sb)) { testPrintE("Cannot write superblock\n"); return false; } return true; } bool corruptGdtFreeBlock(const int blk_fd) { // Read super block. struct ext4_super_block sb; if (!getSuperBlock(blk_fd, &sb)) { return false; } // Make sure the block size is 2K or 4K. if ((sb.s_log_block_size != 1) && (sb.s_log_block_size != 2)) { testPrintE("Ext4 block size not 2K or 4K\n"); return false; } int block_size = 1 << (10 + sb.s_log_block_size); int num_bgs = DIV_ROUND_UP(sb.s_blocks_count_lo, sb.s_blocks_per_group); if (sb.s_desc_size != sizeof(struct ext2_group_desc)) { testPrintE("Can't handle ext4 block group descriptor size of %d", sb.s_desc_size); return false; } // Read first block group descriptor, decrement free block count, and // write it back out. if (lseek(blk_fd, block_size, SEEK_SET) == -1) { testPrintE("Cannot lseek to ext4 block group descriptor table to read"); return false; } // Read in block group descriptors till we read one that has at least one free // block. struct ext2_group_desc gd; for (int i = 0; i < num_bgs; i++) { if (read(blk_fd, &gd, sizeof(gd)) != sizeof(gd)) { testPrintE("Cannot read ext4 group descriptor %d", i); return false; } if (gd.bg_free_blocks_count) { break; } } gd.bg_free_blocks_count--; if (lseek(blk_fd, -sizeof(gd), SEEK_CUR) == -1) { testPrintE("Cannot lseek to ext4 block group descriptor table to write"); return false; } if (write(blk_fd, &gd, sizeof(gd)) != sizeof(gd)) { testPrintE("Cannot write modified ext4 group descriptor"); return false; } return true; } } // namespace ext4 class FsRecoveryTest : public ::testing::Test { protected: FsRecoveryTest() : fs_type(FS_UNKNOWN), blk_fd_(-1) {} bool setCacheInfoFromFstab() { fs_type = FS_UNKNOWN; android::fs_mgr::Fstab fstab; if (!android::fs_mgr::ReadDefaultFstab(&fstab)) { testPrintE("failed to open default fstab\n"); } else { // Loop through entries looking for cache. for (const auto& entry : fstab) { if (entry.mount_point == "/cache") { blk_path_ = entry.blk_device; if (entry.fs_type == "ext4") { fs_type = FS_EXT4; break; } else if (entry.fs_type == "f2fs") { fs_type = FS_F2FS; break; } } } } return fs_type != FS_UNKNOWN; } bool unmountCache() { char cache_str[] = "/cache"; char *umount_argv[] = { UMOUNT_BIN, cache_str, }; return logwrap_fork_execvp(ARRAY_SIZE(umount_argv), umount_argv, nullptr, false, LOG_KLOG, false, nullptr) >= 0; } bool mountAll() { char storage_str[] = "storage"; char mountall_str[] = "mountall"; char *mountall_argv[] = { VDC_BIN, storage_str, mountall_str, }; return logwrap_fork_execvp(ARRAY_SIZE(mountall_argv), mountall_argv, nullptr, false, LOG_KLOG, false, nullptr) >= 0; } int getCacheBlkFd() { if (blk_fd_ == -1) { blk_fd_ = open(blk_path_.c_str(), O_RDWR); } return blk_fd_; } void closeCacheBlkFd() { if (blk_fd_ > -1) { close(blk_fd_); } blk_fd_ = -1; } void assertCacheHealthy() { const char* test_file = "/cache/FsRecoveryTestGarbage.txt"; DataFileVerifier file_verify(test_file); file_verify.verify_write(); file_verify.verify_read(); } virtual void SetUp() { assertCacheHealthy(); ASSERT_TRUE(setCacheInfoFromFstab()); } virtual void TearDown() { // Ensure /cache partition is accessible, mounted and healthy for other // tests. closeCacheBlkFd(); ASSERT_TRUE(mountAll()); assertCacheHealthy(); } Fs_Type fs_type; private: std::string blk_path_; int blk_fd_; }; TEST_F(FsRecoveryTest, EXT4_CorruptGdt) { if (fs_type != FS_EXT4) { return; } // Setup test file in /cache. const char* test_file = "/cache/CorruptGdtGarbage.txt"; DataFileVerifier file_verify(test_file); file_verify.verify_write(); // Unmount and corrupt /cache gdt. ASSERT_TRUE(unmountCache()); ASSERT_TRUE(ext4::corruptGdtFreeBlock(getCacheBlkFd())); closeCacheBlkFd(); ASSERT_TRUE(mountAll()); // Verify results. file_verify.verify_read(); } TEST_F(FsRecoveryTest, EXT4_SetErrorBit) { if (fs_type != FS_EXT4) { return; } // Setup test file in /cache. const char* test_file = "/cache/ErrorBitGarbagetxt"; DataFileVerifier file_verify(test_file); file_verify.verify_write(); // Unmount and set /cache super block error bit. ASSERT_TRUE(unmountCache()); ASSERT_TRUE(ext4::setSbErrorBit(getCacheBlkFd())); closeCacheBlkFd(); ASSERT_TRUE(mountAll()); // Verify results. file_verify.verify_read(); struct ext4_super_block sb; ASSERT_TRUE(ext4::getSuperBlock(getCacheBlkFd(), &sb)); // Verify e2fsck has recovered the error bit of sb. ASSERT_FALSE(sb.s_state & 0x2); } } // namespace android