// 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 "leveldb/dumpfile.h" #include #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: WritableFile* dst_; virtual void Corruption(size_t bytes, const Status& status) { std::string r = "corruption: "; AppendNumberTo(&r, bytes); r += " bytes; "; r += status.ToString(); r.push_back('\n'); dst_->Append(r); } }; // Print contents of a log file. (*func)() is called on every record. Status PrintLogContents(Env* env, const std::string& fname, void (*func)(uint64_t, Slice, WritableFile*), WritableFile* dst) { SequentialFile* file; Status s = env->NewSequentialFile(fname, &file); if (!s.ok()) { return s; } CorruptionReporter reporter; reporter.dst_ = dst; log::Reader reader(file, &reporter, true, 0); Slice record; std::string scratch; while (reader.ReadRecord(&record, &scratch)) { (*func)(reader.LastRecordOffset(), record, dst); } delete file; return Status::OK(); } // Called on every item found in a WriteBatch. class WriteBatchItemPrinter : public WriteBatch::Handler { public: WritableFile* dst_; virtual void Put(const Slice& key, const Slice& value) { std::string r = " put '"; AppendEscapedStringTo(&r, key); r += "' '"; AppendEscapedStringTo(&r, value); r += "'\n"; dst_->Append(r); } virtual void Delete(const Slice& key) { std::string r = " del '"; AppendEscapedStringTo(&r, key); r += "'\n"; dst_->Append(r); } }; // Called on every log record (each one of which is a WriteBatch) // found in a kLogFile. static void WriteBatchPrinter(uint64_t pos, Slice record, WritableFile* dst) { std::string r = "--- offset "; AppendNumberTo(&r, pos); r += "; "; if (record.size() < 12) { r += "log record length "; AppendNumberTo(&r, record.size()); r += " is too small\n"; dst->Append(r); return; } WriteBatch batch; WriteBatchInternal::SetContents(&batch, record); r += "sequence "; AppendNumberTo(&r, WriteBatchInternal::Sequence(&batch)); r.push_back('\n'); dst->Append(r); WriteBatchItemPrinter batch_item_printer; batch_item_printer.dst_ = dst; Status s = batch.Iterate(&batch_item_printer); if (!s.ok()) { dst->Append(" error: " + s.ToString() + "\n"); } } Status DumpLog(Env* env, const std::string& fname, WritableFile* dst) { return PrintLogContents(env, fname, WriteBatchPrinter, dst); } // Called on every log record (each one of which is a WriteBatch) // found in a kDescriptorFile. static void VersionEditPrinter(uint64_t pos, Slice record, WritableFile* dst) { std::string r = "--- offset "; AppendNumberTo(&r, pos); r += "; "; VersionEdit edit; Status s = edit.DecodeFrom(record); if (!s.ok()) { r += s.ToString(); r.push_back('\n'); } else { r += edit.DebugString(); } dst->Append(r); } Status DumpDescriptor(Env* env, const std::string& fname, WritableFile* dst) { return PrintLogContents(env, fname, VersionEditPrinter, dst); } Status DumpTable(Env* env, const std::string& fname, WritableFile* dst) { uint64_t file_size; RandomAccessFile* file = nullptr; Table* table = nullptr; 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()) { delete table; delete file; return s; } ReadOptions ro; ro.fill_cache = false; Iterator* iter = table->NewIterator(ro); std::string r; for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { r.clear(); ParsedInternalKey key; if (!ParseInternalKey(iter->key(), &key)) { r = "badkey '"; AppendEscapedStringTo(&r, iter->key()); r += "' => '"; AppendEscapedStringTo(&r, iter->value()); r += "'\n"; dst->Append(r); } else { r = "'"; AppendEscapedStringTo(&r, key.user_key); r += "' @ "; AppendNumberTo(&r, key.sequence); r += " : "; if (key.type == kTypeDeletion) { r += "del"; } else if (key.type == kTypeValue) { r += "val"; } else { AppendNumberTo(&r, key.type); } r += " => '"; AppendEscapedStringTo(&r, iter->value()); r += "'\n"; dst->Append(r); } } s = iter->status(); if (!s.ok()) { dst->Append("iterator error: " + s.ToString() + "\n"); } delete iter; delete table; delete file; return Status::OK(); } } // namespace Status DumpFile(Env* env, const std::string& fname, WritableFile* dst) { FileType ftype; if (!GuessType(fname, &ftype)) { return Status::InvalidArgument(fname + ": unknown file type"); } switch (ftype) { case kLogFile: return DumpLog(env, fname, dst); case kDescriptorFile: return DumpDescriptor(env, fname, dst); case kTableFile: return DumpTable(env, fname, dst); default: break; } return Status::InvalidArgument(fname + ": not a dump-able file type"); } } // namespace leveldb