blob: 7cca909b206723557b0b5fbafd03f5f5fa412507 [file] [log] [blame]
// 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 <deque>
#include <errno.h>
#include <stdio.h>
#include "base/at_exit.h"
#include "base/file_path.h"
#include "base/file_util.h"
#include "base/lazy_instance.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/platform_file.h"
#include "base/process_util.h"
#include "base/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/sys_info.h"
#include "base/task.h"
#include "base/threading/platform_thread.h"
#include "base/threading/thread.h"
#include "base/utf_string_conversions.h"
#include "leveldb/env.h"
#include "leveldb/slice.h"
#include "port/port.h"
#include "util/logging.h"
#if defined(OS_WIN)
#include <io.h>
#include "base/win/win_util.h"
#endif
namespace {
#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_ANDROID) || \
defined(OS_OPENBSD)
// The following are glibc-specific
size_t fread_unlocked(void *ptr, size_t size, size_t n, FILE *file) {
return fread(ptr, size, n, file);
}
size_t fwrite_unlocked(const void *ptr, size_t size, size_t n, FILE *file) {
return fwrite(ptr, size, n, file);
}
int fflush_unlocked(FILE *file) {
return fflush(file);
}
#if !defined(OS_ANDROID)
int fdatasync(int fildes) {
#if defined(OS_WIN)
return _commit(fildes);
#else
return fsync(fildes);
#endif
}
#endif
#endif
// Wide-char safe fopen wrapper.
FILE* fopen_internal(const char* fname, const char* mode) {
#if defined(OS_WIN)
return _wfopen(UTF8ToUTF16(fname).c_str(), ASCIIToUTF16(mode).c_str());
#else
return fopen(fname, mode);
#endif
}
} // namespace
namespace leveldb {
namespace {
class Thread;
static const ::FilePath::CharType kLevelDBTestDirectoryPrefix[]
= FILE_PATH_LITERAL("leveldb-test-");
::FilePath CreateFilePath(const std::string& file_path) {
#if defined(OS_WIN)
return FilePath(UTF8ToUTF16(file_path));
#else
return FilePath(file_path);
#endif
}
std::string FilePathToString(const ::FilePath& file_path) {
#if defined(OS_WIN)
return UTF16ToUTF8(file_path.value());
#else
return file_path.value();
#endif
}
// TODO(jorlow): This should be moved into Chromium's base.
const char* PlatformFileErrorString(const ::base::PlatformFileError& error) {
switch (error) {
case ::base::PLATFORM_FILE_ERROR_FAILED:
return "Opening file failed.";
case ::base::PLATFORM_FILE_ERROR_IN_USE:
return "File currently in use.";
case ::base::PLATFORM_FILE_ERROR_EXISTS:
return "File already exists.";
case ::base::PLATFORM_FILE_ERROR_NOT_FOUND:
return "File not found.";
case ::base::PLATFORM_FILE_ERROR_ACCESS_DENIED:
return "Access denied.";
case ::base::PLATFORM_FILE_ERROR_TOO_MANY_OPENED:
return "Too many files open.";
case ::base::PLATFORM_FILE_ERROR_NO_MEMORY:
return "Out of memory.";
case ::base::PLATFORM_FILE_ERROR_NO_SPACE:
return "No space left on drive.";
case ::base::PLATFORM_FILE_ERROR_NOT_A_DIRECTORY:
return "Not a directory.";
case ::base::PLATFORM_FILE_ERROR_INVALID_OPERATION:
return "Invalid operation.";
case ::base::PLATFORM_FILE_ERROR_SECURITY:
return "Security error.";
case ::base::PLATFORM_FILE_ERROR_ABORT:
return "File operation aborted.";
case ::base::PLATFORM_FILE_ERROR_NOT_A_FILE:
return "The supplied path was not a file.";
case ::base::PLATFORM_FILE_ERROR_NOT_EMPTY:
return "The file was not empty.";
case ::base::PLATFORM_FILE_ERROR_INVALID_URL:
return "Invalid URL.";
case ::base::PLATFORM_FILE_OK:
return "OK.";
}
NOTIMPLEMENTED();
return "Unknown error.";
}
class ChromiumSequentialFile: public SequentialFile {
private:
std::string filename_;
FILE* file_;
public:
ChromiumSequentialFile(const std::string& fname, FILE* f)
: filename_(fname), file_(f) { }
virtual ~ChromiumSequentialFile() { fclose(file_); }
virtual Status Read(size_t n, Slice* result, char* scratch) {
Status s;
size_t r = fread_unlocked(scratch, 1, n, file_);
*result = Slice(scratch, r);
if (r < n) {
if (feof(file_)) {
// We leave status as ok if we hit the end of the file
} else {
// A partial read with an error: return a non-ok status
s = Status::IOError(filename_, strerror(errno));
}
}
return s;
}
virtual Status Skip(uint64_t n) {
if (fseek(file_, n, SEEK_CUR)) {
return Status::IOError(filename_, strerror(errno));
}
return Status::OK();
}
};
class ChromiumRandomAccessFile: public RandomAccessFile {
private:
std::string filename_;
::base::PlatformFile file_;
public:
ChromiumRandomAccessFile(const std::string& fname, ::base::PlatformFile file)
: filename_(fname), file_(file) { }
virtual ~ChromiumRandomAccessFile() { ::base::ClosePlatformFile(file_); }
virtual Status Read(uint64_t offset, size_t n, Slice* result,
char* scratch) const {
Status s;
int r = ::base::ReadPlatformFile(file_, offset, scratch, n);
*result = Slice(scratch, (r < 0) ? 0 : r);
if (r < 0) {
// An error: return a non-ok status
s = Status::IOError(filename_, "Could not perform read");
}
return s;
}
};
class ChromiumWritableFile : public WritableFile {
private:
std::string filename_;
FILE* file_;
public:
ChromiumWritableFile(const std::string& fname, FILE* f)
: filename_(fname), file_(f) { }
~ChromiumWritableFile() {
if (file_ != NULL) {
// Ignoring any potential errors
fclose(file_);
}
}
virtual Status Append(const Slice& data) {
size_t r = fwrite_unlocked(data.data(), 1, data.size(), file_);
Status result;
if (r != data.size()) {
result = Status::IOError(filename_, strerror(errno));
}
return result;
}
virtual Status Close() {
Status result;
if (fclose(file_) != 0) {
result = Status::IOError(filename_, strerror(errno));
}
file_ = NULL;
return result;
}
virtual Status Flush() {
Status result;
if (fflush_unlocked(file_) != 0) {
result = Status::IOError(filename_, strerror(errno));
}
return result;
}
virtual Status Sync() {
Status result;
if ((fflush_unlocked(file_) != 0) ||
(fdatasync(fileno(file_)) != 0)) {
result = Status::IOError(filename_, strerror(errno));
}
return result;
}
};
class ChromiumFileLock : public FileLock {
public:
::base::PlatformFile file_;
};
class ChromiumEnv : public Env {
public:
ChromiumEnv();
virtual ~ChromiumEnv() {
fprintf(stderr, "Destroying Env::Default()\n");
exit(1);
}
virtual Status NewSequentialFile(const std::string& fname,
SequentialFile** result) {
FILE* f = fopen_internal(fname.c_str(), "rb");
if (f == NULL) {
*result = NULL;
return Status::IOError(fname, strerror(errno));
} else {
*result = new ChromiumSequentialFile(fname, f);
return Status::OK();
}
}
virtual Status NewRandomAccessFile(const std::string& fname,
RandomAccessFile** result) {
int flags = ::base::PLATFORM_FILE_READ | ::base::PLATFORM_FILE_OPEN;
bool created;
::base::PlatformFileError error_code;
::base::PlatformFile file = ::base::CreatePlatformFile(
CreateFilePath(fname), flags, &created, &error_code);
if (error_code != ::base::PLATFORM_FILE_OK) {
*result = NULL;
return Status::IOError(fname, PlatformFileErrorString(error_code));
}
*result = new ChromiumRandomAccessFile(fname, file);
return Status::OK();
}
virtual Status NewWritableFile(const std::string& fname,
WritableFile** result) {
*result = NULL;
FILE* f = fopen_internal(fname.c_str(), "wb");
if (f == NULL) {
return Status::IOError(fname, strerror(errno));
} else {
*result = new ChromiumWritableFile(fname, f);
return Status::OK();
}
}
virtual bool FileExists(const std::string& fname) {
return ::file_util::PathExists(CreateFilePath(fname));
}
virtual Status GetChildren(const std::string& dir,
std::vector<std::string>* result) {
result->clear();
::file_util::FileEnumerator iter(
CreateFilePath(dir), false, ::file_util::FileEnumerator::FILES);
::FilePath current = iter.Next();
while (!current.empty()) {
result->push_back(FilePathToString(current.BaseName()));
current = iter.Next();
}
// TODO(jorlow): Unfortunately, the FileEnumerator swallows errors, so
// we'll always return OK. Maybe manually check for error
// conditions like the file not existing?
return Status::OK();
}
virtual Status DeleteFile(const std::string& fname) {
Status result;
// TODO(jorlow): Should we assert this is a file?
if (!::file_util::Delete(CreateFilePath(fname), false)) {
result = Status::IOError(fname, "Could not delete file.");
}
return result;
};
virtual Status CreateDir(const std::string& name) {
Status result;
if (!::file_util::CreateDirectory(CreateFilePath(name))) {
result = Status::IOError(name, "Could not create directory.");
}
return result;
};
virtual Status DeleteDir(const std::string& name) {
Status result;
// TODO(jorlow): Should we assert this is a directory?
if (!::file_util::Delete(CreateFilePath(name), false)) {
result = Status::IOError(name, "Could not delete directory.");
}
return result;
};
virtual Status GetFileSize(const std::string& fname, uint64_t* size) {
Status s;
int64_t signed_size;
if (!::file_util::GetFileSize(CreateFilePath(fname), &signed_size)) {
*size = 0;
s = Status::IOError(fname, "Could not determine file size.");
} else {
*size = static_cast<uint64_t>(signed_size);
}
return s;
}
virtual Status RenameFile(const std::string& src, const std::string& dst) {
Status result;
if (!::file_util::ReplaceFile(CreateFilePath(src), CreateFilePath(dst))) {
result = Status::IOError(src, "Could not rename file.");
}
return result;
}
virtual Status LockFile(const std::string& fname, FileLock** lock) {
*lock = NULL;
Status result;
int flags = ::base::PLATFORM_FILE_OPEN_ALWAYS |
::base::PLATFORM_FILE_READ |
::base::PLATFORM_FILE_WRITE |
::base::PLATFORM_FILE_EXCLUSIVE_READ |
::base::PLATFORM_FILE_EXCLUSIVE_WRITE;
bool created;
::base::PlatformFileError error_code;
::base::PlatformFile file = ::base::CreatePlatformFile(
CreateFilePath(fname), flags, &created, &error_code);
if (error_code != ::base::PLATFORM_FILE_OK) {
result = Status::IOError(fname, PlatformFileErrorString(error_code));
} else {
ChromiumFileLock* my_lock = new ChromiumFileLock;
my_lock->file_ = file;
*lock = my_lock;
}
return result;
}
virtual Status UnlockFile(FileLock* lock) {
ChromiumFileLock* my_lock = reinterpret_cast<ChromiumFileLock*>(lock);
Status result;
if (!::base::ClosePlatformFile(my_lock->file_)) {
result = Status::IOError("Could not close lock file.");
}
delete my_lock;
return result;
}
virtual void Schedule(void (*function)(void*), void* arg);
virtual void StartThread(void (*function)(void* arg), void* arg);
virtual std::string UserIdentifier() {
#if defined(OS_WIN)
std::wstring user_sid;
bool ret = ::base::win::GetUserSidString(&user_sid);
DCHECK(ret);
return UTF16ToUTF8(user_sid);
#else
char buf[100];
snprintf(buf, sizeof(buf), "%d", int(geteuid()));
return buf;
#endif
}
virtual Status GetTestDirectory(std::string* path) {
mu_.Acquire();
if (test_directory_.empty()) {
if (!::file_util::CreateNewTempDirectory(kLevelDBTestDirectoryPrefix,
&test_directory_)) {
mu_.Release();
return Status::IOError("Could not create temp directory.");
}
}
*path = FilePathToString(test_directory_);
mu_.Release();
return Status::OK();
}
class ChromiumLogger : public Logger {
public:
ChromiumLogger(const std::string& filename) : filename_(filename) {
}
virtual void Logv(const char* format, va_list ap) {
VLOG(5) << "LevelDB: " << filename_ << " " << StringPrintf(format, ap);
}
private:
std::string filename_;
};
virtual Status NewLogger(const std::string& fname, Logger** result) {
*result = new ChromiumLogger(fname);
return Status::OK();
}
virtual uint64_t NowMicros() {
return ::base::TimeTicks::HighResNow().ToInternalValue();
}
virtual void SleepForMicroseconds(int micros) {
// Round up to the next millisecond.
::base::PlatformThread::Sleep((micros + 999) / 1000);
}
private:
// BGThread() is the body of the background thread
void BGThread();
static void BGThreadWrapper(void* arg) {
reinterpret_cast<ChromiumEnv*>(arg)->BGThread();
}
FilePath test_directory_;
size_t page_size_;
::base::Lock mu_;
::base::ConditionVariable bgsignal_;
bool started_bgthread_;
// Entry per Schedule() call
struct BGItem { void* arg; void (*function)(void*); };
typedef std::deque<BGItem> BGQueue;
BGQueue queue_;
};
ChromiumEnv::ChromiumEnv()
: page_size_(::base::SysInfo::VMAllocationGranularity()),
bgsignal_(&mu_),
started_bgthread_(false) {
#if defined(OS_MACOSX)
::base::EnableTerminationOnHeapCorruption();
::base::EnableTerminationOnOutOfMemory();
#endif // OS_MACOSX
}
class Thread : public ::base::PlatformThread::Delegate {
public:
Thread(void (*function)(void* arg), void* arg)
: function_(function), arg_(arg) {
::base::PlatformThreadHandle handle;
bool success = ::base::PlatformThread::Create(0, this, &handle);
DCHECK(success);
}
virtual ~Thread() {}
virtual void ThreadMain() {
(*function_)(arg_);
delete this;
}
private:
void (*function_)(void* arg);
void* arg_;
};
void ChromiumEnv::Schedule(void (*function)(void*), void* arg) {
mu_.Acquire();
// Start background thread if necessary
if (!started_bgthread_) {
started_bgthread_ = true;
StartThread(&ChromiumEnv::BGThreadWrapper, this);
}
// If the queue is currently empty, the background thread may currently be
// waiting.
if (queue_.empty()) {
bgsignal_.Signal();
}
// Add to priority queue
queue_.push_back(BGItem());
queue_.back().function = function;
queue_.back().arg = arg;
mu_.Release();
}
void ChromiumEnv::BGThread() {
while (true) {
// Wait until there is an item that is ready to run
mu_.Acquire();
while (queue_.empty()) {
bgsignal_.Wait();
}
void (*function)(void*) = queue_.front().function;
void* arg = queue_.front().arg;
queue_.pop_front();
mu_.Release();
(*function)(arg);
}
}
void ChromiumEnv::StartThread(void (*function)(void* arg), void* arg) {
new Thread(function, arg); // Will self-delete.
}
::base::LazyInstance<ChromiumEnv, ::base::LeakyLazyInstanceTraits<ChromiumEnv> >
default_env(::base::LINKER_INITIALIZED);
}
Env* Env::Default() {
return default_env.Pointer();
}
}