1 //  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 //
6 // Copyright (c) 2011 The LevelDB Authors. All rights reserved.
7 // Use of this source code is governed by a BSD-style license that can be
8 // found in the LICENSE file. See the AUTHORS file for names of contributors.
9 
10 #ifndef ROCKSDB_LITE
11 
12 #include "rocksdb/db.h"
13 
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <sys/stat.h>
17 #include <sys/types.h>
18 #include <cinttypes>
19 #include "db/db_impl/db_impl.h"
20 #include "db/db_test_util.h"
21 #include "db/log_format.h"
22 #include "db/version_set.h"
23 #include "env/composite_env_wrapper.h"
24 #include "file/filename.h"
25 #include "rocksdb/cache.h"
26 #include "rocksdb/convenience.h"
27 #include "rocksdb/env.h"
28 #include "rocksdb/table.h"
29 #include "rocksdb/write_batch.h"
30 #include "table/block_based/block_based_table_builder.h"
31 #include "table/meta_blocks.h"
32 #include "test_util/testharness.h"
33 #include "test_util/testutil.h"
34 #include "util/string_util.h"
35 
36 namespace ROCKSDB_NAMESPACE {
37 
38 static const int kValueSize = 1000;
39 
40 class CorruptionTest : public testing::Test {
41  public:
42   test::ErrorEnv env_;
43   std::string dbname_;
44   std::shared_ptr<Cache> tiny_cache_;
45   Options options_;
46   DB* db_;
47 
CorruptionTest()48   CorruptionTest() {
49     // If LRU cache shard bit is smaller than 2 (or -1 which will automatically
50     // set it to 0), test SequenceNumberRecovery will fail, likely because of a
51     // bug in recovery code. Keep it 4 for now to make the test passes.
52     tiny_cache_ = NewLRUCache(100, 4);
53     options_.wal_recovery_mode = WALRecoveryMode::kTolerateCorruptedTailRecords;
54     options_.env = &env_;
55     dbname_ = test::PerThreadDBPath("corruption_test");
56     DestroyDB(dbname_, options_);
57 
58     db_ = nullptr;
59     options_.create_if_missing = true;
60     BlockBasedTableOptions table_options;
61     table_options.block_size_deviation = 0;  // make unit test pass for now
62     options_.table_factory.reset(NewBlockBasedTableFactory(table_options));
63     Reopen();
64     options_.create_if_missing = false;
65   }
66 
~CorruptionTest()67   ~CorruptionTest() override {
68     delete db_;
69     DestroyDB(dbname_, Options());
70   }
71 
CloseDb()72   void CloseDb() {
73     delete db_;
74     db_ = nullptr;
75   }
76 
TryReopen(Options * options=nullptr)77   Status TryReopen(Options* options = nullptr) {
78     delete db_;
79     db_ = nullptr;
80     Options opt = (options ? *options : options_);
81     if (opt.env == Options().env) {
82       // If env is not overridden, replace it with ErrorEnv.
83       // Otherwise, the test already uses a non-default Env.
84       opt.env = &env_;
85     }
86     opt.arena_block_size = 4096;
87     BlockBasedTableOptions table_options;
88     table_options.block_cache = tiny_cache_;
89     table_options.block_size_deviation = 0;
90     opt.table_factory.reset(NewBlockBasedTableFactory(table_options));
91     return DB::Open(opt, dbname_, &db_);
92   }
93 
Reopen(Options * options=nullptr)94   void Reopen(Options* options = nullptr) {
95     ASSERT_OK(TryReopen(options));
96   }
97 
RepairDB()98   void RepairDB() {
99     delete db_;
100     db_ = nullptr;
101     ASSERT_OK(::ROCKSDB_NAMESPACE::RepairDB(dbname_, options_));
102   }
103 
Build(int n,int flush_every=0)104   void Build(int n, int flush_every = 0) {
105     std::string key_space, value_space;
106     WriteBatch batch;
107     for (int i = 0; i < n; i++) {
108       if (flush_every != 0 && i != 0 && i % flush_every == 0) {
109         DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
110         dbi->TEST_FlushMemTable();
111       }
112       //if ((i % 100) == 0) fprintf(stderr, "@ %d of %d\n", i, n);
113       Slice key = Key(i, &key_space);
114       batch.Clear();
115       batch.Put(key, Value(i, &value_space));
116       ASSERT_OK(db_->Write(WriteOptions(), &batch));
117     }
118   }
119 
Check(int min_expected,int max_expected)120   void Check(int min_expected, int max_expected) {
121     uint64_t next_expected = 0;
122     uint64_t missed = 0;
123     int bad_keys = 0;
124     int bad_values = 0;
125     int correct = 0;
126     std::string value_space;
127     // Do not verify checksums. If we verify checksums then the
128     // db itself will raise errors because data is corrupted.
129     // Instead, we want the reads to be successful and this test
130     // will detect whether the appropriate corruptions have
131     // occurred.
132     Iterator* iter = db_->NewIterator(ReadOptions(false, true));
133     for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
134       uint64_t key;
135       Slice in(iter->key());
136       if (!ConsumeDecimalNumber(&in, &key) ||
137           !in.empty() ||
138           key < next_expected) {
139         bad_keys++;
140         continue;
141       }
142       missed += (key - next_expected);
143       next_expected = key + 1;
144       if (iter->value() != Value(static_cast<int>(key), &value_space)) {
145         bad_values++;
146       } else {
147         correct++;
148       }
149     }
150     delete iter;
151 
152     fprintf(stderr,
153       "expected=%d..%d; got=%d; bad_keys=%d; bad_values=%d; missed=%llu\n",
154             min_expected, max_expected, correct, bad_keys, bad_values,
155             static_cast<unsigned long long>(missed));
156     ASSERT_LE(min_expected, correct);
157     ASSERT_GE(max_expected, correct);
158   }
159 
CorruptFile(const std::string & fname,int offset,int bytes_to_corrupt)160   void CorruptFile(const std::string& fname, int offset, int bytes_to_corrupt) {
161     struct stat sbuf;
162     if (stat(fname.c_str(), &sbuf) != 0) {
163       const char* msg = strerror(errno);
164       FAIL() << fname << ": " << msg;
165     }
166 
167     if (offset < 0) {
168       // Relative to end of file; make it absolute
169       if (-offset > sbuf.st_size) {
170         offset = 0;
171       } else {
172         offset = static_cast<int>(sbuf.st_size + offset);
173       }
174     }
175     if (offset > sbuf.st_size) {
176       offset = static_cast<int>(sbuf.st_size);
177     }
178     if (offset + bytes_to_corrupt > sbuf.st_size) {
179       bytes_to_corrupt = static_cast<int>(sbuf.st_size - offset);
180     }
181 
182     // Do it
183     std::string contents;
184     Status s = ReadFileToString(Env::Default(), fname, &contents);
185     ASSERT_TRUE(s.ok()) << s.ToString();
186     for (int i = 0; i < bytes_to_corrupt; i++) {
187       contents[i + offset] ^= 0x80;
188     }
189     s = WriteStringToFile(Env::Default(), contents, fname);
190     ASSERT_TRUE(s.ok()) << s.ToString();
191     Options options;
192     EnvOptions env_options;
193     ASSERT_NOK(VerifySstFileChecksum(options, env_options, fname));
194   }
195 
Corrupt(FileType filetype,int offset,int bytes_to_corrupt)196   void Corrupt(FileType filetype, int offset, int bytes_to_corrupt) {
197     // Pick file to corrupt
198     std::vector<std::string> filenames;
199     ASSERT_OK(env_.GetChildren(dbname_, &filenames));
200     uint64_t number;
201     FileType type;
202     std::string fname;
203     int picked_number = -1;
204     for (size_t i = 0; i < filenames.size(); i++) {
205       if (ParseFileName(filenames[i], &number, &type) &&
206           type == filetype &&
207           static_cast<int>(number) > picked_number) {  // Pick latest file
208         fname = dbname_ + "/" + filenames[i];
209         picked_number = static_cast<int>(number);
210       }
211     }
212     ASSERT_TRUE(!fname.empty()) << filetype;
213 
214     CorruptFile(fname, offset, bytes_to_corrupt);
215   }
216 
217   // corrupts exactly one file at level `level`. if no file found at level,
218   // asserts
CorruptTableFileAtLevel(int level,int offset,int bytes_to_corrupt)219   void CorruptTableFileAtLevel(int level, int offset, int bytes_to_corrupt) {
220     std::vector<LiveFileMetaData> metadata;
221     db_->GetLiveFilesMetaData(&metadata);
222     for (const auto& m : metadata) {
223       if (m.level == level) {
224         CorruptFile(dbname_ + "/" + m.name, offset, bytes_to_corrupt);
225         return;
226       }
227     }
228     FAIL() << "no file found at level";
229   }
230 
231 
Property(const std::string & name)232   int Property(const std::string& name) {
233     std::string property;
234     int result;
235     if (db_->GetProperty(name, &property) &&
236         sscanf(property.c_str(), "%d", &result) == 1) {
237       return result;
238     } else {
239       return -1;
240     }
241   }
242 
243   // Return the ith key
Key(int i,std::string * storage)244   Slice Key(int i, std::string* storage) {
245     char buf[100];
246     snprintf(buf, sizeof(buf), "%016d", i);
247     storage->assign(buf, strlen(buf));
248     return Slice(*storage);
249   }
250 
251   // Return the value to associate with the specified key
Value(int k,std::string * storage)252   Slice Value(int k, std::string* storage) {
253     if (k == 0) {
254       // Ugh.  Random seed of 0 used to produce no entropy.  This code
255       // preserves the implementation that was in place when all of the
256       // magic values in this file were picked.
257       *storage = std::string(kValueSize, ' ');
258       return Slice(*storage);
259     } else {
260       Random r(k);
261       return test::RandomString(&r, kValueSize, storage);
262     }
263   }
264 };
265 
TEST_F(CorruptionTest,Recovery)266 TEST_F(CorruptionTest, Recovery) {
267   Build(100);
268   Check(100, 100);
269 #ifdef OS_WIN
270   // On Wndows OS Disk cache does not behave properly
271   // We do not call FlushBuffers on every Flush. If we do not close
272   // the log file prior to the corruption we end up with the first
273   // block not corrupted but only the second. However, under the debugger
274   // things work just fine but never pass when running normally
275   // For that reason people may want to run with unbuffered I/O. That option
276   // is not available for WAL though.
277   CloseDb();
278 #endif
279   Corrupt(kLogFile, 19, 1);      // WriteBatch tag for first record
280   Corrupt(kLogFile, log::kBlockSize + 1000, 1);  // Somewhere in second block
281   ASSERT_TRUE(!TryReopen().ok());
282   options_.paranoid_checks = false;
283   Reopen(&options_);
284 
285   // The 64 records in the first two log blocks are completely lost.
286   Check(36, 36);
287 }
288 
TEST_F(CorruptionTest,RecoverWriteError)289 TEST_F(CorruptionTest, RecoverWriteError) {
290   env_.writable_file_error_ = true;
291   Status s = TryReopen();
292   ASSERT_TRUE(!s.ok());
293 }
294 
TEST_F(CorruptionTest,NewFileErrorDuringWrite)295 TEST_F(CorruptionTest, NewFileErrorDuringWrite) {
296   // Do enough writing to force minor compaction
297   env_.writable_file_error_ = true;
298   const int num =
299       static_cast<int>(3 + (Options().write_buffer_size / kValueSize));
300   std::string value_storage;
301   Status s;
302   bool failed = false;
303   for (int i = 0; i < num; i++) {
304     WriteBatch batch;
305     batch.Put("a", Value(100, &value_storage));
306     s = db_->Write(WriteOptions(), &batch);
307     if (!s.ok()) {
308       failed = true;
309     }
310     ASSERT_TRUE(!failed || !s.ok());
311   }
312   ASSERT_TRUE(!s.ok());
313   ASSERT_GE(env_.num_writable_file_errors_, 1);
314   env_.writable_file_error_ = false;
315   Reopen();
316 }
317 
TEST_F(CorruptionTest,TableFile)318 TEST_F(CorruptionTest, TableFile) {
319   Build(100);
320   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
321   dbi->TEST_FlushMemTable();
322   dbi->TEST_CompactRange(0, nullptr, nullptr);
323   dbi->TEST_CompactRange(1, nullptr, nullptr);
324 
325   Corrupt(kTableFile, 100, 1);
326   Check(99, 99);
327   ASSERT_NOK(dbi->VerifyChecksum());
328 }
329 
TEST_F(CorruptionTest,VerifyChecksumReadahead)330 TEST_F(CorruptionTest, VerifyChecksumReadahead) {
331   Options options;
332   SpecialEnv senv(Env::Default());
333   options.env = &senv;
334   // Disable block cache as we are going to check checksum for
335   // the same file twice and measure number of reads.
336   BlockBasedTableOptions table_options_no_bc;
337   table_options_no_bc.no_block_cache = true;
338   options.table_factory.reset(NewBlockBasedTableFactory(table_options_no_bc));
339 
340   Reopen(&options);
341 
342   Build(10000);
343   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
344   dbi->TEST_FlushMemTable();
345   dbi->TEST_CompactRange(0, nullptr, nullptr);
346   dbi->TEST_CompactRange(1, nullptr, nullptr);
347 
348   senv.count_random_reads_ = true;
349   senv.random_read_counter_.Reset();
350   ASSERT_OK(dbi->VerifyChecksum());
351 
352   // Make sure the counter is enabled.
353   ASSERT_GT(senv.random_read_counter_.Read(), 0);
354 
355   // The SST file is about 10MB. Default readahead size is 256KB.
356   // Give a conservative 20 reads for metadata blocks, The number
357   // of random reads should be within 10 MB / 256KB + 20 = 60.
358   ASSERT_LT(senv.random_read_counter_.Read(), 60);
359 
360   senv.random_read_bytes_counter_ = 0;
361   ReadOptions ro;
362   ro.readahead_size = size_t{32 * 1024};
363   ASSERT_OK(dbi->VerifyChecksum(ro));
364   // The SST file is about 10MB. We set readahead size to 32KB.
365   // Give 0 to 20 reads for metadata blocks, and allow real read
366   // to range from 24KB to 48KB. The lower bound would be:
367   //   10MB / 48KB + 0 = 213
368   // The higher bound is
369   //   10MB / 24KB + 20 = 447.
370   ASSERT_GE(senv.random_read_counter_.Read(), 213);
371   ASSERT_LE(senv.random_read_counter_.Read(), 447);
372 
373   // Test readahead shouldn't break mmap mode (where it should be
374   // disabled).
375   options.allow_mmap_reads = true;
376   Reopen(&options);
377   dbi = static_cast<DBImpl*>(db_);
378   ASSERT_OK(dbi->VerifyChecksum(ro));
379 
380   CloseDb();
381 }
382 
TEST_F(CorruptionTest,TableFileIndexData)383 TEST_F(CorruptionTest, TableFileIndexData) {
384   Options options;
385   // very big, we'll trigger flushes manually
386   options.write_buffer_size = 100 * 1024 * 1024;
387   Reopen(&options);
388   // build 2 tables, flush at 5000
389   Build(10000, 5000);
390   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
391   dbi->TEST_FlushMemTable();
392 
393   // corrupt an index block of an entire file
394   Corrupt(kTableFile, -2000, 500);
395   options.paranoid_checks = false;
396   Reopen(&options);
397   dbi = reinterpret_cast<DBImpl*>(db_);
398   // one full file may be readable, since only one was corrupted
399   // the other file should be fully non-readable, since index was corrupted
400   Check(0, 5000);
401   ASSERT_NOK(dbi->VerifyChecksum());
402 
403   // In paranoid mode, the db cannot be opened due to the corrupted file.
404   ASSERT_TRUE(TryReopen().IsCorruption());
405 }
406 
TEST_F(CorruptionTest,MissingDescriptor)407 TEST_F(CorruptionTest, MissingDescriptor) {
408   Build(1000);
409   RepairDB();
410   Reopen();
411   Check(1000, 1000);
412 }
413 
TEST_F(CorruptionTest,SequenceNumberRecovery)414 TEST_F(CorruptionTest, SequenceNumberRecovery) {
415   ASSERT_OK(db_->Put(WriteOptions(), "foo", "v1"));
416   ASSERT_OK(db_->Put(WriteOptions(), "foo", "v2"));
417   ASSERT_OK(db_->Put(WriteOptions(), "foo", "v3"));
418   ASSERT_OK(db_->Put(WriteOptions(), "foo", "v4"));
419   ASSERT_OK(db_->Put(WriteOptions(), "foo", "v5"));
420   RepairDB();
421   Reopen();
422   std::string v;
423   ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
424   ASSERT_EQ("v5", v);
425   // Write something.  If sequence number was not recovered properly,
426   // it will be hidden by an earlier write.
427   ASSERT_OK(db_->Put(WriteOptions(), "foo", "v6"));
428   ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
429   ASSERT_EQ("v6", v);
430   Reopen();
431   ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
432   ASSERT_EQ("v6", v);
433 }
434 
TEST_F(CorruptionTest,CorruptedDescriptor)435 TEST_F(CorruptionTest, CorruptedDescriptor) {
436   ASSERT_OK(db_->Put(WriteOptions(), "foo", "hello"));
437   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
438   dbi->TEST_FlushMemTable();
439   dbi->TEST_CompactRange(0, nullptr, nullptr);
440 
441   Corrupt(kDescriptorFile, 0, 1000);
442   Status s = TryReopen();
443   ASSERT_TRUE(!s.ok());
444 
445   RepairDB();
446   Reopen();
447   std::string v;
448   ASSERT_OK(db_->Get(ReadOptions(), "foo", &v));
449   ASSERT_EQ("hello", v);
450 }
451 
TEST_F(CorruptionTest,CompactionInputError)452 TEST_F(CorruptionTest, CompactionInputError) {
453   Options options;
454   Reopen(&options);
455   Build(10);
456   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
457   dbi->TEST_FlushMemTable();
458   dbi->TEST_CompactRange(0, nullptr, nullptr);
459   dbi->TEST_CompactRange(1, nullptr, nullptr);
460   ASSERT_EQ(1, Property("rocksdb.num-files-at-level2"));
461 
462   Corrupt(kTableFile, 100, 1);
463   Check(9, 9);
464   ASSERT_NOK(dbi->VerifyChecksum());
465 
466   // Force compactions by writing lots of values
467   Build(10000);
468   Check(10000, 10000);
469   ASSERT_NOK(dbi->VerifyChecksum());
470 }
471 
TEST_F(CorruptionTest,CompactionInputErrorParanoid)472 TEST_F(CorruptionTest, CompactionInputErrorParanoid) {
473   Options options;
474   options.paranoid_checks = true;
475   options.write_buffer_size = 131072;
476   options.max_write_buffer_number = 2;
477   Reopen(&options);
478   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
479 
480   // Fill levels >= 1
481   for (int level = 1; level < dbi->NumberLevels(); level++) {
482     dbi->Put(WriteOptions(), "", "begin");
483     dbi->Put(WriteOptions(), "~", "end");
484     dbi->TEST_FlushMemTable();
485     for (int comp_level = 0; comp_level < dbi->NumberLevels() - level;
486          ++comp_level) {
487       dbi->TEST_CompactRange(comp_level, nullptr, nullptr);
488     }
489   }
490 
491   Reopen(&options);
492 
493   dbi = reinterpret_cast<DBImpl*>(db_);
494   Build(10);
495   dbi->TEST_FlushMemTable();
496   dbi->TEST_WaitForCompact();
497   ASSERT_EQ(1, Property("rocksdb.num-files-at-level0"));
498 
499   CorruptTableFileAtLevel(0, 100, 1);
500   Check(9, 9);
501   ASSERT_NOK(dbi->VerifyChecksum());
502 
503   // Write must eventually fail because of corrupted table
504   Status s;
505   std::string tmp1, tmp2;
506   bool failed = false;
507   for (int i = 0; i < 10000; i++) {
508     s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2));
509     if (!s.ok()) {
510       failed = true;
511     }
512     // if one write failed, every subsequent write must fail, too
513     ASSERT_TRUE(!failed || !s.ok()) << "write did not fail in a corrupted db";
514   }
515   ASSERT_TRUE(!s.ok()) << "write did not fail in corrupted paranoid db";
516 }
517 
TEST_F(CorruptionTest,UnrelatedKeys)518 TEST_F(CorruptionTest, UnrelatedKeys) {
519   Build(10);
520   DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
521   dbi->TEST_FlushMemTable();
522   Corrupt(kTableFile, 100, 1);
523   ASSERT_NOK(dbi->VerifyChecksum());
524 
525   std::string tmp1, tmp2;
526   ASSERT_OK(db_->Put(WriteOptions(), Key(1000, &tmp1), Value(1000, &tmp2)));
527   std::string v;
528   ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
529   ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
530   dbi->TEST_FlushMemTable();
531   ASSERT_OK(db_->Get(ReadOptions(), Key(1000, &tmp1), &v));
532   ASSERT_EQ(Value(1000, &tmp2).ToString(), v);
533 }
534 
TEST_F(CorruptionTest,RangeDeletionCorrupted)535 TEST_F(CorruptionTest, RangeDeletionCorrupted) {
536   ASSERT_OK(
537       db_->DeleteRange(WriteOptions(), db_->DefaultColumnFamily(), "a", "b"));
538   ASSERT_OK(db_->Flush(FlushOptions()));
539   std::vector<LiveFileMetaData> metadata;
540   db_->GetLiveFilesMetaData(&metadata);
541   ASSERT_EQ(static_cast<size_t>(1), metadata.size());
542   std::string filename = dbname_ + metadata[0].name;
543 
544   std::unique_ptr<RandomAccessFile> file;
545   ASSERT_OK(options_.env->NewRandomAccessFile(filename, &file, EnvOptions()));
546   std::unique_ptr<RandomAccessFileReader> file_reader(
547       new RandomAccessFileReader(NewLegacyRandomAccessFileWrapper(file),
548                                  filename));
549 
550   uint64_t file_size;
551   ASSERT_OK(options_.env->GetFileSize(filename, &file_size));
552 
553   BlockHandle range_del_handle;
554   ASSERT_OK(FindMetaBlock(
555       file_reader.get(), file_size, kBlockBasedTableMagicNumber,
556       ImmutableCFOptions(options_), kRangeDelBlock, &range_del_handle));
557 
558   ASSERT_OK(TryReopen());
559   CorruptFile(filename, static_cast<int>(range_del_handle.offset()), 1);
560   ASSERT_TRUE(TryReopen().IsCorruption());
561 }
562 
TEST_F(CorruptionTest,FileSystemStateCorrupted)563 TEST_F(CorruptionTest, FileSystemStateCorrupted) {
564   for (int iter = 0; iter < 2; ++iter) {
565     Options options;
566     options.paranoid_checks = true;
567     options.create_if_missing = true;
568     Reopen(&options);
569     Build(10);
570     ASSERT_OK(db_->Flush(FlushOptions()));
571     DBImpl* dbi = reinterpret_cast<DBImpl*>(db_);
572     std::vector<LiveFileMetaData> metadata;
573     dbi->GetLiveFilesMetaData(&metadata);
574     ASSERT_GT(metadata.size(), size_t(0));
575     std::string filename = dbname_ + metadata[0].name;
576 
577     delete db_;
578     db_ = nullptr;
579 
580     if (iter == 0) {  // corrupt file size
581       std::unique_ptr<WritableFile> file;
582       env_.NewWritableFile(filename, &file, EnvOptions());
583       file->Append(Slice("corrupted sst"));
584       file.reset();
585       Status x = TryReopen(&options);
586       ASSERT_TRUE(x.IsCorruption());
587     } else {  // delete the file
588       env_.DeleteFile(filename);
589       Status x = TryReopen(&options);
590       ASSERT_TRUE(x.IsPathNotFound());
591     }
592 
593     DestroyDB(dbname_, options_);
594   }
595 }
596 
597 }  // namespace ROCKSDB_NAMESPACE
598 
main(int argc,char ** argv)599 int main(int argc, char** argv) {
600   ::testing::InitGoogleTest(&argc, argv);
601   return RUN_ALL_TESTS();
602 }
603 
604 #else
605 #include <stdio.h>
606 
main(int,char **)607 int main(int /*argc*/, char** /*argv*/) {
608   fprintf(stderr, "SKIPPED as RepairDB() is not supported in ROCKSDB_LITE\n");
609   return 0;
610 }
611 
612 #endif  // !ROCKSDB_LITE
613