gabor@google.com | a7d6c31 | 2011-09-15 21:01:32 +0000 | [diff] [blame] | 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 | |
| 16 | namespace leveldb { |
| 17 | |
| 18 | namespace { |
| 19 | |
| 20 | class 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 | |
| 148 | class 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 | |
| 183 | class 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 | |
| 202 | class 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 | |
dgrogan@chromium.org | 29c68f1 | 2012-10-12 19:13:17 +0000 | [diff] [blame] | 224 | class NoOpLogger : public Logger { |
| 225 | public: |
| 226 | virtual void Logv(const char* format, va_list ap) { } |
| 227 | }; |
| 228 | |
gabor@google.com | a7d6c31 | 2011-09-15 21:01:32 +0000 | [diff] [blame] | 229 | class 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 | |
dgrogan@chromium.org | 29c68f1 | 2012-10-12 19:13:17 +0000 | [diff] [blame] | 366 | virtual Status NewLogger(const std::string& fname, Logger** result) { |
| 367 | *result = new NoOpLogger; |
| 368 | return Status::OK(); |
| 369 | } |
| 370 | |
gabor@google.com | a7d6c31 | 2011-09-15 21:01:32 +0000 | [diff] [blame] | 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 | |
hans@chromium.org | 45b9940 | 2011-10-31 17:34:55 +0000 | [diff] [blame] | 378 | } // namespace |
gabor@google.com | a7d6c31 | 2011-09-15 21:01:32 +0000 | [diff] [blame] | 379 | |
| 380 | Env* NewMemEnv(Env* base_env) { |
| 381 | return new InMemoryEnv(base_env); |
| 382 | } |
| 383 | |
hans@chromium.org | 45b9940 | 2011-10-31 17:34:55 +0000 | [diff] [blame] | 384 | } // namespace leveldb |