/* * originally from http://code.google.com/r/kkowalczyk-leveldb/ * code by Krzysztof Kowalczyk kkowalczyk kowalczyk@gmail.com * See also http://blog.kowalczyk.info/software/leveldb-for-windows/index.html */ #include #include #include #include #include "leveldb/env.h" #include "leveldb/slice.h" #include "port/port.h" #include "util/logging.h" #include "win_logger.h" #if defined(_MSC_VER) # ifdef DeleteFile # undef DeleteFile # endif #endif // To properly support file names on Windows we should be using Unicode // (WCHAR) strings. To accomodate existing interface which uses std::string, // we use the following convention: // * all filenames that we return (e.g. from GetTestDirectory()) are // utf8-encoded // * we'll try to interpret all input file names as if they're // utf8-encoded. If they're not valid utf8 strings, we'll try // to interpret them according to a current code page // This just works for names that don't use characters outside ascii // and for those that do, the caller needs to be aware of this convention // whenever it bubbles up to the user-level API. namespace leveldb { static Status IOError(const std::string& context, DWORD err = (DWORD)-1) { char *err_msg = NULL; Status s; if ((DWORD)-1 == err) err = GetLastError(); FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&err_msg, 0, NULL); if (!err_msg) return Status::IOError(context); s = Status::IOError(context, err_msg); LocalFree(err_msg); return s; } class WinSequentialFile: public SequentialFile { private: std::string fname_; HANDLE file_; public: WinSequentialFile(const std::string& fname, HANDLE f) : fname_(fname), file_(f) { } virtual ~WinSequentialFile() { CloseHandle(file_); } virtual Status Read(size_t n, Slice* result, char* scratch) { DWORD n2 = n; DWORD r = 0; BOOL ok = ReadFile(file_, (void*)scratch, n2, &r, NULL); *result = Slice(scratch, r); if (!ok) { // We leave status as ok if we hit the end of the file if (GetLastError() != ERROR_HANDLE_EOF) { return IOError(fname_); } } return Status::OK(); } virtual Status Skip(uint64_t n) { LARGE_INTEGER pos; pos.QuadPart = n; DWORD res = SetFilePointerEx(file_, pos, NULL, FILE_CURRENT); if (res == 0) return IOError(fname_); return Status::OK(); } }; class WinRandomAccessFile: public RandomAccessFile { private: std::string fname_; HANDLE file_; public: WinRandomAccessFile(const std::string& fname, HANDLE file) : fname_(fname), file_(file) { } virtual ~WinRandomAccessFile() { CloseHandle(file_); } virtual Status Read(uint64_t offset, size_t n, Slice* result, char* scratch) const { OVERLAPPED overlapped = { 0 }; overlapped.Offset = static_cast(offset); overlapped.OffsetHigh = static_cast(offset >> 32); DWORD bytes_read = 0; BOOL success = ReadFile(file_, scratch, n, &bytes_read, &overlapped); *result = Slice(scratch, bytes_read); return success != FALSE ? Status::OK() : Status::IOError(fname_); } }; class WinWritableFile : public WritableFile { private: std::string name_; HANDLE file_; public: WinWritableFile(std::string name, HANDLE h) : name_(name), file_(h) { } virtual ~WinWritableFile() { Close(); } virtual Status Append(const Slice& data) { DWORD n = data.size(); DWORD pos = 0; while (pos < n) { DWORD written = 0; BOOL ok = WriteFile(file_, data.data() + pos, n - pos, &written, NULL); if (!ok) return IOError(name_+ "Append: cannot write"); pos += written; } return Status::OK(); } virtual Status Close() { if (INVALID_HANDLE_VALUE == file_) return Status::OK(); Status s = Sync(); CloseHandle(file_); file_ = INVALID_HANDLE_VALUE; return s; } virtual Status Flush() { return Status::OK(); } virtual Status Sync() { BOOL ok = FlushFileBuffers(file_); if (!ok) return IOError(name_); return Status::OK(); } }; namespace { #define DIR_SEP_CHAR L'\\' #define DIR_SEP_STR L"\\" WCHAR *ToWcharFromCodePage(const char *src, UINT cp) { int required_buf_size = MultiByteToWideChar(cp, 0, src, -1, NULL, 0); if (0 == required_buf_size) // indicates an error return NULL; WCHAR *res = reinterpret_cast(malloc(sizeof(WCHAR) * required_buf_size)); if (!res) return NULL; MultiByteToWideChar(cp, 0, src, -1, res, required_buf_size); return res; } // try to convert to WCHAR string trying most common code pages // to be as permissive as we can be WCHAR *ToWcharPermissive(const char *s) { WCHAR *ws = ToWcharFromCodePage(s, CP_UTF8); if (ws != NULL) return ws; ws = ToWcharFromCodePage(s, CP_ACP); if (ws != NULL) return ws; ws = ToWcharFromCodePage(s, CP_OEMCP); return ws; } char *ToUtf8(const WCHAR *s) { int required_buf_size = WideCharToMultiByte(CP_UTF8, 0, s, -1, NULL, 0, NULL, NULL); char *res = (char*)malloc(sizeof(char) * required_buf_size); if (!res) return NULL; WideCharToMultiByte(CP_UTF8, 0, s, -1, res, required_buf_size, NULL, NULL); return res; } static size_t WstrLen(const WCHAR *s) { if (NULL == s) return 0; return wcslen(s); } static WCHAR *WstrJoin(const WCHAR *s1, const WCHAR *s2, const WCHAR *s3=NULL) { size_t s1_len = WstrLen(s1); size_t s2_len = WstrLen(s2); size_t s3_len = WstrLen(s3); size_t len =s1_len + s2_len + s3_len + 1; WCHAR *res = (WCHAR*)malloc(sizeof(WCHAR) * len); if (!res) return NULL; WCHAR *tmp = res; if (s1 != NULL) { memcpy(tmp, s1, s1_len * sizeof(WCHAR)); tmp += s1_len; } if (s2 != NULL) { memcpy(tmp, s2, s2_len * sizeof(WCHAR)); tmp += s2_len; } if (s3 != NULL) { memcpy(tmp, s3, s3_len * sizeof(WCHAR)); tmp += s3_len; } *tmp = 0; return res; } static bool WstrEndsWith(const WCHAR *s1, WCHAR c) { size_t len = WstrLen(s1); return ((len > 0) && (s1[len-1] == c)); } static WCHAR *WstrPathJoin(const WCHAR *s1, const WCHAR *s2) { if (WstrEndsWith(s1, DIR_SEP_CHAR)) return WstrJoin(s1, s2); return WstrJoin(s1, DIR_SEP_STR, s2); } // Return true if s is "." or "..", which are 2 directories // we should skip when enumerating a directory static bool SkipDir(const WCHAR *s) { if (*s == L'.') { if (s[1] == 0) return true; return ((s[1] == '.') && (s[2] == 0)); } return false; } class WinFileLock : public FileLock { public: WinFileLock(const std::string &fname, HANDLE file) : fname_(fname), file_(file) { } virtual ~WinFileLock() { Close(); } bool Close() { bool ok = true; if (file_ != INVALID_HANDLE_VALUE) ok = (CloseHandle(file_) != FALSE); file_ = INVALID_HANDLE_VALUE; return ok; } std::string fname_; HANDLE file_; }; class WinEnv : public Env { public: WinEnv(); virtual ~WinEnv() { fprintf(stderr, "Destroying Env::Default()\n"); //exit(1); } virtual Status NewSequentialFile(const std::string& fname, SequentialFile** result) { *result = NULL; WCHAR *file_name = ToWcharPermissive(fname.c_str()); if (file_name == NULL) { return Status::InvalidArgument("Invalid file name"); } HANDLE h = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); free((void*)file_name); if (h == INVALID_HANDLE_VALUE) { return IOError(fname); } *result = new WinSequentialFile(fname, h); return Status::OK(); } virtual Status NewRandomAccessFile(const std::string& fname, RandomAccessFile** result) { *result = NULL; WCHAR *file_name = ToWcharPermissive(fname.c_str()); if (file_name == NULL) { return Status::InvalidArgument("Invalid file name"); } HANDLE h = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); free((void*)file_name); if (h == INVALID_HANDLE_VALUE) { return IOError(fname); } *result = new WinRandomAccessFile(fname, h); return Status::OK(); } virtual Status NewWritableFile(const std::string& fname, WritableFile** result) { *result = NULL; WCHAR *file_name = ToWcharPermissive(fname.c_str()); if (file_name == NULL) return Status::InvalidArgument("Invalid file name"); HANDLE h = CreateFileW(file_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); free((void*)file_name); if (h == INVALID_HANDLE_VALUE) { return IOError(fname); } *result = new WinWritableFile(fname, h); return Status::OK(); } virtual bool FileExists(const std::string& fname) { WCHAR *file_name = ToWcharPermissive(fname.c_str()); if (file_name == NULL) return false; WIN32_FILE_ATTRIBUTE_DATA file_info; BOOL res = GetFileAttributesExW(file_name, GetFileExInfoStandard, &file_info); free((void*)file_name); if (0 == res) return false; if ((file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) return false; return true; } virtual Status GetChildren(const std::string& dir, std::vector* result) { result->clear(); WCHAR *dir_name = ToWcharPermissive(dir.c_str()); if (dir_name == NULL) return Status::InvalidArgument("Invalid file name"); WCHAR *pattern = WstrPathJoin(dir_name, L"*"); free(dir_name); if (NULL == pattern) return Status::InvalidArgument("Invalid file name"); WIN32_FIND_DATAW file_data; HANDLE h = FindFirstFileW(pattern, &file_data); free(pattern); if (INVALID_HANDLE_VALUE == h) { if (ERROR_FILE_NOT_FOUND == GetLastError()) return Status::OK(); return IOError(dir); } for (;;) { WCHAR *s = file_data.cFileName; if (!SkipDir(s)) { char *s2 = ToUtf8(s); result->push_back(s2); free(s2); } if (FALSE == FindNextFileW(h, &file_data)) break; } FindClose(h); return Status::OK(); } virtual Status DeleteFile(const std::string& fname) { WCHAR *file_path = ToWcharPermissive(fname.c_str()); if (file_path == NULL) return Status::InvalidArgument("Invalid file name"); BOOL ok = DeleteFileW(file_path); free(file_path); if (!ok) { DWORD err = GetLastError(); if ((ERROR_PATH_NOT_FOUND == err) || (ERROR_FILE_NOT_FOUND == err)) return Status::OK(); return IOError("DeleteFile " + fname); } return Status::OK(); } bool CreateDirIfNotExists(const WCHAR *dir) { BOOL ok = CreateDirectoryW(dir, NULL); if (ok) return true; return (ERROR_ALREADY_EXISTS == GetLastError()); } bool DirExists(const WCHAR *dir) { WIN32_FILE_ATTRIBUTE_DATA file_info; BOOL res = GetFileAttributesExW(dir, GetFileExInfoStandard, &file_info); if (0 == res) return false; return (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } WCHAR *WstrDupN(const WCHAR *s, size_t len) { void *res = malloc((len + 1) * sizeof(WCHAR)); if (!res) return NULL; memcpy(res, s, len * sizeof(WCHAR)); WCHAR *res2 = reinterpret_cast(res); res2[len] = 0; return res2; } bool IsPathSep(WCHAR c) { return (c == '\\') || (c == '/'); } WCHAR *GetPathParent(const WCHAR *path) { const WCHAR *last_sep = NULL; const WCHAR *tmp = path; // find the last path separator // (ignoring one at the end of the string) while (*tmp) { if (IsPathSep(*tmp)) { if (0 != tmp[1]) last_sep = tmp; } ++tmp; } if (NULL == last_sep) return NULL; size_t len = last_sep - path; return WstrDupN(path, len); } bool CreateDirRecursive(WCHAR *dir) { WCHAR *parent = GetPathParent(dir); bool ok = true; if (parent && !DirExists(parent)) { ok = CreateDirRecursive(parent); } free(parent); if (!ok) return false; return CreateDirIfNotExists(dir); } virtual Status CreateDir(const std::string& name) { WCHAR *dir = ToWcharPermissive(name.c_str()); if (dir == NULL) return Status::InvalidArgument("Invalid file name"); bool ok = CreateDirRecursive(dir); free(dir); if (!ok) return IOError(name); return Status::OK(); } #if 1 virtual Status DeleteDir(const std::string& name) { WCHAR *dir = ToWcharPermissive(name.c_str()); if (dir == NULL) return Status::InvalidArgument("Invalid file name"); BOOL ok = RemoveDirectoryW(dir); free(dir); if (!ok) return IOError(name); return Status::OK(); } #else virtual Status DeleteDir(const std::string& dirname) { WCHAR *dir = ToWcharPermissive(dirname.c_str()); if (dir == NULL) return Status::InvalidArgument("Invalid file name"); SHFILEOPSTRUCTW fileop = { 0 }; fileop.wFunc = FO_DELETE; fileop.pFrom = (const WCHAR*)dir; fileop.fFlags = FOF_NO_UI; int res = SHFileOperationW(&fileop); free((void*)dir); if (res == 0 && fileop.fAnyOperationsAborted == FALSE) return Status::OK(); return IOError("DeleteDir " + dirname); } #endif virtual Status GetFileSize(const std::string& fname, uint64_t* size) { WCHAR *file_name = ToWcharPermissive(fname.c_str()); if (file_name == NULL) return Status::InvalidArgument("Invalid file name"); HANDLE h = CreateFileW(file_name, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); free(file_name); if (h == INVALID_HANDLE_VALUE) return IOError("GetFileSize " + fname); // Not using GetFileAttributesEx() as it doesn't interact well with symlinks, etc. LARGE_INTEGER lsize; BOOL ok = GetFileSizeEx(h, &lsize); CloseHandle(h); if (!ok) return IOError("GetFileSize " + fname); *size = static_cast(lsize.QuadPart); return Status::OK(); } virtual Status RenameFile(const std::string& src, const std::string& target) { WCHAR *src2 = ToWcharPermissive(src.c_str()); WCHAR *target2 = ToWcharPermissive(target.c_str()); if ((src2 == NULL) || (target2 == NULL)) { free(src2); free(target2); return Status::InvalidArgument("Invalid file name"); } BOOL ok = MoveFileExW(src2, target2, MOVEFILE_REPLACE_EXISTING); free(src2); free(target2); if (!ok) return IOError("RenameFile " + src + " " + target); return Status::OK(); } virtual Status LockFile(const std::string& fname, FileLock** lock) { *lock = NULL; WCHAR *file_name = ToWcharPermissive(fname.c_str()); if (file_name == NULL) { return Status::InvalidArgument("Invalid file name"); } HANDLE h = CreateFileW(file_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); free((void*)file_name); if (h == INVALID_HANDLE_VALUE) { return IOError("LockFile " + fname); } *lock = new WinFileLock(fname, h); return Status::OK(); } virtual Status UnlockFile(FileLock* lock) { Status s; WinFileLock* my_lock = reinterpret_cast(lock); if (!my_lock->Close()) { s = Status::IOError(my_lock->fname_, "Could not close lock file."); } delete my_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) { WCHAR buf[MAX_PATH]; DWORD res = GetTempPathW(MAX_PATH, buf); if (0 == res) { return IOError("Can't get test directory"); } char *s = ToUtf8(buf); if (!s) { return IOError("Can't get test directory"); } *result = std::string(s); free(s); return Status::OK(); } virtual Status NewLogger(const std::string& fname, Logger** result) { *result = NULL; FILE* f = fopen(fname.c_str(), "wt"); if (f == NULL) return Status::IOError(fname, strerror(errno)); *result = new WinLogger(f); return Status::OK(); } virtual uint64_t NowMicros() { LARGE_INTEGER count; QueryPerformanceCounter(&count); return count.QuadPart * 1000000i64 / freq_.QuadPart; } virtual void SleepForMicroseconds(int micros) { // round up to the next millisecond Sleep((micros + 999) / 1000); } private: LARGE_INTEGER freq_; // BGThread() is the body of the background thread void BGThread(); static unsigned __stdcall BGThreadWrapper(void* arg) { (reinterpret_cast(arg))->BGThread(); _endthreadex(0); return 0; } leveldb::port::Mutex mu_; leveldb::port::CondVar bgsignal_; HANDLE bgthread_; // Entry per Schedule() call struct BGItem { void* arg; void (*function)(void*); }; typedef std::deque BGQueue; BGQueue queue_; }; WinEnv::WinEnv() : bgthread_(NULL), bgsignal_(&mu_) { QueryPerformanceFrequency(&freq_); } void WinEnv::Schedule(void (*function)(void*), void* arg) { mu_.Lock(); // Start background thread if necessary if (NULL == bgthread_) { bgthread_ = (HANDLE)_beginthreadex(NULL, 0, &WinEnv::BGThreadWrapper, this, 0, NULL); } // Add to priority queue queue_.push_back(BGItem()); queue_.back().function = function; queue_.back().arg = arg; mu_.Unlock(); bgsignal_.Signal(); } void WinEnv::BGThread() { while (true) { // Wait until there is an item that is ready to run mu_.Lock(); while (queue_.empty()) { bgsignal_.Wait(); } void (*function)(void*) = queue_.front().function; void* arg = queue_.front().arg; queue_.pop_front(); mu_.Unlock(); (*function)(arg); } // TODO: CloseHandle(bgthread_) ?? } namespace { struct StartThreadState { void (*user_function)(void*); void* arg; HANDLE threadHandle; }; } static unsigned __stdcall StartThreadWrapper(void* arg) { StartThreadState* state = reinterpret_cast(arg); state->user_function(state->arg); _endthreadex(0); CloseHandle(state->threadHandle); delete state; return 0; } void WinEnv::StartThread(void (*function)(void* arg), void* arg) { StartThreadState* state = new StartThreadState; state->user_function = function; state->arg = arg; state->threadHandle = (HANDLE)_beginthreadex(NULL, 0, &StartThreadWrapper, state, 0, NULL); } } static Env* default_env; static void InitDefaultEnv() { default_env = new WinEnv(); } static leveldb::port::Mutex default_env_mutex; Env* Env::Default() { default_env_mutex.Lock(); if (NULL == default_env) InitDefaultEnv(); default_env_mutex.Unlock(); return default_env; } }