1// Copyright (c) 2011 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 "helpers/memenv/memenv.h"
6
7#include "leveldb/env.h"
8#include "leveldb/status.h"
9#include "port/port.h"
10#include "util/mutexlock.h"
11#include <map>
12#include <string.h>
13#include <string>
14#include <vector>
15
16namespace leveldb {
17
18namespace {
19
20class FileState {
21 public:
22  // FileStates are reference counted. The initial reference count is zero
23  // and the caller must call Ref() at least once.
24  FileState() : refs_(0), size_(0) {}
25
26  // Increase the reference count.
27  void Ref() {
28    MutexLock lock(&refs_mutex_);
29    ++refs_;
30  }
31
32  // Decrease the reference count. Delete if this is the last reference.
33  void Unref() {
34    bool do_delete = false;
35
36    {
37      MutexLock lock(&refs_mutex_);
38      --refs_;
39      assert(refs_ >= 0);
40      if (refs_ <= 0) {
41        do_delete = true;
42      }
43    }
44
45    if (do_delete) {
46      delete this;
47    }
48  }
49
50  uint64_t Size() const { return size_; }
51
52  Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const {
53    if (offset > size_) {
54      return Status::IOError("Offset greater than file size.");
55    }
56    const uint64_t available = size_ - offset;
57    if (n > available) {
58      n = available;
59    }
60    if (n == 0) {
61      *result = Slice();
62      return Status::OK();
63    }
64
65    size_t block = offset / kBlockSize;
66    size_t block_offset = offset % kBlockSize;
67
68    if (n <= kBlockSize - block_offset) {
69      // The requested bytes are all in the first block.
70      *result = Slice(blocks_[block] + block_offset, n);
71      return Status::OK();
72    }
73
74    size_t bytes_to_copy = n;
75    char* dst = scratch;
76
77    while (bytes_to_copy > 0) {
78      size_t avail = kBlockSize - block_offset;
79      if (avail > bytes_to_copy) {
80        avail = bytes_to_copy;
81      }
82      memcpy(dst, blocks_[block] + block_offset, avail);
83
84      bytes_to_copy -= avail;
85      dst += avail;
86      block++;
87      block_offset = 0;
88    }
89
90    *result = Slice(scratch, n);
91    return Status::OK();
92  }
93
94  Status Append(const Slice& data) {
95    const char* src = data.data();
96    size_t src_len = data.size();
97
98    while (src_len > 0) {
99      size_t avail;
100      size_t offset = size_ % kBlockSize;
101
102      if (offset != 0) {
103        // There is some room in the last block.
104        avail = kBlockSize - offset;
105      } else {
106        // No room in the last block; push new one.
107        blocks_.push_back(new char[kBlockSize]);
108        avail = kBlockSize;
109      }
110
111      if (avail > src_len) {
112        avail = src_len;
113      }
114      memcpy(blocks_.back() + offset, src, avail);
115      src_len -= avail;
116      src += avail;
117      size_ += avail;
118    }
119
120    return Status::OK();
121  }
122
123 private:
124  // Private since only Unref() should be used to delete it.
125  ~FileState() {
126    for (std::vector<char*>::iterator i = blocks_.begin(); i != blocks_.end();
127         ++i) {
128      delete [] *i;
129    }
130  }
131
132  // No copying allowed.
133  FileState(const FileState&);
134  void operator=(const FileState&);
135
136  port::Mutex refs_mutex_;
137  int refs_;  // Protected by refs_mutex_;
138
139  // The following fields are not protected by any mutex. They are only mutable
140  // while the file is being written, and concurrent access is not allowed
141  // to writable files.
142  std::vector<char*> blocks_;
143  uint64_t size_;
144
145  enum { kBlockSize = 8 * 1024 };
146};
147
148class SequentialFileImpl : public SequentialFile {
149 public:
150  explicit SequentialFileImpl(FileState* file) : file_(file), pos_(0) {
151    file_->Ref();
152  }
153
154  ~SequentialFileImpl() {
155    file_->Unref();
156  }
157
158  virtual Status Read(size_t n, Slice* result, char* scratch) {
159    Status s = file_->Read(pos_, n, result, scratch);
160    if (s.ok()) {
161      pos_ += result->size();
162    }
163    return s;
164  }
165
166  virtual Status Skip(uint64_t n) {
167    if (pos_ > file_->Size()) {
168      return Status::IOError("pos_ > file_->Size()");
169    }
170    const size_t available = file_->Size() - pos_;
171    if (n > available) {
172      n = available;
173    }
174    pos_ += n;
175    return Status::OK();
176  }
177
178 private:
179  FileState* file_;
180  size_t pos_;
181};
182
183class RandomAccessFileImpl : public RandomAccessFile {
184 public:
185  explicit RandomAccessFileImpl(FileState* file) : file_(file) {
186    file_->Ref();
187  }
188
189  ~RandomAccessFileImpl() {
190    file_->Unref();
191  }
192
193  virtual Status Read(uint64_t offset, size_t n, Slice* result,
194                      char* scratch) const {
195    return file_->Read(offset, n, result, scratch);
196  }
197
198 private:
199  FileState* file_;
200};
201
202class WritableFileImpl : public WritableFile {
203 public:
204  WritableFileImpl(FileState* file) : file_(file) {
205    file_->Ref();
206  }
207
208  ~WritableFileImpl() {
209    file_->Unref();
210  }
211
212  virtual Status Append(const Slice& data) {
213    return file_->Append(data);
214  }
215
216  virtual Status Close() { return Status::OK(); }
217  virtual Status Flush() { return Status::OK(); }
218  virtual Status Sync() { return Status::OK(); }
219
220 private:
221  FileState* file_;
222};
223
224class NoOpLogger : public Logger {
225 public:
226  virtual void Logv(const char* format, va_list ap) { }
227};
228
229class InMemoryEnv : public EnvWrapper {
230 public:
231  explicit InMemoryEnv(Env* base_env) : EnvWrapper(base_env) { }
232
233  virtual ~InMemoryEnv() {
234    for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i){
235      i->second->Unref();
236    }
237  }
238
239  // Partial implementation of the Env interface.
240  virtual Status NewSequentialFile(const std::string& fname,
241                                   SequentialFile** result) {
242    MutexLock lock(&mutex_);
243    if (file_map_.find(fname) == file_map_.end()) {
244      *result = NULL;
245      return Status::IOError(fname, "File not found");
246    }
247
248    *result = new SequentialFileImpl(file_map_[fname]);
249    return Status::OK();
250  }
251
252  virtual Status NewRandomAccessFile(const std::string& fname,
253                                     RandomAccessFile** result) {
254    MutexLock lock(&mutex_);
255    if (file_map_.find(fname) == file_map_.end()) {
256      *result = NULL;
257      return Status::IOError(fname, "File not found");
258    }
259
260    *result = new RandomAccessFileImpl(file_map_[fname]);
261    return Status::OK();
262  }
263
264  virtual Status NewWritableFile(const std::string& fname,
265                                 WritableFile** result) {
266    MutexLock lock(&mutex_);
267    if (file_map_.find(fname) != file_map_.end()) {
268      DeleteFileInternal(fname);
269    }
270
271    FileState* file = new FileState();
272    file->Ref();
273    file_map_[fname] = file;
274
275    *result = new WritableFileImpl(file);
276    return Status::OK();
277  }
278
279  virtual bool FileExists(const std::string& fname) {
280    MutexLock lock(&mutex_);
281    return file_map_.find(fname) != file_map_.end();
282  }
283
284  virtual Status GetChildren(const std::string& dir,
285                             std::vector<std::string>* result) {
286    MutexLock lock(&mutex_);
287    result->clear();
288
289    for (FileSystem::iterator i = file_map_.begin(); i != file_map_.end(); ++i){
290      const std::string& filename = i->first;
291
292      if (filename.size() >= dir.size() + 1 && filename[dir.size()] == '/' &&
293          Slice(filename).starts_with(Slice(dir))) {
294        result->push_back(filename.substr(dir.size() + 1));
295      }
296    }
297
298    return Status::OK();
299  }
300
301  void DeleteFileInternal(const std::string& fname) {
302    if (file_map_.find(fname) == file_map_.end()) {
303      return;
304    }
305
306    file_map_[fname]->Unref();
307    file_map_.erase(fname);
308  }
309
310  virtual Status DeleteFile(const std::string& fname) {
311    MutexLock lock(&mutex_);
312    if (file_map_.find(fname) == file_map_.end()) {
313      return Status::IOError(fname, "File not found");
314    }
315
316    DeleteFileInternal(fname);
317    return Status::OK();
318  }
319
320  virtual Status CreateDir(const std::string& dirname) {
321    return Status::OK();
322  }
323
324  virtual Status DeleteDir(const std::string& dirname) {
325    return Status::OK();
326  }
327
328  virtual Status GetFileSize(const std::string& fname, uint64_t* file_size) {
329    MutexLock lock(&mutex_);
330    if (file_map_.find(fname) == file_map_.end()) {
331      return Status::IOError(fname, "File not found");
332    }
333
334    *file_size = file_map_[fname]->Size();
335    return Status::OK();
336  }
337
338  virtual Status RenameFile(const std::string& src,
339                            const std::string& target) {
340    MutexLock lock(&mutex_);
341    if (file_map_.find(src) == file_map_.end()) {
342      return Status::IOError(src, "File not found");
343    }
344
345    DeleteFileInternal(target);
346    file_map_[target] = file_map_[src];
347    file_map_.erase(src);
348    return Status::OK();
349  }
350
351  virtual Status LockFile(const std::string& fname, FileLock** lock) {
352    *lock = new FileLock;
353    return Status::OK();
354  }
355
356  virtual Status UnlockFile(FileLock* lock) {
357    delete lock;
358    return Status::OK();
359  }
360
361  virtual Status GetTestDirectory(std::string* path) {
362    *path = "/test";
363    return Status::OK();
364  }
365
366  virtual Status NewLogger(const std::string& fname, Logger** result) {
367    *result = new NoOpLogger;
368    return Status::OK();
369  }
370
371 private:
372  // Map from filenames to FileState objects, representing a simple file system.
373  typedef std::map<std::string, FileState*> FileSystem;
374  port::Mutex mutex_;
375  FileSystem file_map_;  // Protected by mutex_.
376};
377
378}  // namespace
379
380Env* NewMemEnv(Env* base_env) {
381  return new InMemoryEnv(base_env);
382}
383
384}  // namespace leveldb
385