mirror of
https://github.com/Amulet-Team/leveldb-mcpe.git
synced 2024-11-21 20:06:23 +00:00
9994871cb3
Fix UnlockFileEx call. overlapped must be defined when calling UnlockFileEx or an access violation occurs. Call LockFileEx in exclusive and fail immediately mode so that the file cannot be read by other processes and so it doesn't block if a lock is already held.
621 lines
17 KiB
C++
621 lines
17 KiB
C++
// 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 <windows.h>
|
|
#include "leveldb/env.h"
|
|
#include "leveldb/slice.h"
|
|
|
|
#include "util/win_logger.h"
|
|
#include "port/port.h"
|
|
#include "util/logging.h"
|
|
|
|
|
|
#include <deque>
|
|
#include <fstream>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
#include <chrono>
|
|
#include <memory>
|
|
#include <condition_variable>
|
|
#include <thread>
|
|
#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<uint32_t>(::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, 3, 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)
|
|
{
|
|
OVERLAPPED overlapped = { };
|
|
if (!::UnlockFileEx(_file, 0, _fileSizeLow, _fileSizeHigh, &overlapped))
|
|
{
|
|
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<std::string>* 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<std::chrono::microseconds>(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<WinRTEnv*>(arg)->BGThread();
|
|
}
|
|
|
|
std::mutex mu_;
|
|
std::condition_variable bgsignal_;
|
|
std::unique_ptr<std::thread> bgthread_;
|
|
|
|
// Entry per Schedule() call
|
|
struct BGItem { void* arg; void(*function)(void*); };
|
|
typedef std::deque<BGItem> BGQueue;
|
|
BGQueue queue_;
|
|
};
|
|
|
|
WinRTEnv::WinRTEnv() {}
|
|
|
|
void WinRTEnv::Schedule(void(*function)(void*), void* arg) {
|
|
std::unique_lock<std::mutex> 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<std::mutex> 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
|