// 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. #if defined(LEVELDB_PLATFORM_WINDOWS) #define VC_EXTRALEAN // Exclude rarely-used stuff #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include #include "leveldb/env.h" #include "leveldb/slice.h" #include "util/win_logger.h" #include "port/port.h" #include "util/logging.h" #include #include #include #include #include #include #include #include #include "Filepath.h" #define MAX_FILENAME 512 namespace leveldb { namespace { class NoOpLogger : public Logger { public: virtual void Logv(const char* format, va_list ap) { } }; struct IOException : public std::exception { std::string s; IOException(std::string ss) : s(ss) {} ~IOException() throw () {} // Updated const char* what() const throw() { return s.c_str(); } }; static std::string ws2s(const std::wstring& ws) { int len; int wslength = (int)ws.length() + 1; len = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), wslength, 0, 0, NULL, NULL); char* buf = new char[len]; WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), wslength, buf, len, NULL, NULL); std::string r(buf); delete[] buf; return r; } static Status GetLastWindowsError(const std::string& name) { WCHAR lpBuffer[256] = L"?"; FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, // It's a system error NULL, // No string to be formatted needed GetLastError(), // Hey Windows: Please explain this error! MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Do it in the standard language lpBuffer, // Put the message here (sizeof(lpBuffer) / sizeof(WCHAR)), // Number of characters to store the message NULL); return Status::IOError(name, ws2s(lpBuffer).c_str()); } static std::wstring GetFullPath(const std::string& fname) { return ::port::toFilePath(fname); } static void EnsureDirectory(const std::string& fname) { std::string dir = fname; std::replace(dir.begin(), dir.end(), '/', '\\'); char tmpName[MAX_FILENAME]; strcpy_s(tmpName, dir.c_str()); // Create parent directories for (char* p = strchr(tmpName, '\\'); p; p = strchr(p + 1, '\\')) { *p = 0; ::CreateDirectoryW(GetFullPath(tmpName).c_str(), NULL); // may or may not already exist *p = '\\'; } } static Status OpenFile(const std::string& fname, DWORD dwDesiredAccess, DWORD dwShareMode, DWORD dwCreationDisposition, HANDLE& file, DWORD dwFlags = 0) { EnsureDirectory(fname); std::wstring path = GetFullPath(fname); #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP | WINAPI_PARTITION_SYSTEM) && (_WIN32_WINNT >= 0x0602) CREATEFILE2_EXTENDED_PARAMETERS extraParams; ZeroMemory(&extraParams, sizeof(extraParams)); extraParams.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; extraParams.dwSize = sizeof(extraParams); extraParams.dwFileFlags = dwFlags; file = ::CreateFile2(path.c_str(), dwDesiredAccess, dwShareMode, dwCreationDisposition, &extraParams); #else file = ::CreateFileW(path.c_str(), dwDesiredAccess, dwShareMode, NULL, dwCreationDisposition, dwFlags ? dwFlags : FILE_ATTRIBUTE_NORMAL, NULL); #endif return (file == INVALID_HANDLE_VALUE ? GetLastWindowsError(fname) : Status::OK()); } static Status CloseFile(const std::string& fname, HANDLE& file) { if (file != INVALID_HANDLE_VALUE) { BOOL ret = ::CloseHandle(file); file = INVALID_HANDLE_VALUE; return (!ret ? GetLastWindowsError(fname) : Status::OK()); } else return Status::OK(); } // returns the ID of the current process static uint32_t current_process_id(void) { return static_cast(::GetCurrentProcessId()); } class WinSequentialFile : public SequentialFile { private: std::string _fname; HANDLE _file; public: WinSequentialFile(const std::string& fname) : _fname(fname) { Status s = OpenFile(fname, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, _file); if (!s.ok()) throw IOException(s.ToString().c_str()); } virtual ~WinSequentialFile() { CloseFile(_fname, _file); } virtual Status Read(size_t n, Slice* result, char* scratch) { DWORD dwRead; BOOL ret = ::ReadFile(_file, scratch, n, &dwRead, NULL); if (!ret) return GetLastWindowsError(_fname); *result = Slice(scratch, dwRead); if (dwRead < n) { LARGE_INTEGER cur, end; ret = ::SetFilePointerEx(_file, LARGE_INTEGER(), &cur, FILE_CURRENT); if (!ret) return GetLastWindowsError(_fname); ret = ::SetFilePointerEx(_file, LARGE_INTEGER(), &end, FILE_END); if (!ret) return GetLastWindowsError(_fname); if (end.QuadPart > cur.QuadPart) { // couldn't read enough bytes ::SetFilePointerEx(_file, cur, NULL, FILE_CURRENT); return Status::IOError(_fname, "Couldn't read all data"); } else return Status::OK(); } else return Status::OK(); } virtual Status Skip(uint64_t n) { LARGE_INTEGER cur; cur.QuadPart = n; return (!::SetFilePointerEx(_file, cur, NULL, FILE_CURRENT) ? GetLastWindowsError(_fname) : Status::OK()); } }; class WinRandomAccessFile : public RandomAccessFile { private: std::string _fname; HANDLE _file; public: WinRandomAccessFile(const std::string& fname) : _fname(fname) { Status s = OpenFile(fname, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, _file, FILE_FLAG_OVERLAPPED | FILE_FLAG_RANDOM_ACCESS); if (!s.ok()) throw IOException(s.ToString().c_str()); } virtual ~WinRandomAccessFile() { CloseFile(_fname, _file); } virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const { OVERLAPPED readDesc; ZeroMemory(&readDesc, sizeof(readDesc)); readDesc.Offset = offset; readDesc.OffsetHigh = offset >> 32; readDesc.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); if (readDesc.hEvent == NULL) { return GetLastWindowsError(_fname); } DWORD dwRead = 0; BOOL ret = ::ReadFile(_file, scratch, n, NULL, &readDesc); // the function might be completing asynchronously if (ret == 0 && GetLastError() != ERROR_IO_PENDING) { ::CloseHandle(readDesc.hEvent); return GetLastWindowsError(_fname); } // Wait until the read is completed ret = WaitForSingleObject(readDesc.hEvent, INFINITE); if (ret == WAIT_FAILED) { ::CloseHandle(readDesc.hEvent); return GetLastWindowsError(_fname); } // then read the result and the read bytes ret = GetOverlappedResult(_file, &readDesc, &dwRead, FALSE); if(ret == 0) { ::CloseHandle(readDesc.hEvent); return GetLastWindowsError(_fname); } *result = Slice(scratch, dwRead); ::CloseHandle(readDesc.hEvent); return Status::OK(); } }; // We preallocate up to an extra megabyte and use memcpy to append new // data to the file. This is safe since we either properly close the // file before reading from it, or for log files, the reading code // knows enough to skip zero suffixes. class WinFile : public WritableFile { private: std::string _fname; HANDLE _file; public: explicit WinFile(std::string fname) : _fname(fname) { Status s = OpenFile(fname, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, CREATE_ALWAYS, _file); if (!s.ok()) throw IOException(s.ToString().c_str()); } virtual ~WinFile() { Close(); } private: public: virtual Status Append(const Slice& data) { DWORD dwWritten; BOOL ret = ::WriteFile(_file, data.data(), data.size(), &dwWritten, NULL); return ((!ret || dwWritten < data.size()) ? GetLastWindowsError(_fname) : Status::OK()); } virtual Status Close() { return CloseFile(_fname, _file); } virtual Status Flush() { //BOOL ret = ::FlushFileBuffers(_file); //return (!ret ? GetLastWindowsError(_fname) : Status::OK()); return Status::OK(); } virtual Status Sync() { BOOL ret = ::FlushFileBuffers(_file); return (!ret ? GetLastWindowsError(_fname) : Status::OK()); //return Flush(); } }; class WinFileLock : public FileLock { private: std::string _fname; HANDLE _file; DWORD _fileSizeHigh; DWORD _fileSizeLow; public: WinFileLock(const std::string& fname) : _fname(fname) { FILE_STANDARD_INFO fi; Status s = OpenFile(fname, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_ALWAYS, _file); if (!s.ok()) throw IOException(s.ToString().c_str()); if (_file != INVALID_HANDLE_VALUE && GetFileInformationByHandleEx(_file, FILE_INFO_BY_HANDLE_CLASS::FileStandardInfo, &fi, sizeof(fi))) { _fileSizeLow = fi.EndOfFile.LowPart; _fileSizeHigh = fi.EndOfFile.HighPart; if (_fileSizeLow > 0 || _fileSizeHigh > 0) { OVERLAPPED overlapped = { }; if (!::LockFileEx(_file, 0, 0, _fileSizeLow, _fileSizeHigh, &overlapped)) { Status s = GetLastWindowsError(fname); throw IOException(s.ToString().c_str()); } } } else { _fileSizeLow = _fileSizeHigh = 0; } } ~WinFileLock() { if (_file != INVALID_HANDLE_VALUE) { if (_fileSizeLow > 0 || _fileSizeHigh > 0) if (!::UnlockFileEx(_file, 0, _fileSizeLow, _fileSizeHigh, NULL)) { Status s = GetLastWindowsError(_fname); } CloseFile(_fname, _file); } } }; class WinRTEnv : public Env { public: WinRTEnv(); virtual ~WinRTEnv() { fprintf(stderr, "Destroying Env::Default()\n"); } virtual Status NewSequentialFile(const std::string& fname, SequentialFile** result) { Status s; try { *result = new WinSequentialFile(fname); } catch (const IOException & e) { s = Status::IOError(fname, e.what()); } return s; } virtual Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result) { Status s; try { *result = new WinRandomAccessFile(fname); } catch (const IOException & e) { s = Status::IOError(fname, e.what()); } return s; } virtual Status NewWritableFile(const std::string& fname, WritableFile** result) { Status s; try { // will create a new empty file to write to *result = new WinFile(fname); } catch(const IOException & e) { s = Status::IOError(fname, e.what()); } return s; } virtual bool FileExists(const std::string& fname) { WIN32_FILE_ATTRIBUTE_DATA fi; return (GetFileAttributesExW(GetFullPath(fname).c_str(), GET_FILEEX_INFO_LEVELS::GetFileExInfoStandard, &fi) ? true : false); } virtual Status GetChildren(const std::string& dir, std::vector* result) { std::string path = dir; result->clear(); WIN32_FIND_DATAW ffd; HANDLE hFind; path = dir + "/*"; hFind = FindFirstFileExW(GetFullPath(path).c_str(), FINDEX_INFO_LEVELS::FindExInfoStandard, &ffd, FINDEX_SEARCH_OPS::FindExSearchNameMatch, NULL, 0); if(INVALID_HANDLE_VALUE == hFind) { return GetLastWindowsError(path); } do { result->push_back(ws2s(ffd.cFileName)); } while(FindNextFileW(hFind, &ffd) != 0); FindClose(hFind); return Status::OK(); } virtual Status DeleteFile(const std::string& fname) { if (::DeleteFileW(GetFullPath(fname).c_str()) != 0) { return Status::OK(); } else { return GetLastWindowsError(fname); } } virtual Status CreateDir(const std::string& name) { EnsureDirectory(name); ::CreateDirectoryW(GetFullPath(name).c_str(), NULL); return Status::OK(); }; virtual Status DeleteDir(const std::string& name) { BOOL ret = ::RemoveDirectoryW(GetFullPath(name).c_str()); if (!ret) Status s = GetLastWindowsError(name); return Status::OK(); }; virtual Status GetFileSize(const std::string& fname, uint64_t* size) { WIN32_FILE_ATTRIBUTE_DATA fi; BOOL ret = GetFileAttributesExW(GetFullPath(fname).c_str(), GET_FILEEX_INFO_LEVELS::GetFileExInfoStandard, &fi); if (!ret) return GetLastWindowsError(fname); *size = ((uint64_t)fi.nFileSizeLow + ((uint64_t)fi.nFileSizeHigh << 32)); return Status::OK(); } virtual Status RenameFile(const std::string& src, const std::string& target) { std::wstring fullsrc = GetFullPath(src); std::wstring fulltarget = GetFullPath(target); ::DeleteFileW(fulltarget.c_str()); if (::MoveFileExW(fullsrc.c_str(), fulltarget.c_str(), 0) != TRUE) { return GetLastWindowsError(src); } else { return Status::OK(); } } virtual Status LockFile(const std::string& fname, FileLock** lock) { *lock = NULL; if (!FileExists(fname)) { HANDLE file; Status s = OpenFile(fname, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, CREATE_ALWAYS, file); if (s.ok()) CloseFile(fname, file); } try { *lock = new WinFileLock(fname); } catch (const IOException & e) { return Status::IOError(fname, e.what()); } return Status::OK(); } virtual Status UnlockFile(FileLock* lock) { delete lock; return Status::OK(); } virtual void Schedule(void(*function)(void*), void* arg); virtual void StartThread(void(*function)(void* arg), void* arg); virtual Status GetTestDirectory(std::string* result) { std::stringstream ss; ss << "tmp/leveldb_tests/" << current_process_id(); // Directory may already exist CreateDir(ss.str()); *result = ss.str(); return Status::OK(); } #ifndef WIN32 static uint64_t gettid() { pthread_t tid = pthread_self(); uint64_t thread_id = 0; memcpy(&thread_id, &tid, std::min(sizeof(thread_id), sizeof(tid))); return thread_id; } #endif virtual Status NewLogger(const std::string& fname, Logger** result) { *result = new NoOpLogger(); return Status::OK(); } virtual uint64_t NowMicros() { const auto now = std::chrono::high_resolution_clock::now().time_since_epoch(); return std::chrono::duration_cast(now).count(); } virtual void SleepForMicroseconds(int micros) { std::this_thread::sleep_for(std::chrono::microseconds(micros)); } private: // BGThread() is the body of the background thread void BGThread(); static void BGThreadWrapper(void* arg) { reinterpret_cast(arg)->BGThread(); } std::mutex mu_; std::condition_variable bgsignal_; std::unique_ptr bgthread_; // Entry per Schedule() call struct BGItem { void* arg; void(*function)(void*); }; typedef std::deque BGQueue; BGQueue queue_; }; WinRTEnv::WinRTEnv() {} void WinRTEnv::Schedule(void(*function)(void*), void* arg) { std::unique_lock lock(mu_); // Start background thread if necessary if(!bgthread_) { bgthread_.reset( new std::thread(&BGThreadWrapper, this)); } // Add to priority queue queue_.push_back(BGItem()); queue_.back().function = function; queue_.back().arg = arg; lock.unlock(); bgsignal_.notify_one(); } void WinRTEnv::BGThread() { while(true) { // Wait until there is an item that is ready to run std::unique_lock lock(mu_); while(queue_.empty()) { bgsignal_.wait(lock); } void(*function)(void*) = queue_.front().function; void* arg = queue_.front().arg; queue_.pop_front(); lock.unlock(); (*function)(arg); } } namespace { struct StartThreadState { void(*user_function)(void*); void* arg; }; } void WinRTEnv::StartThread(void(*function)(void* arg), void* arg) { std::thread new_thread(function, arg); new_thread.detach(); } } static INIT_ONCE g_InitOnce = INIT_ONCE_STATIC_INIT; static Env* default_env; static BOOL CALLBACK InitDefaultEnv(PINIT_ONCE InitOnce, PVOID Parameter, PVOID *lpContext) { default_env = new WinRTEnv; return TRUE; } Env* Env::Default() { #if 0 PVOID lpContext; InitOnceExecuteOnce(&g_InitOnce, // One-time initialization structure InitDefaultEnv, // Pointer to initialization callback function "", // Optional parameter to callback function (not used) &lpContext); // Receives pointer to event object stored in g_InitOnce #else if (default_env == NULL) InitDefaultEnv(NULL, NULL, NULL); #endif return default_env; } } #endif