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