text: // Copyright (c) 2011 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 "helpers/memenv/memenv.h"
  
  #include <string.h>
  
  #include <limits>
  #include <map>
  #include <string>
  #include <vector>
  
  #include "leveldb/env.h"
  #include "leveldb/status.h"
  #include "port/port.h"
  #include "port/thread_annotations.h"
  #include "util/mutexlock.h"
  
  namespace leveldb {
  
  namespace {
  
  class FileState {
   public:
    // FileStates are reference counted. The initial reference count is zero
    // and the caller must call Ref() at least once.
    FileState() : refs_(0), size_(0) {}
  
    // Increase the reference count.
    void Ref() {
      MutexLock lock(&refs_mutex_);
      ++refs_;
    }
  
    // Decrease the reference count. Delete if this is the last reference.
    void Unref() {
      bool do_delete = false;
  
      {
        MutexLock lock(&refs_mutex_);
        --refs_;
        assert(refs_ >= 0);
        if (refs_ <= 0) {
          do_delete = true;
        }
      }
  
      if (do_delete) {
        delete this;
      }
    }
  
    uint64_t Size() const { return size_; }
  
    Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const {
      if (offset > size_) {
        return Status::IOError("Offset greater than file size.");
      }
      const uint64_t available = size_ - offset;
      if (n > available) {
        n = static_cast<size_t>(available);
      }
      if (n == 0) {
        *result = Slice();
        return Status::OK();
      }
  
      assert(offset / kBlockSize <= std::numeric_limits<size_t>::max());
      size_t block = static_cast<size_t>(offset / kBlockSize);
      size_t block_offset = offset % kBlockSize;
  
      if (n <= kBlockSize - block_offset) {
        // The requested bytes are all in the first block.
        *result = Slice(blocks_[block] + block_offset, n);
        return Status::OK();
      }
  
      size_t bytes_to_copy = n;
      char* dst = scratch;
  
      while (bytes_to_copy > 0) {
        size_t avail = kBlockSize - block_offset;
        if (avail > bytes_to_copy) {
          avail = bytes_to_copy;
        }
        memcpy(dst, blocks_[block] + block_offset, avail);
  
        bytes_to_copy -= avail;
        dst += avail;
        block++;
        block_offset = 0;
      }
  
      *result = Slice(scratch, n);
      return Status::OK();
    }
  
    Status Append(const Slice& data) {
      const char* src = data.data();
      size_t src_len = data.size();
  
      while (src_len > 0) {
        size_t avail;
        size_t offset = size_ % kBlockSize;
  
        if (offset != 0) {
          // There is some room in the last block.
          avail = kBlockSize - offset;
        } else {
          // No room in the last block; push new one.
          blocks_.push_back(new char[kBlockSize]);
          avail = kBlockSize;
        }
  
        if (avail > src_len) {
          avail = src_len;
        }
        memcpy(blocks_.back() + offset, src, avail);
        src_len -= avail;
        src += avail;
        size_ += avail;
      }
  
      return Status::OK();
    }
  
   private:
    // Private since only Unref() should be used to delete it.
    ~FileState() {
      for (std::vector<char*>::iterator i = blocks_.begin(); i != blocks_.end();
           ++i) {
        delete [] *i;
      }
    }
  
    // No copying allowed.
    FileState(const FileState&);
    void operator=(const FileState&);
  
    port::Mutex refs_mutex_;
    int refs_ GUARDED_BY(refs_mutex_);
  
    // The following fields are not protected by any mutex. They are only mutable
    // while the file is being written, and concurrent access is not allowed
    // to writable files.
    std::vector<char*> blocks_;
    uint64_t size_;
  
    enum { kBlockSize = 8 * 1024 };
  };
  
  class SequentialFileImpl : public SequentialFile {
   public:
    explicit SequentialFileImpl(FileState* file) : file_(file), pos_(0) {
      file_->Ref();
    }
  
    ~SequentialFileImpl() {
      file_->Unref();
    }
  
    virtual Status Read(size_t n, Slice* result, char* scratch) {
      Status s = file_->Read(pos_, n, result, scratch);
      if (s.ok()) {
        pos_ += result->size();
      }
      return s;
    }
  
    virtual Status Skip(uint64_t n) {
      if (pos_ > file_->Size()) {
        return Status::IOError("pos_ > file_->Size()");
      }
      const uint64_t available = file_->Size() - pos_;
      if (n > available) {
        n = available;
      }
      pos_ += n;
      return Status::OK();
    }
  
   private:
    FileState* file_;
    uint64_t pos_;
  };
  
  class RandomAccessFileImpl : public RandomAccessFile {
   public:
    explicit RandomAccessFileImpl(FileState* file) : file_(file) {
      file_->Ref();
    }
  
    ~RandomAccessFileImpl() {
      file_->Unref();
    }
  
    virtual Status Read(uint64_t offset, size_t n, Slice* result,
                        char* scratch) const {
      return file_->Read(offset, n, result, scratch);
    }
  
   private:
    FileState* file_;
  };
  
  class WritableFileImpl : public WritableFile {
   public:
    WritableFileImpl(FileState* file) : file_(file) {
      file_->Ref();
    }
  
    ~WritableFileImpl() {
      file_->Unref();
    }
  
    virtual Status Append(const Slice& data) {
      return file_->Append(data);
    }
  
    virtual Status Close() { return Status::OK(); }
    virtual Status Flush() { return Status::OK(); }
    virtual Status Sync() { return Status::OK(); }
  
   private:
    FileState* file_;
  };
  
  class NoOpLogger : public Logger {
   public:
    virtual void Logv(const char* format, va_list ap) { }
  };
  
  class InMemoryEnv : public EnvWrapper {
   public:
    explicit InMemoryEnv(Env* base_env) : EnvWrapper(base_env) { }
  
    virtual ~InMemoryEnv() {
      for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i){
        i->second->Unref();
      }
    }
  
    // Partial implementation of the Env interface.
    virtual Status NewSequentialFile(const std::string& fname,
                                     SequentialFile** result) {
      MutexLock lock(&mutex_);
      if (file_map_.find(fname) == file_map_.end()) {
        *result = nullptr;
        return Status::IOError(fname, "File not found");
      }
  
      *result = new SequentialFileImpl(file_map_[fname]);
      return Status::OK();
    }
  
    virtual Status NewRandomAccessFile(const std::string& fname,
                                       RandomAccessFile** result) {
      MutexLock lock(&mutex_);
      if (file_map_.find(fname) == file_map_.end()) {
        *result = nullptr;
        return Status::IOError(fname, "File not found");
      }
  
      *result = new RandomAccessFileImpl(file_map_[fname]);
      return Status::OK();
    }
  
    virtual Status NewWritableFile(const std::string& fname,
                                   WritableFile** result) {
      MutexLock lock(&mutex_);
      if (file_map_.find(fname) != file_map_.end()) {
        DeleteFileInternal(fname);
      }
  
      FileState* file = new FileState();
      file->Ref();
      file_map_[fname] = file;
  
      *result = new WritableFileImpl(file);
      return Status::OK();
    }
  
    virtual Status NewAppendableFile(const std::string& fname,
                                     WritableFile** result) {
      MutexLock lock(&mutex_);
      FileState** sptr = &file_map_[fname];
      FileState* file = *sptr;
      if (file == nullptr) {
        file = new FileState();
        file->Ref();
      }
      *result = new WritableFileImpl(file);
      return Status::OK();
    }
  
    virtual bool FileExists(const std::string& fname) {
      MutexLock lock(&mutex_);
      return file_map_.find(fname) != file_map_.end();
    }
  
    virtual Status GetChildren(const std::string& dir,
                               std::vector<std::string>* result) {
      MutexLock lock(&mutex_);
      result->clear();
  
      for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i){
        const std::string& filename = i->first;
  
        if (filename.size() >= dir.size() + 1 && filename[dir.size()] == '/' &&
            Slice(filename).starts_with(Slice(dir))) {
          result->push_back(filename.substr(dir.size() + 1));
        }
      }
  
      return Status::OK();
    }
  
    void DeleteFileInternal(const std::string& fname)
        EXCLUSIVE_LOCKS_REQUIRED(mutex_) {
      if (file_map_.find(fname) == file_map_.end()) {
        return;
      }
  
      file_map_[fname]->Unref();
      file_map_.erase(fname);
    }
  
    virtual Status DeleteFile(const std::string& fname) {
      MutexLock lock(&mutex_);
      if (file_map_.find(fname) == file_map_.end()) {
        return Status::IOError(fname, "File not found");
      }
  
      DeleteFileInternal(fname);
      return Status::OK();
    }
  
    virtual Status CreateDir(const std::string& dirname) {
      return Status::OK();
    }
  
    virtual Status DeleteDir(const std::string& dirname) {
      return Status::OK();
    }
  
    virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) {
      MutexLock lock(&mutex_);
      if (file_map_.find(fname) == file_map_.end()) {
        return Status::IOError(fname, "File not found");
      }
  
      *file_size = file_map_[fname]->Size();
      return Status::OK();
    }
  
    virtual Status RenameFile(const std::string& src,
                              const std::string& target) {
      MutexLock lock(&mutex_);
      if (file_map_.find(src) == file_map_.end()) {
        return Status::IOError(src, "File not found");
      }
  
      DeleteFileInternal(target);
      file_map_[target] = file_map_[src];
      file_map_.erase(src);
      return Status::OK();
    }
  
    virtual Status LockFile(const std::string& fname, FileLock** lock) {
      *lock = new FileLock;
      return Status::OK();
    }
  
    virtual Status UnlockFile(FileLock* lock) {
      delete lock;
      return Status::OK();
    }
  
    virtual Status GetTestDirectory(std::string* path) {
      *path = "/test";
      return Status::OK();
    }
  
    virtual Status NewLogger(const std::string& fname, Logger** result) {
      *result = new NoOpLogger;
      return Status::OK();
    }
  
   private:
    // Map from filenames to FileState objects, representing a simple file system.
    typedef std::map<std::string, FileState*> FileSystem;
    port::Mutex mutex_;
    FileSystem file_map_ GUARDED_BY(mutex_);
  };
  
  }  // namespace
  
  Env* NewMemEnv(Env* base_env) {
    return new InMemoryEnv(base_env);
  }
  
  }  // namespace leveldb
  