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