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 | |
19 | namespace leveldb { |
20 | |
21 | namespace { |
22 | |
23 | class 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 | |
152 | class 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 | |
185 | class 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 | |
200 | class 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 | |
216 | class NoOpLogger : public Logger { |
217 | public: |
218 | void Logv(const char* format, std::va_list ap) override {} |
219 | }; |
220 | |
221 | class 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 | |
388 | Env* NewMemEnv(Env* base_env) { return new InMemoryEnv(base_env); } |
389 | |
390 | } // namespace leveldb |
391 | |