226 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			226 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // 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:
 | |
|   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 = 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()) {
 | |
|     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
 |