OSDN Git Service

fstest: Add a set of filesystem recovery tests with gTest.
authorMohamad Ayyash <mkayyash@google.com>
Fri, 14 Mar 2014 03:56:12 +0000 (20:56 -0700)
committerMohamad Ayyash <mkayyash@google.com>
Mon, 17 Mar 2014 20:50:44 +0000 (13:50 -0700)
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

tests/ext4/Android.mk
tests/ext4/corrupt_gdt_free_blocks.c [deleted file]
tests/ext4/set_ext4_err_bit.c [deleted file]
tests/fstest/Android.mk
tests/fstest/recovery_test.cpp [new file with mode: 0644]

index e76ccae..e471ef9 100644 (file)
@@ -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 (file)
index 7be737d..0000000
+++ /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 (file)
index 88893d8..0000000
+++ /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;
-}
-
index 7ab89e1..7f2bdfc 100644 (file)
@@ -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 (file)
index 0000000..4121a87
--- /dev/null
@@ -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