blob: 82af93a997036fa73be861228083b196b93f85f9 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/leveldb/env_mojo.h"
#include <errno.h>
#include <memory>
#include "base/strings/string_util.h"
#include "base/trace_event/trace_event.h"
#include "third_party/leveldatabase/chromium_logger.h"
#include "third_party/leveldatabase/src/include/leveldb/status.h"
namespace leveldb {
namespace {
const base::FilePath::CharType table_extension[] = FILE_PATH_LITERAL(".ldb");
base::File::Error LastFileError() {
#if defined(OS_WIN)
return base::File::OSErrorToFileError(GetLastError());
#else
return base::File::OSErrorToFileError(errno);
#endif
}
Status FilesystemErrorToStatus(filesystem::mojom::FileError error,
const std::string& filename,
leveldb_env::MethodID method) {
if (error == filesystem::mojom::FileError::OK)
return Status::OK();
std::string err_str =
base::File::ErrorToString(base::File::Error(static_cast<int>(error)));
char buf[512];
snprintf(buf, sizeof(buf), "%s (MojoFSError: %d::%s)", err_str.c_str(),
method, MethodIDToString(method));
return Status::IOError(filename, buf);
}
class MojoFileLock : public FileLock {
public:
MojoFileLock(LevelDBMojoProxy::OpaqueLock* lock, const std::string& name)
: fname_(name), lock_(lock) {}
~MojoFileLock() override { DCHECK(!lock_); }
const std::string& name() const { return fname_; }
LevelDBMojoProxy::OpaqueLock* TakeLock() {
LevelDBMojoProxy::OpaqueLock* to_return = lock_;
lock_ = nullptr;
return to_return;
}
private:
std::string fname_;
LevelDBMojoProxy::OpaqueLock* lock_;
};
class MojoSequentialFile : public leveldb::SequentialFile {
public:
MojoSequentialFile(const std::string& fname, base::File f)
: filename_(fname), file_(std::move(f)) {}
~MojoSequentialFile() override {}
Status Read(size_t n, Slice* result, char* scratch) override {
int bytes_read = file_.ReadAtCurrentPosNoBestEffort(
scratch,
static_cast<int>(n));
if (bytes_read == -1) {
base::File::Error error = LastFileError();
return MakeIOError(filename_, base::File::ErrorToString(error),
leveldb_env::kSequentialFileRead, error);
} else {
*result = Slice(scratch, bytes_read);
return Status::OK();
}
}
Status Skip(uint64_t n) override {
if (file_.Seek(base::File::FROM_CURRENT, n) == -1) {
base::File::Error error = LastFileError();
return MakeIOError(filename_, base::File::ErrorToString(error),
leveldb_env::kSequentialFileSkip, error);
} else {
return Status::OK();
}
}
private:
std::string filename_;
base::File file_;
DISALLOW_COPY_AND_ASSIGN(MojoSequentialFile);
};
class MojoRandomAccessFile : public leveldb::RandomAccessFile {
public:
MojoRandomAccessFile(const std::string& fname, base::File file)
: filename_(fname), file_(std::move(file)) {}
~MojoRandomAccessFile() override {}
Status Read(uint64_t offset,
size_t n,
Slice* result,
char* scratch) const override {
Status s;
int r = file_.Read(offset, scratch, static_cast<int>(n));
*result = Slice(scratch, (r < 0) ? 0 : r);
if (r < 0) {
// An error: return a non-ok status
s = MakeIOError(filename_, "Could not perform read",
leveldb_env::kRandomAccessFileRead);
}
return s;
}
private:
std::string filename_;
mutable base::File file_;
DISALLOW_COPY_AND_ASSIGN(MojoRandomAccessFile);
};
class MojoWritableFile : public leveldb::WritableFile {
public:
MojoWritableFile(LevelDBMojoProxy::OpaqueDir* dir,
const std::string& fname,
base::File f,
scoped_refptr<LevelDBMojoProxy> thread)
: filename_(fname),
file_(std::move(f)),
file_type_(kOther),
dir_(dir),
thread_(thread) {
base::FilePath path = base::FilePath::FromUTF8Unsafe(fname);
if (base::StartsWith(path.BaseName().AsUTF8Unsafe(), "MANIFEST",
base::CompareCase::SENSITIVE)) {
file_type_ = kManifest;
} else if (path.MatchesExtension(table_extension)) {
file_type_ = kTable;
}
parent_dir_ =
base::FilePath::FromUTF8Unsafe(fname).DirName().AsUTF8Unsafe();
}
~MojoWritableFile() override {}
leveldb::Status Append(const leveldb::Slice& data) override {
size_t bytes_written = file_.WriteAtCurrentPos(
data.data(), static_cast<int>(data.size()));
if (bytes_written != data.size()) {
base::File::Error error = LastFileError();
return MakeIOError(filename_, base::File::ErrorToString(error),
leveldb_env::kWritableFileAppend, error);
}
return Status::OK();
}
leveldb::Status Close() override {
file_.Close();
return Status::OK();
}
leveldb::Status Flush() override {
// base::File doesn't do buffered I/O (i.e. POSIX FILE streams) so nothing
// to
// flush.
return Status::OK();
}
leveldb::Status Sync() override {
TRACE_EVENT0("leveldb", "MojoWritableFile::Sync");
if (!file_.Flush()) {
base::File::Error error = LastFileError();
return MakeIOError(filename_, base::File::ErrorToString(error),
leveldb_env::kWritableFileSync, error);
}
// leveldb's implicit contract for Sync() is that if this instance is for a
// manifest file then the directory is also sync'ed. See leveldb's
// env_posix.cc.
if (file_type_ == kManifest)
return SyncParent();
return Status::OK();
}
private:
enum Type { kManifest, kTable, kOther };
leveldb::Status SyncParent() {
filesystem::mojom::FileError error =
thread_->SyncDirectory(dir_, parent_dir_);
return error == filesystem::mojom::FileError::OK
? Status::OK()
: Status::IOError(filename_,
base::File::ErrorToString(base::File::Error(
static_cast<int>(error))));
}
std::string filename_;
base::File file_;
Type file_type_;
LevelDBMojoProxy::OpaqueDir* dir_;
std::string parent_dir_;
scoped_refptr<LevelDBMojoProxy> thread_;
DISALLOW_COPY_AND_ASSIGN(MojoWritableFile);
};
} // namespace
MojoEnv::MojoEnv(const std::string& name,
scoped_refptr<LevelDBMojoProxy> file_thread,
LevelDBMojoProxy::OpaqueDir* dir)
: ChromiumEnv(name), thread_(file_thread), dir_(dir) {}
MojoEnv::~MojoEnv() {
thread_->UnregisterDirectory(dir_);
}
Status MojoEnv::NewSequentialFile(const std::string& fname,
SequentialFile** result) {
TRACE_EVENT1("leveldb", "MojoEnv::NewSequentialFile", "fname", fname);
base::File f = thread_->OpenFileHandle(
dir_, fname, filesystem::mojom::kFlagOpen | filesystem::mojom::kFlagRead);
if (!f.IsValid()) {
*result = nullptr;
return MakeIOError(fname, "Unable to create sequential file",
leveldb_env::kNewSequentialFile, f.error_details());
}
*result = new MojoSequentialFile(fname, std::move(f));
return Status::OK();
}
Status MojoEnv::NewRandomAccessFile(const std::string& fname,
RandomAccessFile** result) {
TRACE_EVENT1("leveldb", "MojoEnv::NewRandomAccessFile", "fname", fname);
base::File f = thread_->OpenFileHandle(
dir_, fname, filesystem::mojom::kFlagRead | filesystem::mojom::kFlagOpen);
if (!f.IsValid()) {
*result = nullptr;
base::File::Error error_code = f.error_details();
return MakeIOError(fname, FileErrorString(error_code),
leveldb_env::kNewRandomAccessFile, error_code);
}
*result = new MojoRandomAccessFile(fname, std::move(f));
return Status::OK();
}
Status MojoEnv::NewWritableFile(const std::string& fname,
WritableFile** result) {
TRACE_EVENT1("leveldb", "MojoEnv::NewWritableFile", "fname", fname);
base::File f =
thread_->OpenFileHandle(dir_, fname, filesystem::mojom::kCreateAlways |
filesystem::mojom::kFlagWrite);
if (!f.IsValid()) {
*result = nullptr;
return MakeIOError(fname, "Unable to create writable file",
leveldb_env::kNewWritableFile, f.error_details());
}
*result = new MojoWritableFile(dir_, fname, std::move(f), thread_);
return Status::OK();
}
Status MojoEnv::NewAppendableFile(const std::string& fname,
WritableFile** result) {
TRACE_EVENT1("leveldb", "MojoEnv::NewAppendableFile", "fname", fname);
base::File f =
thread_->OpenFileHandle(dir_, fname, filesystem::mojom::kFlagOpenAlways |
filesystem::mojom::kFlagAppend);
if (!f.IsValid()) {
*result = nullptr;
return MakeIOError(fname, "Unable to create appendable file",
leveldb_env::kNewAppendableFile, f.error_details());
}
*result = new MojoWritableFile(dir_, fname, std::move(f), thread_);
return Status::OK();
}
bool MojoEnv::FileExists(const std::string& fname) {
TRACE_EVENT1("leveldb", "MojoEnv::FileExists", "fname", fname);
return thread_->FileExists(dir_, fname);
}
Status MojoEnv::GetChildren(const std::string& path,
std::vector<std::string>* result) {
TRACE_EVENT1("leveldb", "MojoEnv::GetChildren", "path", path);
return FilesystemErrorToStatus(thread_->GetChildren(dir_, path, result), path,
leveldb_env::kGetChildren);
}
Status MojoEnv::DeleteFile(const std::string& fname) {
TRACE_EVENT1("leveldb", "MojoEnv::DeleteFile", "fname", fname);
return FilesystemErrorToStatus(thread_->Delete(dir_, fname, 0), fname,
leveldb_env::kDeleteFile);
}
Status MojoEnv::CreateDir(const std::string& dirname) {
TRACE_EVENT1("leveldb", "MojoEnv::CreateDir", "dirname", dirname);
return FilesystemErrorToStatus(thread_->CreateDir(dir_, dirname), dirname,
leveldb_env::kCreateDir);
}
Status MojoEnv::DeleteDir(const std::string& dirname) {
TRACE_EVENT1("leveldb", "MojoEnv::DeleteDir", "dirname", dirname);
return FilesystemErrorToStatus(
thread_->Delete(dir_, dirname, filesystem::mojom::kDeleteFlagRecursive),
dirname, leveldb_env::kDeleteDir);
}
Status MojoEnv::GetFileSize(const std::string& fname, uint64_t* file_size) {
TRACE_EVENT1("leveldb", "MojoEnv::GetFileSize", "fname", fname);
return FilesystemErrorToStatus(thread_->GetFileSize(dir_, fname, file_size),
fname,
leveldb_env::kGetFileSize);
}
Status MojoEnv::RenameFile(const std::string& src, const std::string& target) {
TRACE_EVENT2("leveldb", "MojoEnv::RenameFile", "src", src, "target", target);
return FilesystemErrorToStatus(thread_->RenameFile(dir_, src, target), src,
leveldb_env::kRenameFile);
}
Status MojoEnv::LockFile(const std::string& fname, FileLock** lock) {
TRACE_EVENT1("leveldb", "MojoEnv::LockFile", "fname", fname);
std::pair<filesystem::mojom::FileError, LevelDBMojoProxy::OpaqueLock*> p =
thread_->LockFile(dir_, fname);
if (p.second)
*lock = new MojoFileLock(p.second, fname);
return FilesystemErrorToStatus(p.first, fname, leveldb_env::kLockFile);
}
Status MojoEnv::UnlockFile(FileLock* lock) {
MojoFileLock* my_lock = reinterpret_cast<MojoFileLock*>(lock);
std::string fname = my_lock ? my_lock->name() : "(invalid)";
TRACE_EVENT1("leveldb", "MojoEnv::UnlockFile", "fname", fname);
filesystem::mojom::FileError err = thread_->UnlockFile(my_lock->TakeLock());
delete my_lock;
return FilesystemErrorToStatus(err, fname, leveldb_env::kUnlockFile);
}
Status MojoEnv::GetTestDirectory(std::string* path) {
// TODO(erg): This method is actually only used from the test harness in
// leveldb. And when we go and port that test stuff to a
// service_manager::ServiceTest,
// we probably won't use it since the mojo filesystem actually handles
// temporary filesystems just fine.
NOTREACHED();
return Status::OK();
}
Status MojoEnv::NewLogger(const std::string& fname, Logger** result) {
TRACE_EVENT1("leveldb", "MojoEnv::NewLogger", "fname", fname);
base::File f(thread_->OpenFileHandle(
dir_, fname,
filesystem::mojom::kCreateAlways | filesystem::mojom::kFlagWrite));
if (!f.IsValid()) {
*result = NULL;
return MakeIOError(fname, "Unable to create log file",
leveldb_env::kNewLogger, f.error_details());
} else {
*result = new leveldb::ChromiumLogger(std::move(f));
return Status::OK();
}
}
} // namespace leveldb