| // 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 <errno.h> |
| #include <stdio.h> |
| |
| #include <deque> |
| |
| #include "base/at_exit.h" |
| #include "base/debug/trace_event.h" |
| #include "base/file_util.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/lazy_instance.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "base/platform_file.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/synchronization/lock.h" |
| #include "base/sys_info.h" |
| #include "base/threading/platform_thread.h" |
| #include "base/threading/thread.h" |
| #include "chromium_logger.h" |
| #include "env_chromium.h" |
| #include "leveldb/env.h" |
| #include "leveldb/slice.h" |
| #include "port/port.h" |
| #include "third_party/re2/re2/re2.h" |
| #include "util/logging.h" |
| |
| #if defined(OS_WIN) |
| #include <io.h> |
| #include "base/win/win_util.h" |
| #endif |
| |
| #if defined(OS_POSIX) |
| #include <dirent.h> |
| #include <fcntl.h> |
| #include <sys/resource.h> |
| #include <sys/time.h> |
| #endif |
| |
| using namespace leveldb; |
| |
| namespace leveldb_env { |
| |
| namespace { |
| |
| const base::FilePath::CharType backup_table_extension[] = |
| FILE_PATH_LITERAL(".bak"); |
| const base::FilePath::CharType table_extension[] = FILE_PATH_LITERAL(".ldb"); |
| |
| #if (defined(OS_POSIX) && !defined(OS_LINUX)) || defined(OS_WIN) |
| // 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 HANDLE_EINTR(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 |
| } |
| |
| base::FilePath CreateFilePath(const std::string& file_path) { |
| #if defined(OS_WIN) |
| return base::FilePath(UTF8ToUTF16(file_path)); |
| #else |
| return base::FilePath(file_path); |
| #endif |
| } |
| |
| static const base::FilePath::CharType kLevelDBTestDirectoryPrefix[] |
| = FILE_PATH_LITERAL("leveldb-test-"); |
| |
| const char* PlatformFileErrorString(const ::base::PlatformFileError& error) { |
| switch (error) { |
| case ::base::PLATFORM_FILE_ERROR_FAILED: |
| return "No further details."; |
| 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_ERROR_IO: |
| return "OS or hardware error."; |
| case ::base::PLATFORM_FILE_OK: |
| return "OK."; |
| case ::base::PLATFORM_FILE_ERROR_MAX: |
| NOTREACHED(); |
| } |
| NOTIMPLEMENTED(); |
| return "Unknown error."; |
| } |
| |
| class ChromiumSequentialFile: public SequentialFile { |
| private: |
| std::string filename_; |
| FILE* file_; |
| const UMALogger* uma_logger_; |
| |
| public: |
| ChromiumSequentialFile(const std::string& fname, FILE* f, |
| const UMALogger* uma_logger) |
| : filename_(fname), file_(f), uma_logger_(uma_logger) { } |
| 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 = MakeIOError(filename_, strerror(errno), kSequentialFileRead, errno); |
| uma_logger_->RecordErrorAt(kSequentialFileRead); |
| } |
| } |
| return s; |
| } |
| |
| virtual Status Skip(uint64_t n) { |
| if (fseek(file_, n, SEEK_CUR)) { |
| int saved_errno = errno; |
| uma_logger_->RecordErrorAt(kSequentialFileSkip); |
| return MakeIOError( |
| filename_, strerror(saved_errno), kSequentialFileSkip, saved_errno); |
| } |
| return Status::OK(); |
| } |
| }; |
| |
| class ChromiumRandomAccessFile: public RandomAccessFile { |
| private: |
| std::string filename_; |
| ::base::PlatformFile file_; |
| const UMALogger* uma_logger_; |
| |
| public: |
| ChromiumRandomAccessFile(const std::string& fname, ::base::PlatformFile file, |
| const UMALogger* uma_logger) |
| : filename_(fname), file_(file), uma_logger_(uma_logger) { } |
| 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 = MakeIOError( |
| filename_, "Could not perform read", kRandomAccessFileRead); |
| uma_logger_->RecordErrorAt(kRandomAccessFileRead); |
| } |
| return s; |
| } |
| }; |
| |
| class ChromiumFileLock : public FileLock { |
| public: |
| ::base::PlatformFile file_; |
| std::string name_; |
| }; |
| |
| class Retrier { |
| public: |
| Retrier(MethodID method, RetrierProvider* provider) |
| : start_(base::TimeTicks::Now()), |
| limit_(start_ + base::TimeDelta::FromMilliseconds( |
| provider->MaxRetryTimeMillis())), |
| last_(start_), |
| time_to_sleep_(base::TimeDelta::FromMilliseconds(10)), |
| success_(true), |
| method_(method), |
| last_error_(base::PLATFORM_FILE_OK), |
| provider_(provider) {} |
| ~Retrier() { |
| if (success_) { |
| provider_->GetRetryTimeHistogram(method_)->AddTime(last_ - start_); |
| if (last_error_ != base::PLATFORM_FILE_OK) { |
| DCHECK(last_error_ < 0); |
| provider_->GetRecoveredFromErrorHistogram(method_)->Add(-last_error_); |
| } |
| } |
| } |
| bool ShouldKeepTrying(base::PlatformFileError last_error) { |
| DCHECK_NE(last_error, base::PLATFORM_FILE_OK); |
| last_error_ = last_error; |
| if (last_ < limit_) { |
| base::PlatformThread::Sleep(time_to_sleep_); |
| last_ = base::TimeTicks::Now(); |
| return true; |
| } |
| success_ = false; |
| return false; |
| } |
| |
| private: |
| base::TimeTicks start_; |
| base::TimeTicks limit_; |
| base::TimeTicks last_; |
| base::TimeDelta time_to_sleep_; |
| bool success_; |
| MethodID method_; |
| base::PlatformFileError last_error_; |
| RetrierProvider* provider_; |
| }; |
| |
| class IDBEnv : public ChromiumEnv { |
| public: |
| IDBEnv() : ChromiumEnv() { |
| name_ = "LevelDBEnv.IDB"; |
| make_backup_ = true; |
| } |
| }; |
| |
| ::base::LazyInstance<IDBEnv>::Leaky idb_env = LAZY_INSTANCE_INITIALIZER; |
| |
| ::base::LazyInstance<ChromiumEnv>::Leaky default_env = |
| LAZY_INSTANCE_INITIALIZER; |
| |
| } // unnamed namespace |
| |
| const char* MethodIDToString(MethodID method) { |
| switch (method) { |
| case kSequentialFileRead: |
| return "SequentialFileRead"; |
| case kSequentialFileSkip: |
| return "SequentialFileSkip"; |
| case kRandomAccessFileRead: |
| return "RandomAccessFileRead"; |
| case kWritableFileAppend: |
| return "WritableFileAppend"; |
| case kWritableFileClose: |
| return "WritableFileClose"; |
| case kWritableFileFlush: |
| return "WritableFileFlush"; |
| case kWritableFileSync: |
| return "WritableFileSync"; |
| case kNewSequentialFile: |
| return "NewSequentialFile"; |
| case kNewRandomAccessFile: |
| return "NewRandomAccessFile"; |
| case kNewWritableFile: |
| return "NewWritableFile"; |
| case kDeleteFile: |
| return "DeleteFile"; |
| case kCreateDir: |
| return "CreateDir"; |
| case kDeleteDir: |
| return "DeleteDir"; |
| case kGetFileSize: |
| return "GetFileSize"; |
| case kRenameFile: |
| return "RenameFile"; |
| case kLockFile: |
| return "LockFile"; |
| case kUnlockFile: |
| return "UnlockFile"; |
| case kGetTestDirectory: |
| return "GetTestDirectory"; |
| case kNewLogger: |
| return "NewLogger"; |
| case kSyncParent: |
| return "SyncParent"; |
| case kGetChildren: |
| return "GetChildren"; |
| case kNumEntries: |
| NOTREACHED(); |
| return "kNumEntries"; |
| } |
| NOTREACHED(); |
| return "Unknown"; |
| } |
| |
| Status MakeIOError(Slice filename, |
| const char* message, |
| MethodID method, |
| int saved_errno) { |
| char buf[512]; |
| snprintf(buf, |
| sizeof(buf), |
| "%s (ChromeMethodErrno: %d::%s::%d)", |
| message, |
| method, |
| MethodIDToString(method), |
| saved_errno); |
| return Status::IOError(filename, buf); |
| } |
| |
| Status MakeIOError(Slice filename, |
| const char* message, |
| MethodID method, |
| base::PlatformFileError error) { |
| DCHECK(error < 0); |
| char buf[512]; |
| snprintf(buf, |
| sizeof(buf), |
| "%s (ChromeMethodPFE: %d::%s::%d)", |
| message, |
| method, |
| MethodIDToString(method), |
| -error); |
| return Status::IOError(filename, buf); |
| } |
| |
| Status MakeIOError(Slice filename, const char* message, MethodID method) { |
| char buf[512]; |
| snprintf(buf, |
| sizeof(buf), |
| "%s (ChromeMethodOnly: %d::%s)", |
| message, |
| method, |
| MethodIDToString(method)); |
| return Status::IOError(filename, buf); |
| } |
| |
| ErrorParsingResult ParseMethodAndError(const char* string, |
| MethodID* method_param, |
| int* error) { |
| int method; |
| if (RE2::PartialMatch(string, "ChromeMethodOnly: (\\d+)", &method)) { |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_ONLY; |
| } |
| if (RE2::PartialMatch( |
| string, "ChromeMethodPFE: (\\d+)::.*::(\\d+)", &method, error)) { |
| *error = -*error; |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_AND_PFE; |
| } |
| if (RE2::PartialMatch( |
| string, "ChromeMethodErrno: (\\d+)::.*::(\\d+)", &method, error)) { |
| *method_param = static_cast<MethodID>(method); |
| return METHOD_AND_ERRNO; |
| } |
| return NONE; |
| } |
| |
| bool IndicatesDiskFull(leveldb::Status status) { |
| if (status.ok()) |
| return false; |
| leveldb_env::MethodID method; |
| int error = -1; |
| leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError( |
| status.ToString().c_str(), &method, &error); |
| return (result == leveldb_env::METHOD_AND_PFE && |
| static_cast<base::PlatformFileError>(error) == |
| base::PLATFORM_FILE_ERROR_NO_SPACE) || |
| (result == leveldb_env::METHOD_AND_ERRNO && error == ENOSPC); |
| } |
| |
| bool IsIOError(leveldb::Status status) { |
| leveldb_env::MethodID method; |
| int error = -1; |
| leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError( |
| status.ToString().c_str(), &method, &error); |
| return result != leveldb_env::NONE; |
| } |
| |
| std::string FilePathToString(const base::FilePath& file_path) { |
| #if defined(OS_WIN) |
| return UTF16ToUTF8(file_path.value()); |
| #else |
| return file_path.value(); |
| #endif |
| } |
| |
| ChromiumWritableFile::ChromiumWritableFile(const std::string& fname, |
| FILE* f, |
| const UMALogger* uma_logger, |
| WriteTracker* tracker, |
| bool make_backup) |
| : filename_(fname), |
| file_(f), |
| uma_logger_(uma_logger), |
| tracker_(tracker), |
| file_type_(kOther), |
| make_backup_(make_backup) { |
| base::FilePath path = base::FilePath::FromUTF8Unsafe(fname); |
| if (FilePathToString(path.BaseName()).find("MANIFEST") == 0) |
| file_type_ = kManifest; |
| else if (path.MatchesExtension(table_extension)) |
| file_type_ = kTable; |
| if (file_type_ != kManifest) |
| tracker_->DidCreateNewFile(filename_); |
| parent_dir_ = FilePathToString(CreateFilePath(fname).DirName()); |
| } |
| |
| ChromiumWritableFile::~ChromiumWritableFile() { |
| if (file_ != NULL) { |
| // Ignoring any potential errors |
| fclose(file_); |
| } |
| } |
| |
| Status ChromiumWritableFile::SyncParent() { |
| Status s; |
| #if !defined(OS_WIN) |
| TRACE_EVENT0("leveldb", "SyncParent"); |
| |
| int parent_fd = |
| HANDLE_EINTR(open(parent_dir_.c_str(), O_RDONLY)); |
| if (parent_fd < 0) { |
| int saved_errno = errno; |
| return MakeIOError( |
| parent_dir_, strerror(saved_errno), kSyncParent, saved_errno); |
| } |
| if (HANDLE_EINTR(fsync(parent_fd)) != 0) { |
| int saved_errno = errno; |
| s = MakeIOError( |
| parent_dir_, strerror(saved_errno), kSyncParent, saved_errno); |
| }; |
| HANDLE_EINTR(close(parent_fd)); |
| #endif |
| return s; |
| } |
| |
| Status ChromiumWritableFile::Append(const Slice& data) { |
| if (file_type_ == kManifest && tracker_->DoesDirNeedSync(filename_)) { |
| Status s = SyncParent(); |
| if (!s.ok()) |
| return s; |
| tracker_->DidSyncDir(filename_); |
| } |
| |
| size_t r = fwrite_unlocked(data.data(), 1, data.size(), file_); |
| if (r != data.size()) { |
| int saved_errno = errno; |
| uma_logger_->RecordOSError(kWritableFileAppend, saved_errno); |
| return MakeIOError( |
| filename_, strerror(saved_errno), kWritableFileAppend, saved_errno); |
| } |
| return Status::OK(); |
| } |
| |
| Status ChromiumWritableFile::Close() { |
| Status result; |
| if (fclose(file_) != 0) { |
| result = MakeIOError(filename_, strerror(errno), kWritableFileClose, errno); |
| uma_logger_->RecordErrorAt(kWritableFileClose); |
| } |
| file_ = NULL; |
| return result; |
| } |
| |
| Status ChromiumWritableFile::Flush() { |
| Status result; |
| if (HANDLE_EINTR(fflush_unlocked(file_))) { |
| int saved_errno = errno; |
| result = MakeIOError( |
| filename_, strerror(saved_errno), kWritableFileFlush, saved_errno); |
| uma_logger_->RecordOSError(kWritableFileFlush, saved_errno); |
| } |
| return result; |
| } |
| |
| static bool MakeBackup(const std::string& fname) { |
| base::FilePath original_table_name = CreateFilePath(fname); |
| base::FilePath backup_table_name = |
| original_table_name.ReplaceExtension(backup_table_extension); |
| return base::CopyFile(original_table_name, backup_table_name); |
| } |
| |
| Status ChromiumWritableFile::Sync() { |
| TRACE_EVENT0("leveldb", "ChromiumEnv::Sync"); |
| Status result; |
| int error = 0; |
| |
| if (HANDLE_EINTR(fflush_unlocked(file_))) |
| error = errno; |
| // Sync even if fflush gave an error; perhaps the data actually got out, |
| // even though something went wrong. |
| if (fdatasync(fileno(file_)) && !error) |
| error = errno; |
| // Report the first error we found. |
| if (error) { |
| result = MakeIOError(filename_, strerror(error), kWritableFileSync, error); |
| uma_logger_->RecordErrorAt(kWritableFileSync); |
| } else if (make_backup_ && file_type_ == kTable) { |
| bool success = MakeBackup(filename_); |
| uma_logger_->RecordBackupResult(success); |
| } |
| return result; |
| } |
| |
| ChromiumEnv::ChromiumEnv() |
| : name_("LevelDBEnv"), |
| make_backup_(false), |
| bgsignal_(&mu_), |
| started_bgthread_(false), |
| kMaxRetryTimeMillis(1000) { |
| } |
| |
| ChromiumEnv::~ChromiumEnv() { |
| // In chromium, ChromiumEnv is leaked. It'd be nice to add NOTREACHED here to |
| // ensure that behavior isn't accidentally changed, but there's an instance in |
| // a unit test that is deleted. |
| } |
| |
| Status ChromiumEnv::NewSequentialFile(const std::string& fname, |
| SequentialFile** result) { |
| FILE* f = fopen_internal(fname.c_str(), "rb"); |
| if (f == NULL) { |
| *result = NULL; |
| int saved_errno = errno; |
| RecordOSError(kNewSequentialFile, saved_errno); |
| return MakeIOError( |
| fname, strerror(saved_errno), kNewSequentialFile, saved_errno); |
| } else { |
| *result = new ChromiumSequentialFile(fname, f, this); |
| return Status::OK(); |
| } |
| } |
| |
| void ChromiumEnv::RecordOpenFilesLimit(const std::string& type) { |
| #if defined(OS_POSIX) |
| struct rlimit nofile; |
| if (getrlimit(RLIMIT_NOFILE, &nofile)) |
| return; |
| GetMaxFDHistogram(type)->Add(nofile.rlim_cur); |
| #endif |
| } |
| |
| Status ChromiumEnv::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 = new ChromiumRandomAccessFile(fname, file, this); |
| RecordOpenFilesLimit("Success"); |
| return Status::OK(); |
| } |
| if (error_code == ::base::PLATFORM_FILE_ERROR_TOO_MANY_OPENED) |
| RecordOpenFilesLimit("TooManyOpened"); |
| else |
| RecordOpenFilesLimit("OtherError"); |
| *result = NULL; |
| RecordOSError(kNewRandomAccessFile, error_code); |
| return MakeIOError(fname, |
| PlatformFileErrorString(error_code), |
| kNewRandomAccessFile, |
| error_code); |
| } |
| |
| Status ChromiumEnv::NewWritableFile(const std::string& fname, |
| WritableFile** result) { |
| *result = NULL; |
| FILE* f = fopen_internal(fname.c_str(), "wb"); |
| if (f == NULL) { |
| int saved_errno = errno; |
| RecordErrorAt(kNewWritableFile); |
| return MakeIOError( |
| fname, strerror(saved_errno), kNewWritableFile, saved_errno); |
| } else { |
| *result = new ChromiumWritableFile(fname, f, this, this, make_backup_); |
| return Status::OK(); |
| } |
| } |
| |
| bool ChromiumEnv::FileExists(const std::string& fname) { |
| return ::base::PathExists(CreateFilePath(fname)); |
| } |
| |
| base::FilePath ChromiumEnv::RestoreFromBackup(const base::FilePath& base_name) { |
| base::FilePath table_name = |
| base_name.AddExtension(table_extension); |
| bool result = base::CopyFile(base_name.AddExtension(backup_table_extension), |
| table_name); |
| std::string uma_name(name_); |
| uma_name.append(".TableRestore"); |
| base::BooleanHistogram::FactoryGet( |
| uma_name, base::Histogram::kUmaTargetedHistogramFlag)->AddBoolean(result); |
| return table_name; |
| } |
| |
| void ChromiumEnv::RestoreIfNecessary(const std::string& dir, |
| std::vector<std::string>* result) { |
| std::set<base::FilePath> tables_found; |
| std::set<base::FilePath> backups_found; |
| for (std::vector<std::string>::iterator it = result->begin(); |
| it != result->end(); |
| ++it) { |
| base::FilePath current = CreateFilePath(*it); |
| if (current.MatchesExtension(table_extension)) |
| tables_found.insert(current.RemoveExtension()); |
| if (current.MatchesExtension(backup_table_extension)) |
| backups_found.insert(current.RemoveExtension()); |
| } |
| std::set<base::FilePath> backups_only; |
| std::set_difference(backups_found.begin(), |
| backups_found.end(), |
| tables_found.begin(), |
| tables_found.end(), |
| std::inserter(backups_only, backups_only.begin())); |
| if (backups_only.size()) { |
| std::string uma_name(name_); |
| uma_name.append(".MissingFiles"); |
| int num_missing_files = |
| backups_only.size() > INT_MAX ? INT_MAX : backups_only.size(); |
| base::Histogram::FactoryGet(uma_name, |
| 1 /*min*/, |
| 100 /*max*/, |
| 8 /*num_buckets*/, |
| base::Histogram::kUmaTargetedHistogramFlag) |
| ->Add(num_missing_files); |
| } |
| base::FilePath dir_filepath = base::FilePath::FromUTF8Unsafe(dir); |
| for (std::set<base::FilePath>::iterator it = backups_only.begin(); |
| it != backups_only.end(); |
| ++it) { |
| base::FilePath restored_table_name = |
| RestoreFromBackup(dir_filepath.Append(*it)); |
| result->push_back(FilePathToString(restored_table_name.BaseName())); |
| } |
| } |
| |
| namespace { |
| #if defined(OS_WIN) |
| static base::PlatformFileError GetDirectoryEntries( |
| const base::FilePath& dir_filepath, |
| std::vector<base::FilePath>* result) { |
| // TODO(dgrogan): Replace with FindFirstFile / FindNextFile. Note that until |
| // that happens this is filtering out directories whereas the Posix version |
| // below is not. There shouldn't be any directories so this shouldn't be an |
| // issue. |
| base::FileEnumerator iter(dir_filepath, false, base::FileEnumerator::FILES); |
| base::FilePath current = iter.Next(); |
| while (!current.empty()) { |
| result->push_back(current.BaseName()); |
| current = iter.Next(); |
| } |
| return base::PLATFORM_FILE_OK; |
| } |
| #else |
| static base::PlatformFileError GetDirectoryEntries( |
| const base::FilePath& dir_filepath, |
| std::vector<base::FilePath>* result) { |
| const std::string dir_string = FilePathToString(dir_filepath); |
| result->clear(); |
| DIR* dir = opendir(dir_string.c_str()); |
| if (!dir) |
| return base::ErrnoToPlatformFileError(errno); |
| struct dirent dent_buf; |
| struct dirent* dent; |
| int readdir_result; |
| while ((readdir_result = readdir_r(dir, &dent_buf, &dent)) == 0 && dent) |
| result->push_back(CreateFilePath(dent->d_name)); |
| int saved_errno = errno; |
| closedir(dir); |
| if (readdir_result != 0) |
| return base::ErrnoToPlatformFileError(saved_errno); |
| return base::PLATFORM_FILE_OK; |
| } |
| #endif |
| } |
| |
| Status ChromiumEnv::GetChildren(const std::string& dir_string, |
| std::vector<std::string>* result) { |
| std::vector<base::FilePath> entries; |
| base::PlatformFileError error = |
| GetDirectoryEntries(CreateFilePath(dir_string), &entries); |
| if (error != base::PLATFORM_FILE_OK) { |
| RecordOSError(kGetChildren, error); |
| return MakeIOError( |
| dir_string, "Could not open/read directory", kGetChildren, error); |
| } |
| for (std::vector<base::FilePath>::iterator it = entries.begin(); |
| it != entries.end(); |
| ++it) { |
| result->push_back(FilePathToString(*it)); |
| } |
| |
| if (make_backup_) |
| RestoreIfNecessary(dir_string, result); |
| return Status::OK(); |
| } |
| |
| Status ChromiumEnv::DeleteFile(const std::string& fname) { |
| Status result; |
| base::FilePath fname_filepath = CreateFilePath(fname); |
| // TODO(jorlow): Should we assert this is a file? |
| if (!::base::DeleteFile(fname_filepath, false)) { |
| result = MakeIOError(fname, "Could not delete file.", kDeleteFile); |
| RecordErrorAt(kDeleteFile); |
| } |
| if (make_backup_ && fname_filepath.MatchesExtension(table_extension)) { |
| base::DeleteFile(fname_filepath.ReplaceExtension(backup_table_extension), |
| false); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::CreateDir(const std::string& name) { |
| Status result; |
| base::PlatformFileError error = base::PLATFORM_FILE_OK; |
| Retrier retrier(kCreateDir, this); |
| do { |
| if (::file_util::CreateDirectoryAndGetError(CreateFilePath(name), &error)) |
| return result; |
| } while (retrier.ShouldKeepTrying(error)); |
| result = MakeIOError(name, "Could not create directory.", kCreateDir, error); |
| RecordOSError(kCreateDir, error); |
| return result; |
| } |
| |
| Status ChromiumEnv::DeleteDir(const std::string& name) { |
| Status result; |
| // TODO(jorlow): Should we assert this is a directory? |
| if (!::base::DeleteFile(CreateFilePath(name), false)) { |
| result = MakeIOError(name, "Could not delete directory.", kDeleteDir); |
| RecordErrorAt(kDeleteDir); |
| } |
| return result; |
| } |
| |
| Status ChromiumEnv::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 = MakeIOError(fname, "Could not determine file size.", kGetFileSize); |
| RecordErrorAt(kGetFileSize); |
| } else { |
| *size = static_cast<uint64_t>(signed_size); |
| } |
| return s; |
| } |
| |
| Status ChromiumEnv::RenameFile(const std::string& src, const std::string& dst) { |
| Status result; |
| base::FilePath src_file_path = CreateFilePath(src); |
| if (!::base::PathExists(src_file_path)) |
| return result; |
| base::FilePath destination = CreateFilePath(dst); |
| |
| Retrier retrier(kRenameFile, this); |
| base::PlatformFileError error = base::PLATFORM_FILE_OK; |
| do { |
| if (base::ReplaceFile(src_file_path, destination, &error)) |
| return result; |
| } while (retrier.ShouldKeepTrying(error)); |
| |
| DCHECK(error != base::PLATFORM_FILE_OK); |
| RecordOSError(kRenameFile, error); |
| char buf[100]; |
| snprintf(buf, |
| sizeof(buf), |
| "Could not rename file: %s", |
| PlatformFileErrorString(error)); |
| return MakeIOError(src, buf, kRenameFile, error); |
| } |
| |
| Status ChromiumEnv::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; |
| bool created; |
| ::base::PlatformFileError error_code; |
| ::base::PlatformFile file; |
| Retrier retrier(kLockFile, this); |
| do { |
| file = ::base::CreatePlatformFile( |
| CreateFilePath(fname), flags, &created, &error_code); |
| } while (error_code != ::base::PLATFORM_FILE_OK && |
| retrier.ShouldKeepTrying(error_code)); |
| |
| if (error_code == ::base::PLATFORM_FILE_ERROR_NOT_FOUND) { |
| ::base::FilePath parent = CreateFilePath(fname).DirName(); |
| ::base::FilePath last_parent; |
| int num_missing_ancestors = 0; |
| do { |
| if (base::DirectoryExists(parent)) |
| break; |
| ++num_missing_ancestors; |
| last_parent = parent; |
| parent = parent.DirName(); |
| } while (parent != last_parent); |
| RecordLockFileAncestors(num_missing_ancestors); |
| } |
| |
| if (error_code != ::base::PLATFORM_FILE_OK) { |
| result = MakeIOError( |
| fname, PlatformFileErrorString(error_code), kLockFile, error_code); |
| RecordOSError(kLockFile, error_code); |
| return result; |
| } |
| |
| if (!locks_.Insert(fname)) { |
| result = MakeIOError(fname, "Lock file already locked.", kLockFile); |
| ::base::ClosePlatformFile(file); |
| return result; |
| } |
| |
| Retrier lock_retrier = Retrier(kLockFile, this); |
| do { |
| error_code = ::base::LockPlatformFile(file); |
| } while (error_code != ::base::PLATFORM_FILE_OK && |
| retrier.ShouldKeepTrying(error_code)); |
| |
| if (error_code != ::base::PLATFORM_FILE_OK) { |
| ::base::ClosePlatformFile(file); |
| locks_.Remove(fname); |
| result = MakeIOError( |
| fname, PlatformFileErrorString(error_code), kLockFile, error_code); |
| RecordOSError(kLockFile, error_code); |
| return result; |
| } |
| |
| ChromiumFileLock* my_lock = new ChromiumFileLock; |
| my_lock->file_ = file; |
| my_lock->name_ = fname; |
| *lock = my_lock; |
| return result; |
| } |
| |
| Status ChromiumEnv::UnlockFile(FileLock* lock) { |
| ChromiumFileLock* my_lock = reinterpret_cast<ChromiumFileLock*>(lock); |
| Status result; |
| |
| ::base::PlatformFileError error_code = |
| ::base::UnlockPlatformFile(my_lock->file_); |
| if (error_code != ::base::PLATFORM_FILE_OK) { |
| result = |
| MakeIOError(my_lock->name_, "Could not unlock lock file.", kUnlockFile); |
| RecordOSError(kUnlockFile, error_code); |
| ::base::ClosePlatformFile(my_lock->file_); |
| } else if (!::base::ClosePlatformFile(my_lock->file_)) { |
| result = |
| MakeIOError(my_lock->name_, "Could not close lock file.", kUnlockFile); |
| RecordErrorAt(kUnlockFile); |
| } |
| bool removed = locks_.Remove(my_lock->name_); |
| DCHECK(removed); |
| delete my_lock; |
| return result; |
| } |
| |
| Status ChromiumEnv::GetTestDirectory(std::string* path) { |
| mu_.Acquire(); |
| if (test_directory_.empty()) { |
| if (!::file_util::CreateNewTempDirectory(kLevelDBTestDirectoryPrefix, |
| &test_directory_)) { |
| mu_.Release(); |
| RecordErrorAt(kGetTestDirectory); |
| return MakeIOError( |
| "Could not create temp directory.", "", kGetTestDirectory); |
| } |
| } |
| *path = FilePathToString(test_directory_); |
| mu_.Release(); |
| return Status::OK(); |
| } |
| |
| Status ChromiumEnv::NewLogger(const std::string& fname, Logger** result) { |
| FILE* f = fopen_internal(fname.c_str(), "w"); |
| if (f == NULL) { |
| *result = NULL; |
| int saved_errno = errno; |
| RecordOSError(kNewLogger, saved_errno); |
| return MakeIOError(fname, strerror(saved_errno), kNewLogger, saved_errno); |
| } else { |
| *result = new ChromiumLogger(f); |
| return Status::OK(); |
| } |
| } |
| |
| uint64_t ChromiumEnv::NowMicros() { |
| return ::base::TimeTicks::Now().ToInternalValue(); |
| } |
| |
| void ChromiumEnv::SleepForMicroseconds(int micros) { |
| // Round up to the next millisecond. |
| ::base::PlatformThread::Sleep(::base::TimeDelta::FromMicroseconds(micros)); |
| } |
| |
| void ChromiumEnv::RecordErrorAt(MethodID method) const { |
| GetMethodIOErrorHistogram()->Add(method); |
| } |
| |
| void ChromiumEnv::RecordLockFileAncestors(int num_missing_ancestors) const { |
| GetLockFileAncestorHistogram()->Add(num_missing_ancestors); |
| } |
| |
| void ChromiumEnv::RecordOSError(MethodID method, |
| base::PlatformFileError error) const { |
| DCHECK(error < 0); |
| RecordErrorAt(method); |
| GetOSErrorHistogram(method, -base::PLATFORM_FILE_ERROR_MAX)->Add(-error); |
| } |
| |
| void ChromiumEnv::RecordOSError(MethodID method, int error) const { |
| DCHECK(error > 0); |
| RecordErrorAt(method); |
| GetOSErrorHistogram(method, ERANGE + 1)->Add(error); |
| } |
| |
| void ChromiumEnv::RecordBackupResult(bool result) const { |
| std::string uma_name(name_); |
| uma_name.append(".TableBackup"); |
| base::BooleanHistogram::FactoryGet( |
| uma_name, base::Histogram::kUmaTargetedHistogramFlag)->AddBoolean(result); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetOSErrorHistogram(MethodID method, |
| int limit) const { |
| std::string uma_name(name_); |
| // TODO(dgrogan): This is probably not the best way to concatenate strings. |
| uma_name.append(".IOError.").append(MethodIDToString(method)); |
| return base::LinearHistogram::FactoryGet(uma_name, 1, limit, limit + 1, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetMethodIOErrorHistogram() const { |
| std::string uma_name(name_); |
| uma_name.append(".IOError"); |
| return base::LinearHistogram::FactoryGet(uma_name, 1, kNumEntries, |
| kNumEntries + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetMaxFDHistogram( |
| const std::string& type) const { |
| std::string uma_name(name_); |
| uma_name.append(".MaxFDs.").append(type); |
| // These numbers make each bucket twice as large as the previous bucket. |
| const int kFirstEntry = 1; |
| const int kLastEntry = 65536; |
| const int kNumBuckets = 18; |
| return base::Histogram::FactoryGet( |
| uma_name, kFirstEntry, kLastEntry, kNumBuckets, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetLockFileAncestorHistogram() const { |
| std::string uma_name(name_); |
| uma_name.append(".LockFileAncestorsNotFound"); |
| const int kMin = 1; |
| const int kMax = 10; |
| const int kNumBuckets = 11; |
| return base::LinearHistogram::FactoryGet( |
| uma_name, kMin, kMax, kNumBuckets, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetRetryTimeHistogram(MethodID method) const { |
| std::string uma_name(name_); |
| // TODO(dgrogan): This is probably not the best way to concatenate strings. |
| uma_name.append(".TimeUntilSuccessFor").append(MethodIDToString(method)); |
| |
| const int kBucketSizeMillis = 25; |
| // Add 2, 1 for each of the buckets <1 and >max. |
| const int kNumBuckets = kMaxRetryTimeMillis / kBucketSizeMillis + 2; |
| return base::Histogram::FactoryTimeGet( |
| uma_name, base::TimeDelta::FromMilliseconds(1), |
| base::TimeDelta::FromMilliseconds(kMaxRetryTimeMillis + 1), |
| kNumBuckets, |
| base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| base::HistogramBase* ChromiumEnv::GetRecoveredFromErrorHistogram( |
| MethodID method) const { |
| std::string uma_name(name_); |
| uma_name.append(".RetryRecoveredFromErrorIn") |
| .append(MethodIDToString(method)); |
| return base::LinearHistogram::FactoryGet(uma_name, 1, kNumEntries, |
| kNumEntries + 1, base::Histogram::kUmaTargetedHistogramFlag); |
| } |
| |
| 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() { |
| base::PlatformThread::SetName(name_.c_str()); |
| |
| 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(); |
| TRACE_EVENT0("leveldb", "ChromiumEnv::BGThread-Task"); |
| (*function)(arg); |
| } |
| } |
| |
| void ChromiumEnv::StartThread(void (*function)(void* arg), void* arg) { |
| new Thread(function, arg); // Will self-delete. |
| } |
| |
| static std::string GetDirName(const std::string& filename) { |
| base::FilePath file = base::FilePath::FromUTF8Unsafe(filename); |
| return FilePathToString(file.DirName()); |
| } |
| |
| void ChromiumEnv::DidCreateNewFile(const std::string& filename) { |
| base::AutoLock auto_lock(map_lock_); |
| needs_sync_map_[GetDirName(filename)] = true; |
| } |
| |
| bool ChromiumEnv::DoesDirNeedSync(const std::string& filename) { |
| base::AutoLock auto_lock(map_lock_); |
| return needs_sync_map_.find(GetDirName(filename)) != needs_sync_map_.end(); |
| } |
| |
| void ChromiumEnv::DidSyncDir(const std::string& filename) { |
| base::AutoLock auto_lock(map_lock_); |
| needs_sync_map_.erase(GetDirName(filename)); |
| } |
| |
| } // namespace leveldb_env |
| |
| namespace leveldb { |
| |
| Env* IDBEnv() { |
| return leveldb_env::idb_env.Pointer(); |
| } |
| |
| Env* Env::Default() { |
| return leveldb_env::default_env.Pointer(); |
| } |
| |
| } // namespace leveldb |
| |