| // 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(scoped_refptr<LevelDBMojoProxy> file_thread, |
| LevelDBMojoProxy::OpaqueDir* dir) |
| : 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 |