1// Copyright (c) 2012 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 <stdio.h>
6#include "db/dbformat.h"
7#include "db/filename.h"
8#include "db/log_reader.h"
9#include "db/version_edit.h"
10#include "db/write_batch_internal.h"
11#include "leveldb/env.h"
12#include "leveldb/iterator.h"
13#include "leveldb/options.h"
14#include "leveldb/status.h"
15#include "leveldb/table.h"
16#include "leveldb/write_batch.h"
17#include "util/logging.h"
18
19namespace leveldb {
20
21namespace {
22
23bool GuessType(const std::string& fname, FileType* type) {
24  size_t pos = fname.rfind('/');
25  std::string basename;
26  if (pos == std::string::npos) {
27    basename = fname;
28  } else {
29    basename = std::string(fname.data() + pos + 1, fname.size() - pos - 1);
30  }
31  uint64_t ignored;
32  return ParseFileName(basename, &ignored, type);
33}
34
35// Notified when log reader encounters corruption.
36class CorruptionReporter : public log::Reader::Reporter {
37 public:
38  virtual void Corruption(size_t bytes, const Status& status) {
39    printf("corruption: %d bytes; %s\n",
40            static_cast<int>(bytes),
41            status.ToString().c_str());
42  }
43};
44
45// Print contents of a log file. (*func)() is called on every record.
46bool PrintLogContents(Env* env, const std::string& fname,
47                      void (*func)(Slice)) {
48  SequentialFile* file;
49  Status s = env->NewSequentialFile(fname, &file);
50  if (!s.ok()) {
51    fprintf(stderr, "%s\n", s.ToString().c_str());
52    return false;
53  }
54  CorruptionReporter reporter;
55  log::Reader reader(file, &reporter, true, 0);
56  Slice record;
57  std::string scratch;
58  while (reader.ReadRecord(&record, &scratch)) {
59    printf("--- offset %llu; ",
60           static_cast<unsigned long long>(reader.LastRecordOffset()));
61    (*func)(record);
62  }
63  delete file;
64  return true;
65}
66
67// Called on every item found in a WriteBatch.
68class WriteBatchItemPrinter : public WriteBatch::Handler {
69 public:
70  uint64_t offset_;
71  uint64_t sequence_;
72
73  virtual void Put(const Slice& key, const Slice& value) {
74    printf("  put '%s' '%s'\n",
75           EscapeString(key).c_str(),
76           EscapeString(value).c_str());
77  }
78  virtual void Delete(const Slice& key) {
79    printf("  del '%s'\n",
80           EscapeString(key).c_str());
81  }
82};
83
84
85// Called on every log record (each one of which is a WriteBatch)
86// found in a kLogFile.
87static void WriteBatchPrinter(Slice record) {
88  if (record.size() < 12) {
89    printf("log record length %d is too small\n",
90           static_cast<int>(record.size()));
91    return;
92  }
93  WriteBatch batch;
94  WriteBatchInternal::SetContents(&batch, record);
95  printf("sequence %llu\n",
96         static_cast<unsigned long long>(WriteBatchInternal::Sequence(&batch)));
97  WriteBatchItemPrinter batch_item_printer;
98  Status s = batch.Iterate(&batch_item_printer);
99  if (!s.ok()) {
100    printf("  error: %s\n", s.ToString().c_str());
101  }
102}
103
104bool DumpLog(Env* env, const std::string& fname) {
105  return PrintLogContents(env, fname, WriteBatchPrinter);
106}
107
108// Called on every log record (each one of which is a WriteBatch)
109// found in a kDescriptorFile.
110static void VersionEditPrinter(Slice record) {
111  VersionEdit edit;
112  Status s = edit.DecodeFrom(record);
113  if (!s.ok()) {
114    printf("%s\n", s.ToString().c_str());
115    return;
116  }
117  printf("%s", edit.DebugString().c_str());
118}
119
120bool DumpDescriptor(Env* env, const std::string& fname) {
121  return PrintLogContents(env, fname, VersionEditPrinter);
122}
123
124bool DumpTable(Env* env, const std::string& fname) {
125  uint64_t file_size;
126  RandomAccessFile* file = NULL;
127  Table* table = NULL;
128  Status s = env->GetFileSize(fname, &file_size);
129  if (s.ok()) {
130    s = env->NewRandomAccessFile(fname, &file);
131  }
132  if (s.ok()) {
133    // We use the default comparator, which may or may not match the
134    // comparator used in this database. However this should not cause
135    // problems since we only use Table operations that do not require
136    // any comparisons.  In particular, we do not call Seek or Prev.
137    s = Table::Open(Options(), file, file_size, &table);
138  }
139  if (!s.ok()) {
140    fprintf(stderr, "%s\n", s.ToString().c_str());
141    delete table;
142    delete file;
143    return false;
144  }
145
146  ReadOptions ro;
147  ro.fill_cache = false;
148  Iterator* iter = table->NewIterator(ro);
149  for (iter->SeekToFirst(); iter->Valid(); iter->Next()) {
150    ParsedInternalKey key;
151    if (!ParseInternalKey(iter->key(), &key)) {
152      printf("badkey '%s' => '%s'\n",
153             EscapeString(iter->key()).c_str(),
154             EscapeString(iter->value()).c_str());
155    } else {
156      char kbuf[20];
157      const char* type;
158      if (key.type == kTypeDeletion) {
159        type = "del";
160      } else if (key.type == kTypeValue) {
161        type = "val";
162      } else {
163        snprintf(kbuf, sizeof(kbuf), "%d", static_cast<int>(key.type));
164        type = kbuf;
165      }
166      printf("'%s' @ %8llu : %s => '%s'\n",
167             EscapeString(key.user_key).c_str(),
168             static_cast<unsigned long long>(key.sequence),
169             type,
170             EscapeString(iter->value()).c_str());
171    }
172  }
173  s = iter->status();
174  if (!s.ok()) {
175    printf("iterator error: %s\n", s.ToString().c_str());
176  }
177
178  delete iter;
179  delete table;
180  delete file;
181  return true;
182}
183
184bool DumpFile(Env* env, const std::string& fname) {
185  FileType ftype;
186  if (!GuessType(fname, &ftype)) {
187    fprintf(stderr, "%s: unknown file type\n", fname.c_str());
188    return false;
189  }
190  switch (ftype) {
191    case kLogFile:         return DumpLog(env, fname);
192    case kDescriptorFile:  return DumpDescriptor(env, fname);
193    case kTableFile:       return DumpTable(env, fname);
194
195    default: {
196      fprintf(stderr, "%s: not a dump-able file type\n", fname.c_str());
197      break;
198    }
199  }
200  return false;
201}
202
203bool HandleDumpCommand(Env* env, char** files, int num) {
204  bool ok = true;
205  for (int i = 0; i < num; i++) {
206    ok &= DumpFile(env, files[i]);
207  }
208  return ok;
209}
210
211}
212}  // namespace leveldb
213
214static void Usage() {
215  fprintf(
216      stderr,
217      "Usage: leveldbutil command...\n"
218      "   dump files...         -- dump contents of specified files\n"
219      );
220}
221
222int main(int argc, char** argv) {
223  leveldb::Env* env = leveldb::Env::Default();
224  bool ok = true;
225  if (argc < 2) {
226    Usage();
227    ok = false;
228  } else {
229    std::string command = argv[1];
230    if (command == "dump") {
231      ok = leveldb::HandleDumpCommand(env, argv+2, argc-2);
232    } else {
233      Usage();
234      ok = false;
235    }
236  }
237  return (ok ? 0 : 1);
238}
239