corruption_test.cc raw

   1  // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style license that can be
   3  // found in the LICENSE file. See the AUTHORS file for names of contributors.
   4  
   5  #include <sys/types.h>
   6  
   7  #include "db/db_impl.h"
   8  #include "db/filename.h"
   9  #include "db/log_format.h"
  10  #include "db/version_set.h"
  11  #include "leveldb/cache.h"
  12  #include "leveldb/db.h"
  13  #include "leveldb/table.h"
  14  #include "leveldb/write_batch.h"
  15  #include "util/logging.h"
  16  #include "util/testharness.h"
  17  #include "util/testutil.h"
  18  
  19  namespace leveldb {
  20  
  21  static const int kValueSize = 1000;
  22  
  23  class CorruptionTest {
  24   public:
  25    CorruptionTest()
  26        : db_(nullptr),
  27          dbname_("/memenv/corruption_test"),
  28          tiny_cache_(NewLRUCache(100)) {
  29      options_.env = &env_;
  30      options_.block_cache = tiny_cache_;
  31      DestroyDB(dbname_, options_);
  32  
  33      options_.create_if_missing = true;
  34      Reopen();
  35      options_.create_if_missing = false;
  36    }
  37  
  38    ~CorruptionTest() {
  39      delete db_;
  40      delete tiny_cache_;
  41    }
  42  
  43    Status TryReopen() {
  44      delete db_;
  45      db_ = nullptr;
  46      return DB::Open(options_, dbname_, &db_);
  47    }
  48  
  49    void Reopen() { ASSERT_OK(TryReopen()); }
  50  
  51    void RepairDB() {
  52      delete db_;
  53      db_ = nullptr;
  54      ASSERT_OK(::leveldb::RepairDB(dbname_, options_));
  55    }
  56  
  57    void Build(int n) {
  58      std::string key_space, value_space;
  59      WriteBatch batch;
  60      for (int i = 0; i < n; i++) {
  61        // if ((i % 100) == 0) fprintf(stderr, "@ %d of %d\n", i, n);
  62        Slice key = Key(i, &key_space);
  63        batch.Clear();
  64        batch.Put(key, Value(i, &value_space));
  65        WriteOptions options;
  66        // Corrupt() doesn't work without this sync on windows; stat reports 0 for
  67        // the file size.
  68        if (i == n - 1) {
  69          options.sync = true;
  70        }
  71        ASSERT_OK(db_->Write(options, &batch));
  72      }
  73    }
  74  
  75    void Check(int min_expected, int max_expected) {
  76      int next_expected = 0;
  77      int missed = 0;
  78      int bad_keys = 0;
  79      int bad_values = 0;
  80      int correct = 0;
  81      std::string value_space;
  82      Iterator* iter = db_->NewIterator(ReadOptions());
  83      for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
  84        uint64_t key;
  85        Slice in(iter->key());
  86        if (in == "" || in == "~") {
  87          // Ignore boundary keys.
  88          continue;
  89        }
  90        if (!ConsumeDecimalNumber(&in, &key) || !in.empty() ||
  91            key < next_expected) {
  92          bad_keys++;
  93          continue;
  94        }
  95        missed += (key - next_expected);
  96        next_expected = key + 1;
  97        if (iter->value() != Value(key, &value_space)) {
  98          bad_values++;
  99        } else {
 100          correct++;
 101        }
 102      }
 103      delete iter;
 104  
 105      fprintf(stderr,
 106              "expected=%d..%d; got=%d; bad_keys=%d; bad_values=%d; missed=%d\n",
 107              min_expected, max_expected, correct, bad_keys, bad_values, missed);
 108      ASSERT_LE(min_expected, correct);
 109      ASSERT_GE(max_expected, correct);
 110    }
 111  
 112    void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
 113      // Pick file to corrupt
 114      std::vector<std::string> filenames;
 115      ASSERT_OK(env_.target()->GetChildren(dbname_, &filenames));
 116      uint64_t number;
 117      FileType type;
 118      std::string fname;
 119      int picked_number = -1;
 120      for (size_t i = 0; i < filenames.size(); i++) {
 121        if (ParseFileName(filenames[i], &number, &type) && type == filetype &&
 122            int(number) > picked_number) {  // Pick latest file
 123          fname = dbname_ + "/" + filenames[i];
 124          picked_number = number;
 125        }
 126      }
 127      ASSERT_TRUE(!fname.empty()) << filetype;
 128  
 129      uint64_t file_size;
 130      ASSERT_OK(env_.target()->GetFileSize(fname, &file_size));
 131  
 132      if (offset < 0) {
 133        // Relative to end of file; make it absolute
 134        if (-offset > file_size) {
 135          offset = 0;
 136        } else {
 137          offset = file_size + offset;
 138        }
 139      }
 140      if (offset > file_size) {
 141        offset = file_size;
 142      }
 143      if (offset + bytes_to_corrupt > file_size) {
 144        bytes_to_corrupt = file_size - offset;
 145      }
 146  
 147      // Do it
 148      std::string contents;
 149      Status s = ReadFileToString(env_.target(), fname, &contents);
 150      ASSERT_TRUE(s.ok()) << s.ToString();
 151      for (int i = 0; i < bytes_to_corrupt; i++) {
 152        contents[i + offset] ^= 0x80;
 153      }
 154      s = WriteStringToFile(env_.target(), contents, fname);
 155      ASSERT_TRUE(s.ok()) << s.ToString();
 156    }
 157  
 158    int Property(const std::string& name) {
 159      std::string property;
 160      int result;
 161      if (db_->GetProperty(name, &property) &&
 162          sscanf(property.c_str(), "%d", &result) == 1) {
 163        return result;
 164      } else {
 165        return -1;
 166      }
 167    }
 168  
 169    // Return the ith key
 170    Slice Key(int i, std::string* storage) {
 171      char buf[100];
 172      snprintf(buf, sizeof(buf), "%016d", i);
 173      storage->assign(buf, strlen(buf));
 174      return Slice(*storage);
 175    }
 176  
 177    // Return the value to associate with the specified key
 178    Slice Value(int k, std::string* storage) {
 179      Random r(k);
 180      return test::RandomString(&r, kValueSize, storage);
 181    }
 182  
 183    test::ErrorEnv env_;
 184    Options options_;
 185    DB* db_;
 186  
 187   private:
 188    std::string dbname_;
 189    Cache* tiny_cache_;
 190  };
 191  
 192  TEST(CorruptionTest, Recovery) {
 193    Build(100);
 194    Check(100, 100);
 195    Corrupt(kLogFile, 19, 1);  // WriteBatch tag for first record
 196    Corrupt(kLogFile, log::kBlockSize + 1000, 1);  // Somewhere in second block
 197    Reopen();
 198  
 199    // The 64 records in the first two log blocks are completely lost.
 200    Check(36, 36);
 201  }
 202  
 203  TEST(CorruptionTest, RecoverWriteError) {
 204    env_.writable_file_error_ = true;
 205    Status s = TryReopen();
 206    ASSERT_TRUE(!s.ok());
 207  }
 208  
 209  TEST(CorruptionTest, NewFileErrorDuringWrite) {
 210    // Do enough writing to force minor compaction
 211    env_.writable_file_error_ = true;
 212    const int num = 3 + (Options().write_buffer_size / kValueSize);
 213    std::string value_storage;
 214    Status s;
 215    for (int i = 0; s.ok() && i < num; i++) {
 216      WriteBatch batch;
 217      batch.Put("a", Value(100, &value_storage));
 218      s = db_->Write(WriteOptions(), &batch);
 219    }
 220    ASSERT_TRUE(!s.ok());
 221    ASSERT_GE(env_.num_writable_file_errors_, 1);
 222    env_.writable_file_error_ = false;
 223    Reopen();
 224  }
 225  
 226  TEST(CorruptionTest, TableFile) {
 227    Build(100);
 228    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 229    dbi->TEST_CompactMemTable();
 230    dbi->TEST_CompactRange(0, nullptr, nullptr);
 231    dbi->TEST_CompactRange(1, nullptr, nullptr);
 232  
 233    Corrupt(kTableFile, 100, 1);
 234    Check(90, 99);
 235  }
 236  
 237  TEST(CorruptionTest, TableFileRepair) {
 238    options_.block_size = 2 * kValueSize;  // Limit scope of corruption
 239    options_.paranoid_checks = true;
 240    Reopen();
 241    Build(100);
 242    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 243    dbi->TEST_CompactMemTable();
 244    dbi->TEST_CompactRange(0, nullptr, nullptr);
 245    dbi->TEST_CompactRange(1, nullptr, nullptr);
 246  
 247    Corrupt(kTableFile, 100, 1);
 248    RepairDB();
 249    Reopen();
 250    Check(95, 99);
 251  }
 252  
 253  TEST(CorruptionTest, TableFileIndexData) {
 254    Build(10000);  // Enough to build multiple Tables
 255    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 256    dbi->TEST_CompactMemTable();
 257  
 258    Corrupt(kTableFile, -2000, 500);
 259    Reopen();
 260    Check(5000, 9999);
 261  }
 262  
 263  TEST(CorruptionTest, MissingDescriptor) {
 264    Build(1000);
 265    RepairDB();
 266    Reopen();
 267    Check(1000, 1000);
 268  }
 269  
 270  TEST(CorruptionTest, SequenceNumberRecovery) {
 271    ASSERT_OK(db_->Put(WriteOptions(), "foo", "v1"));
 272    ASSERT_OK(db_->Put(WriteOptions(), "foo", "v2"));
 273    ASSERT_OK(db_->Put(WriteOptions(), "foo", "v3"));
 274    ASSERT_OK(db_->Put(WriteOptions(), "foo", "v4"));
 275    ASSERT_OK(db_->Put(WriteOptions(), "foo", "v5"));
 276    RepairDB();
 277    Reopen();
 278    std::string v;
 279    ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
 280    ASSERT_EQ("v5", v);
 281    // Write something.  If sequence number was not recovered properly,
 282    // it will be hidden by an earlier write.
 283    ASSERT_OK(db_->Put(WriteOptions(), "foo", "v6"));
 284    ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
 285    ASSERT_EQ("v6", v);
 286    Reopen();
 287    ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
 288    ASSERT_EQ("v6", v);
 289  }
 290  
 291  TEST(CorruptionTest, CorruptedDescriptor) {
 292    ASSERT_OK(db_->Put(WriteOptions(), "foo", "hello"));
 293    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 294    dbi->TEST_CompactMemTable();
 295    dbi->TEST_CompactRange(0, nullptr, nullptr);
 296  
 297    Corrupt(kDescriptorFile, 0, 1000);
 298    Status s = TryReopen();
 299    ASSERT_TRUE(!s.ok());
 300  
 301    RepairDB();
 302    Reopen();
 303    std::string v;
 304    ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
 305    ASSERT_EQ("hello", v);
 306  }
 307  
 308  TEST(CorruptionTest, CompactionInputError) {
 309    Build(10);
 310    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 311    dbi->TEST_CompactMemTable();
 312    const int last = config::kMaxMemCompactLevel;
 313    ASSERT_EQ(1, Property("leveldb.num-files-at-level" + NumberToString(last)));
 314  
 315    Corrupt(kTableFile, 100, 1);
 316    Check(5, 9);
 317  
 318    // Force compactions by writing lots of values
 319    Build(10000);
 320    Check(10000, 10000);
 321  }
 322  
 323  TEST(CorruptionTest, CompactionInputErrorParanoid) {
 324    options_.paranoid_checks = true;
 325    options_.write_buffer_size = 512 << 10;
 326    Reopen();
 327    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 328  
 329    // Make multiple inputs so we need to compact.
 330    for (int i = 0; i < 2; i++) {
 331      Build(10);
 332      dbi->TEST_CompactMemTable();
 333      Corrupt(kTableFile, 100, 1);
 334      env_.SleepForMicroseconds(100000);
 335    }
 336    dbi->CompactRange(nullptr, nullptr);
 337  
 338    // Write must fail because of corrupted table
 339    std::string tmp1, tmp2;
 340    Status s = db_->Put(WriteOptions(), Key(5, &tmp1), Value(5, &tmp2));
 341    ASSERT_TRUE(!s.ok()) << "write did not fail in corrupted paranoid db";
 342  }
 343  
 344  TEST(CorruptionTest, UnrelatedKeys) {
 345    Build(10);
 346    DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
 347    dbi->TEST_CompactMemTable();
 348    Corrupt(kTableFile, 100, 1);
 349  
 350    std::string tmp1, tmp2;
 351    ASSERT_OK(db_->Put(WriteOptions(), Key(1000, &tmp1), Value(1000, &tmp2)));
 352    std::string v;
 353    ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
 354    ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
 355    dbi->TEST_CompactMemTable();
 356    ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
 357    ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
 358  }
 359  
 360  }  // namespace leveldb
 361  
 362  int main(int argc, char** argv) { return leveldb::test::RunAllTests(); }
 363