mirror of
https://github.com/Amulet-Team/leveldb-mcpe.git
synced 2026-07-02 07:12:31 +00:00
618 lines
16 KiB
C++
618 lines
16 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, 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<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
|