// Copyright (c) 2011-2013 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 "base/debug/trace_event.h" #include "base/metrics/histogram.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/utf_string_conversions.h" #include "chromium_logger.h" #include "env_chromium_stdio.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> #endif using namespace leveldb; namespace leveldb_env { namespace { #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(base::UTF8ToUTF16(fname).c_str(), base::ASCIIToUTF16(mode).c_str()); #else return fopen(fname, mode); #endif } 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_; mutable ::base::File file_; const UMALogger* uma_logger_; public: ChromiumRandomAccessFile(const std::string& fname, ::base::File file, const UMALogger* uma_logger) : filename_(fname), file_(file.Pass()), uma_logger_(uma_logger) {} virtual ~ChromiumRandomAccessFile() {} virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const { Status s; int r = file_.Read(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; } }; } // unnamed namespace 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 (ChromiumEnv::HasTableExtension(path)) file_type_ = kTable; if (file_type_ != kManifest) tracker_->DidCreateNewFile(filename_); parent_dir_ = FilePathToString(ChromiumEnv::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); }; 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; } Status ChromiumWritableFile::Sync() { TRACE_EVENT0("leveldb", "ChromiumEnvStdio::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 = ChromiumEnv::MakeBackup(filename_); uma_logger_->RecordBackupResult(success); } return result; } ChromiumEnvStdio::ChromiumEnvStdio() {} ChromiumEnvStdio::~ChromiumEnvStdio() {} Status ChromiumEnvStdio::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 ChromiumEnvStdio::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 ChromiumEnvStdio::NewRandomAccessFile(const std::string& fname, RandomAccessFile** result) { int flags = ::base::File::FLAG_READ | ::base::File::FLAG_OPEN; ::base::File file(ChromiumEnv::CreateFilePath(fname), flags); if (file.IsValid()) { *result = new ChromiumRandomAccessFile(fname, file.Pass(), this); RecordOpenFilesLimit("Success"); return Status::OK(); } ::base::File::Error error_code = file.error_details(); if (error_code == ::base::File::FILE_ERROR_TOO_MANY_OPENED) RecordOpenFilesLimit("TooManyOpened"); else RecordOpenFilesLimit("OtherError"); *result = NULL; RecordOSError(kNewRandomAccessFile, error_code); return MakeIOError(fname, FileErrorString(error_code), kNewRandomAccessFile, error_code); } Status ChromiumEnvStdio::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(); } } #if defined(OS_WIN) base::File::Error ChromiumEnvStdio::GetDirectoryEntries( const base::FilePath& dir_param, std::vector<base::FilePath>* result) const { result->clear(); base::FilePath dir_filepath = dir_param.Append(FILE_PATH_LITERAL("*")); WIN32_FIND_DATA find_data; HANDLE find_handle = FindFirstFile(dir_filepath.value().c_str(), &find_data); if (find_handle == INVALID_HANDLE_VALUE) { DWORD last_error = GetLastError(); if (last_error == ERROR_FILE_NOT_FOUND) return base::File::FILE_OK; return base::File::OSErrorToFileError(last_error); } do { base::FilePath filepath(find_data.cFileName); base::FilePath::StringType basename = filepath.BaseName().value(); if (basename == FILE_PATH_LITERAL(".") || basename == FILE_PATH_LITERAL("..")) continue; result->push_back(filepath.BaseName()); } while (FindNextFile(find_handle, &find_data)); DWORD last_error = GetLastError(); base::File::Error return_value = base::File::FILE_OK; if (last_error != ERROR_NO_MORE_FILES) return_value = base::File::OSErrorToFileError(last_error); FindClose(find_handle); return return_value; } #else base::File::Error ChromiumEnvStdio::GetDirectoryEntries( const base::FilePath& dir_filepath, std::vector<base::FilePath>* result) const { const std::string dir_string = FilePathToString(dir_filepath); result->clear(); DIR* dir = opendir(dir_string.c_str()); if (!dir) return base::File::OSErrorToFileError(errno); struct dirent dent_buf; struct dirent* dent; int readdir_result; while ((readdir_result = readdir_r(dir, &dent_buf, &dent)) == 0 && dent) { if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) continue; result->push_back(ChromiumEnv::CreateFilePath(dent->d_name)); } int saved_errno = errno; closedir(dir); if (readdir_result != 0) return base::File::OSErrorToFileError(saved_errno); return base::File::FILE_OK; } #endif Status ChromiumEnvStdio::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(); } } } // namespace leveldb_env