/ src / leveldb / db / corruption_test.cc
corruption_test.cc
  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(); }