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