summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMohamad Ayyash <mkayyash@google.com>2014-03-13 20:56:12 -0700
committerMohamad Ayyash <mkayyash@google.com>2014-03-17 13:50:44 -0700
commit8303f4d324caf9f66d043a53961ed75c2b6d4e2d (patch)
tree96e3492cec9d55eb2e20aa43f00c4d186a746408
parente17edfec6b0835f129020e658b584eab2ca7f1ee (diff)
downloadextras-8303f4d324caf9f66d043a53961ed75c2b6d4e2d.tar.gz
fstest: Add a set of filesystem recovery tests with gTest.
Tests to verify recovery from super block and gdt corruption. TESTED: Verified in dmesg that e2fsck is detecting and fixing errors in the filesystem. Change-Id: I1da6352c67fda3466b57875554a42291bd11809c
-rw-r--r--tests/ext4/Android.mk21
-rw-r--r--tests/ext4/corrupt_gdt_free_blocks.c100
-rw-r--r--tests/ext4/set_ext4_err_bit.c62
-rw-r--r--tests/fstest/Android.mk21
-rw-r--r--tests/fstest/recovery_test.cpp320
5 files changed, 342 insertions, 182 deletions
diff --git a/tests/ext4/Android.mk b/tests/ext4/Android.mk
index e76ccae6..e471ef95 100644
--- a/tests/ext4/Android.mk
+++ b/tests/ext4/Android.mk
@@ -3,27 +3,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
-LOCAL_SRC_FILES:= corrupt_gdt_free_blocks.c
-LOCAL_MODULE:= corrupt_gdt_free_blocks
-LOCAL_MODULE_TAGS := debug
-LOCAL_C_INCLUDES += system/extras/ext4_utils
-
-include $(BUILD_EXECUTABLE)
-
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= set_ext4_err_bit.c
-LOCAL_MODULE:= set_ext4_err_bit
-LOCAL_MODULE_TAGS := debug
-
-include $(BUILD_EXECUTABLE)
-
-
-include $(CLEAR_VARS)
-
LOCAL_SRC_FILES:= rand_emmc_perf.c
-LOCAL_MODULE:= rand_emmc_perf
+LOCAL_MODULE:= rand_emmc_perf
LOCAL_MODULE_TAGS := optional
LOCAL_FORCE_STATIC_EXECUTABLE := true
LOCAL_STATIC_LIBRARIES := libm libc
diff --git a/tests/ext4/corrupt_gdt_free_blocks.c b/tests/ext4/corrupt_gdt_free_blocks.c
deleted file mode 100644
index 7be737da..00000000
--- a/tests/ext4/corrupt_gdt_free_blocks.c
+++ /dev/null
@@ -1,100 +0,0 @@
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#include "ext4.h"
-#include "ext4_utils.h"
-
-#define SB_OFFSET 1024
-
-int main(int argc, char *argv[])
-{
- char me[] = "corrupt_gdt_free_blocks";
- int fd;
- int block_size;
- int num_bgs;
- int i;
- struct ext4_super_block sb;
- struct ext2_group_desc gd;
-
- if (argc != 2) {
- fprintf(stderr, "%s: Usage: %s <ext4_block_device>\n", me, me);
- exit(1);
- }
-
- fd = open(argv[1], O_RDWR);
-
- if (fd < 0) {
- fprintf(stderr, "%s: Cannot open block device %s\n", me, argv[1]);
- exit(1);
- }
-
- if (lseek(fd, SB_OFFSET, SEEK_SET) == -1) {
- fprintf(stderr, "%s: Cannot lseek to superblock to read\n", me);
- exit(1);
- }
-
- if (read(fd, &sb, sizeof(sb)) != sizeof(sb)) {
- fprintf(stderr, "%s: Cannot read superblock\n", me);
- exit(1);
- }
-
- if (sb.s_magic != 0xEF53) {
- fprintf(stderr, "%s: invalid superblock magic\n", me);
- exit(1);
- }
-
- /* Make sure the block size is 2K or 4K */
- if ((sb.s_log_block_size != 1) && (sb.s_log_block_size != 2)) {
- fprintf(stderr, "%s: block size not 2K or 4K\n", me);
- exit(1);
- }
-
- block_size = 1 << (10 + sb.s_log_block_size);
- 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)) {
- fprintf(stderr, "%s: Can't handle block group descriptor size of %d\n",
- me, sb.s_desc_size);
- exit(1);
- }
-
- /* read first block group descriptor, decrement free block count, and
- * write it back out
- */
- if (lseek(fd, block_size, SEEK_SET) == -1) {
- fprintf(stderr, "%s: Cannot lseek to block group descriptor table to read\n", me);
- exit(1);
- }
-
- /* Read in block group descriptors till we read one that has at least one free block */
-
- for (i=0; i < num_bgs; i++) {
- if (read(fd, &gd, sizeof(gd)) != sizeof(gd)) {
- fprintf(stderr, "%s: Cannot read group descriptor %d\n", me, i);
- exit(1);
- }
- if (gd.bg_free_blocks_count) {
- break;
- }
- }
-
- gd.bg_free_blocks_count--;
-
- if (lseek(fd, -sizeof(gd), SEEK_CUR) == -1) {
- fprintf(stderr, "%s: Cannot lseek to block group descriptor table to write\n", me);
- exit(1);
- }
-
- if (write(fd, &gd, sizeof(gd)) != sizeof(gd)) {
- fprintf(stderr, "%s: Cannot write modified group descriptor\n", me);
- exit(1);
- }
-
- close(fd);
-
- return 0;
-}
-
diff --git a/tests/ext4/set_ext4_err_bit.c b/tests/ext4/set_ext4_err_bit.c
deleted file mode 100644
index 88893d84..00000000
--- a/tests/ext4/set_ext4_err_bit.c
+++ /dev/null
@@ -1,62 +0,0 @@
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-#include <stdio.h>
-#include <stdlib.h>
-
-#define SB_OFFSET 1024
-#define SB_SIZE 1024
-#define EXT4_MAGIC_OFFSET 0x38
-#define EXT4_STATE_OFFSET 0x3A
-
-int main(int argc, char *argv[])
-{
- int fd;
- char me[] = "set_ext4_err_bit";
- unsigned char sb[1024];
-
- if (argc != 2) {
- fprintf(stderr, "%s: Usage: %s <ext4_block_device>\n", me, me);
- exit(1);
- }
-
- fd = open(argv[1], O_RDWR);
-
- if (fd < 0) {
- fprintf(stderr, "%s: Cannot open block device %s\n", me, argv[1]);
- exit(1);
- }
-
- if (lseek(fd, SB_OFFSET, SEEK_SET) == -1) {
- fprintf(stderr, "%s: Cannot lseek to superblock to read\n", me);
- exit(1);
- }
-
- if (read(fd, sb, SB_SIZE) != SB_SIZE) {
- fprintf(stderr, "%s: Cannot read superblock\n", me);
- exit(1);
- }
-
- if ((sb[EXT4_MAGIC_OFFSET] != 0x53) || (sb[EXT4_MAGIC_OFFSET+1] != 0xEF)) {
- fprintf(stderr, "%s: invalid superblock magic\n", me);
- exit(1);
- }
-
- /* Set the errors detected bit */
- sb[EXT4_STATE_OFFSET] |= 0x2;
-
- if (lseek(fd, SB_OFFSET, SEEK_SET) == -1) {
- fprintf(stderr, "%s: Cannot lseek to superblock to write\n", me);
- exit(1);
- }
-
- if (write(fd, sb, SB_SIZE) != SB_SIZE) {
- fprintf(stderr, "%s: Cannot write superblock\n", me);
- exit(1);
- }
-
- close(fd);
-
- return 0;
-}
-
diff --git a/tests/fstest/Android.mk b/tests/fstest/Android.mk
index 7ab89e12..7f2bdfce 100644
--- a/tests/fstest/Android.mk
+++ b/tests/fstest/Android.mk
@@ -43,3 +43,24 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/local
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
+
+####
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE := recovery_test
+
+LOCAL_SRC_FILES := recovery_test.cpp
+
+LOCAL_SHARED_LIBRARIES += libcutils libutils liblog liblogwrap
+
+LOCAL_STATIC_LIBRARIES += libtestUtil libfs_mgr
+
+LOCAL_C_INCLUDES += system/extras/tests/include \
+ system/core/fs_mgr/include \
+ system/extras/ext4_utils \
+ system/core/logwrapper/include
+
+include $(BUILD_NATIVE_TEST)
diff --git a/tests/fstest/recovery_test.cpp b/tests/fstest/recovery_test.cpp
new file mode 100644
index 00000000..4121a871
--- /dev/null
+++ b/tests/fstest/recovery_test.cpp
@@ -0,0 +1,320 @@
+/*
+ * 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 <assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fs_mgr.h>
+#include <gtest/gtest.h>
+#include <logwrap/logwrap.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "cutils/properties.h"
+#include "ext4.h"
+#include "ext4_utils.h"
+
+#define LOG_TAG "fsRecoveryTest"
+#include <utils/Log.h>
+#include <testUtil.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
+#define FSTAB_PREFIX "/fstab."
+#define SB_OFFSET 1024
+#define UMOUNT_BIN "/system/bin/umount"
+#define VDC_BIN "/system/bin/vdc"
+
+enum Fs_Type { FS_UNKNOWN, FS_EXT4, FS_F2FS };
+
+namespace android {
+
+class DataFileVerifier {
+ public:
+ 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;
+ char propbuf[PROPERTY_VALUE_MAX];
+ property_get("ro.hardware", propbuf, "");
+ char fstab_filename[PROPERTY_VALUE_MAX + sizeof(FSTAB_PREFIX)];
+ snprintf(fstab_filename, sizeof(fstab_filename), FSTAB_PREFIX"%s", propbuf);
+
+ struct fstab *fstab = fs_mgr_read_fstab(fstab_filename);
+ if (!fstab) {
+ testPrintE("failed to open %s\n", fstab_filename);
+ } else {
+ // Loop through entries looking for cache.
+ for (int i = 0; i < fstab->num_entries; ++i) {
+ if (!strcmp(fstab->recs[i].mount_point, "/cache")) {
+ strcpy(blk_path_, fstab->recs[i].blk_device);
+ if (!strcmp(fstab->recs[i].fs_type, "ext4")) {
+ fs_type = FS_EXT4;
+ break;
+ } else if (!strcmp(fstab->recs[i].fs_type, "f2fs")) {
+ fs_type = FS_F2FS;
+ break;
+ }
+ }
+ }
+ fs_mgr_free_fstab(fstab);
+ }
+ return fs_type != FS_UNKNOWN;
+ }
+
+ bool unmountCache() {
+ char *umount_argv[] = {
+ UMOUNT_BIN,
+ "/cache"
+ };
+ int status;
+ return android_fork_execvp_ext(ARRAY_SIZE(umount_argv), umount_argv,
+ NULL, true, LOG_KLOG, false, NULL) >= 0;
+ }
+
+ bool mountAll() {
+ char *mountall_argv[] = {
+ VDC_BIN,
+ "storage",
+ "mountall"
+ };
+ int status;
+ return android_fork_execvp_ext(ARRAY_SIZE(mountall_argv), mountall_argv,
+ NULL, true, LOG_KLOG, false, NULL) >= 0;
+ }
+
+ int getCacheBlkFd() {
+ if (blk_fd_ == -1) {
+ blk_fd_ = open(blk_path_, 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:
+ char blk_path_[FILENAME_MAX];
+ 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