Sanjay Ghemawat
12 years ago
committed by
Gavin Andresen
5 changed files with 253 additions and 5 deletions
@ -0,0 +1,238 @@
@@ -0,0 +1,238 @@
|
||||
// Copyright (c) 2012 The LevelDB Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file. See the AUTHORS file for names of contributors.
|
||||
|
||||
#include <stdio.h> |
||||
#include "db/dbformat.h" |
||||
#include "db/filename.h" |
||||
#include "db/log_reader.h" |
||||
#include "db/version_edit.h" |
||||
#include "db/write_batch_internal.h" |
||||
#include "leveldb/env.h" |
||||
#include "leveldb/iterator.h" |
||||
#include "leveldb/options.h" |
||||
#include "leveldb/status.h" |
||||
#include "leveldb/table.h" |
||||
#include "leveldb/write_batch.h" |
||||
#include "util/logging.h" |
||||
|
||||
namespace leveldb { |
||||
|
||||
namespace { |
||||
|
||||
bool GuessType(const std::string& fname, FileType* type) { |
||||
size_t pos = fname.rfind('/'); |
||||
std::string basename; |
||||
if (pos == std::string::npos) { |
||||
basename = fname; |
||||
} else { |
||||
basename = std::string(fname.data() + pos + 1, fname.size() - pos - 1); |
||||
} |
||||
uint64_t ignored; |
||||
return ParseFileName(basename, &ignored, type); |
||||
} |
||||
|
||||
// Notified when log reader encounters corruption.
|
||||
class CorruptionReporter : public log::Reader::Reporter { |
||||
public: |
||||
virtual void Corruption(size_t bytes, const Status& status) { |
||||
printf("corruption: %d bytes; %s\n", |
||||
static_cast<int>(bytes), |
||||
status.ToString().c_str()); |
||||
} |
||||
}; |
||||
|
||||
// Print contents of a log file. (*func)() is called on every record.
|
||||
bool PrintLogContents(Env* env, const std::string& fname, |
||||
void (*func)(Slice)) { |
||||
SequentialFile* file; |
||||
Status s = env->NewSequentialFile(fname, &file); |
||||
if (!s.ok()) { |
||||
fprintf(stderr, "%s\n", s.ToString().c_str()); |
||||
return false; |
||||
} |
||||
CorruptionReporter reporter; |
||||
log::Reader reader(file, &reporter, true, 0); |
||||
Slice record; |
||||
std::string scratch; |
||||
while (reader.ReadRecord(&record, &scratch)) { |
||||
printf("--- offset %llu; ", |
||||
static_cast<unsigned long long>(reader.LastRecordOffset())); |
||||
(*func)(record); |
||||
} |
||||
delete file; |
||||
return true; |
||||
} |
||||
|
||||
// Called on every item found in a WriteBatch.
|
||||
class WriteBatchItemPrinter : public WriteBatch::Handler { |
||||
public: |
||||
uint64_t offset_; |
||||
uint64_t sequence_; |
||||
|
||||
virtual void Put(const Slice& key, const Slice& value) { |
||||
printf(" put '%s' '%s'\n", |
||||
EscapeString(key).c_str(), |
||||
EscapeString(value).c_str()); |
||||
} |
||||
virtual void Delete(const Slice& key) { |
||||
printf(" del '%s'\n", |
||||
EscapeString(key).c_str()); |
||||
} |
||||
}; |
||||
|
||||
|
||||
// Called on every log record (each one of which is a WriteBatch)
|
||||
// found in a kLogFile.
|
||||
static void WriteBatchPrinter(Slice record) { |
||||
if (record.size() < 12) { |
||||
printf("log record length %d is too small\n", |
||||
static_cast<int>(record.size())); |
||||
return; |
||||
} |
||||
WriteBatch batch; |
||||
WriteBatchInternal::SetContents(&batch, record); |
||||
printf("sequence %llu\n", |
||||
static_cast<unsigned long long>(WriteBatchInternal::Sequence(&batch))); |
||||
WriteBatchItemPrinter batch_item_printer; |
||||
Status s = batch.Iterate(&batch_item_printer); |
||||
if (!s.ok()) { |
||||
printf(" error: %s\n", s.ToString().c_str()); |
||||
} |
||||
} |
||||
|
||||
bool DumpLog(Env* env, const std::string& fname) { |
||||
return PrintLogContents(env, fname, WriteBatchPrinter); |
||||
} |
||||
|
||||
// Called on every log record (each one of which is a WriteBatch)
|
||||
// found in a kDescriptorFile.
|
||||
static void VersionEditPrinter(Slice record) { |
||||
VersionEdit edit; |
||||
Status s = edit.DecodeFrom(record); |
||||
if (!s.ok()) { |
||||
printf("%s\n", s.ToString().c_str()); |
||||
return; |
||||
} |
||||
printf("%s", edit.DebugString().c_str()); |
||||
} |
||||
|
||||
bool DumpDescriptor(Env* env, const std::string& fname) { |
||||
return PrintLogContents(env, fname, VersionEditPrinter); |
||||
} |
||||
|
||||
bool DumpTable(Env* env, const std::string& fname) { |
||||
uint64_t file_size; |
||||
RandomAccessFile* file = NULL; |
||||
Table* table = NULL; |
||||
Status s = env->GetFileSize(fname, &file_size); |
||||
if (s.ok()) { |
||||
s = env->NewRandomAccessFile(fname, &file); |
||||
} |
||||
if (s.ok()) { |
||||
// We use the default comparator, which may or may not match the
|
||||
// comparator used in this database. However this should not cause
|
||||
// problems since we only use Table operations that do not require
|
||||
// any comparisons. In particular, we do not call Seek or Prev.
|
||||
s = Table::Open(Options(), file, file_size, &table); |
||||
} |
||||
if (!s.ok()) { |
||||
fprintf(stderr, "%s\n", s.ToString().c_str()); |
||||
delete table; |
||||
delete file; |
||||
return false; |
||||
} |
||||
|
||||
ReadOptions ro; |
||||
ro.fill_cache = false; |
||||
Iterator* iter = table->NewIterator(ro); |
||||
for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { |
||||
ParsedInternalKey key; |
||||
if (!ParseInternalKey(iter->key(), &key)) { |
||||
printf("badkey '%s' => '%s'\n", |
||||
EscapeString(iter->key()).c_str(), |
||||
EscapeString(iter->value()).c_str()); |
||||
} else { |
||||
char kbuf[20]; |
||||
const char* type; |
||||
if (key.type == kTypeDeletion) { |
||||
type = "del"; |
||||
} else if (key.type == kTypeValue) { |
||||
type = "val"; |
||||
} else { |
||||
snprintf(kbuf, sizeof(kbuf), "%d", static_cast<int>(key.type)); |
||||
type = kbuf; |
||||
} |
||||
printf("'%s' @ %8llu : %s => '%s'\n", |
||||
EscapeString(key.user_key).c_str(), |
||||
static_cast<unsigned long long>(key.sequence), |
||||
type, |
||||
EscapeString(iter->value()).c_str()); |
||||
} |
||||
} |
||||
s = iter->status(); |
||||
if (!s.ok()) { |
||||
printf("iterator error: %s\n", s.ToString().c_str()); |
||||
} |
||||
|
||||
delete iter; |
||||
delete table; |
||||
delete file; |
||||
return true; |
||||
} |
||||
|
||||
bool DumpFile(Env* env, const std::string& fname) { |
||||
FileType ftype; |
||||
if (!GuessType(fname, &ftype)) { |
||||
fprintf(stderr, "%s: unknown file type\n", fname.c_str()); |
||||
return false; |
||||
} |
||||
switch (ftype) { |
||||
case kLogFile: return DumpLog(env, fname); |
||||
case kDescriptorFile: return DumpDescriptor(env, fname); |
||||
case kTableFile: return DumpTable(env, fname); |
||||
|
||||
default: { |
||||
fprintf(stderr, "%s: not a dump-able file type\n", fname.c_str()); |
||||
break; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
bool HandleDumpCommand(Env* env, char** files, int num) { |
||||
bool ok = true; |
||||
for (int i = 0; i < num; i++) { |
||||
ok &= DumpFile(env, files[i]); |
||||
} |
||||
return ok; |
||||
} |
||||
|
||||
} |
||||
} // namespace leveldb
|
||||
|
||||
static void Usage() { |
||||
fprintf( |
||||
stderr, |
||||
"Usage: leveldbutil command...\n" |
||||
" dump files... -- dump contents of specified files\n" |
||||
); |
||||
} |
||||
|
||||
int main(int argc, char** argv) { |
||||
leveldb::Env* env = leveldb::Env::Default(); |
||||
bool ok = true; |
||||
if (argc < 2) { |
||||
Usage(); |
||||
ok = false; |
||||
} else { |
||||
std::string command = argv[1]; |
||||
if (command == "dump") { |
||||
ok = leveldb::HandleDumpCommand(env, argv+2, argc-2); |
||||
} else { |
||||
Usage(); |
||||
ok = false; |
||||
} |
||||
} |
||||
return (ok ? 0 : 1); |
||||
} |
Loading…
Reference in new issue