Merge pull request #3994

1d17647 epee.string_tools: add conversion between UTF-8 and UTF-16 (stoffu)
59de6f8 util: add file_locker class (stoffu)
3d623a8 wallet: prevent the same wallet file from being opened by multiple processes (stoffu)
This commit is contained in:
luigi1111 2018-06-27 20:17:25 -05:00
commit 4c6de54ee2
No known key found for this signature in database
GPG Key ID: F4ACA0183641E010
7 changed files with 154 additions and 26 deletions

View File

@ -33,6 +33,7 @@
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#ifdef WIN32 #ifdef WIN32
#include <windows.h> #include <windows.h>
#include "string_tools.h"
#endif #endif
// On Windows there is a problem with non-ASCII characters in path and file names // On Windows there is a problem with non-ASCII characters in path and file names
@ -72,11 +73,9 @@ namespace file_io_utils
bool save_string_to_file(const std::string& path_to_file, const std::string& str) bool save_string_to_file(const std::string& path_to_file, const std::string& str)
{ {
#ifdef WIN32 #ifdef WIN32
WCHAR wide_path[1000]; std::wstring wide_path;
int chars = MultiByteToWideChar(CP_UTF8, 0, path_to_file.c_str(), path_to_file.size() + 1, wide_path, 1000); try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; }
if (chars == 0) HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
return false;
HANDLE file_handle = CreateFileW(wide_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) if (file_handle == INVALID_HANDLE_VALUE)
return false; return false;
DWORD bytes_written; DWORD bytes_written;
@ -131,11 +130,9 @@ namespace file_io_utils
bool load_file_to_string(const std::string& path_to_file, std::string& target_str, size_t max_size = 1000000000) bool load_file_to_string(const std::string& path_to_file, std::string& target_str, size_t max_size = 1000000000)
{ {
#ifdef WIN32 #ifdef WIN32
WCHAR wide_path[1000]; std::wstring wide_path;
int chars = MultiByteToWideChar(CP_UTF8, 0, path_to_file.c_str(), path_to_file.size() + 1, wide_path, 1000); try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; }
if (chars == 0) HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
return false;
HANDLE file_handle = CreateFileW(wide_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) if (file_handle == INVALID_HANDLE_VALUE)
return false; return false;
DWORD file_size = GetFileSize(file_handle, NULL); DWORD file_size = GetFileSize(file_handle, NULL);
@ -202,11 +199,9 @@ namespace file_io_utils
bool get_file_size(const std::string& path_to_file, uint64_t &size) bool get_file_size(const std::string& path_to_file, uint64_t &size)
{ {
#ifdef WIN32 #ifdef WIN32
WCHAR wide_path[1000]; std::wstring wide_path;
int chars = MultiByteToWideChar(CP_UTF8, 0, path_to_file.c_str(), path_to_file.size() + 1, wide_path, 1000); try { wide_path = string_tools::utf8_to_utf16(path_to_file); } catch (...) { return false; }
if (chars == 0) HANDLE file_handle = CreateFileW(wide_path.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
return false;
HANDLE file_handle = CreateFileW(wide_path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (file_handle == INVALID_HANDLE_VALUE) if (file_handle == INVALID_HANDLE_VALUE)
return false; return false;
LARGE_INTEGER file_size; LARGE_INTEGER file_size;

View File

@ -381,6 +381,41 @@ POP_WARNINGS
res = str.substr(0, pos); res = str.substr(0, pos);
return res; return res;
} }
//----------------------------------------------------------------------------
#ifdef _WIN32
inline std::wstring utf8_to_utf16(const std::string& str)
{
if (str.empty())
return {};
int wstr_size = MultiByteToWideChar(CP_UTF8, 0, &str[0], str.size(), NULL, 0);
if (wstr_size == 0)
{
throw std::runtime_error(std::error_code(GetLastError(), std::system_category()).message());
}
std::wstring wstr(wstr_size, wchar_t{});
if (!MultiByteToWideChar(CP_UTF8, 0, &str[0], str.size(), &wstr[0], wstr_size))
{
throw std::runtime_error(std::error_code(GetLastError(), std::system_category()).message());
}
return wstr;
}
inline std::string utf16_to_utf8(const std::wstring& wstr)
{
if (wstr.empty())
return {};
int str_size = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], wstr.size(), NULL, 0, NULL, NULL);
if (str_size == 0)
{
throw std::runtime_error(std::error_code(GetLastError(), std::system_category()).message());
}
std::string str(str_size, char{});
if (!WideCharToMultiByte(CP_UTF8, 0, &wstr[0], wstr.size(), &str[0], str_size, NULL, NULL))
{
throw std::runtime_error(std::error_code(GetLastError(), std::system_category()).message());
}
return str;
}
#endif
} }
} }
#endif //_STRING_TOOLS_H_ #endif //_STRING_TOOLS_H_

View File

@ -195,6 +195,73 @@ namespace tools
catch (...) {} catch (...) {}
} }
file_locker::file_locker(const std::string &filename)
{
#ifdef WIN32
m_fd = INVALID_HANDLE_VALUE;
std::wstring filename_wide;
try
{
filename_wide = string_tools::utf8_to_utf16(filename);
}
catch (const std::exception &e)
{
MERROR("Failed to convert path \"" << filename << "\" to UTF-16: " << e.what());
return;
}
m_fd = CreateFileW(filename_wide.c_str(), GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (m_fd != INVALID_HANDLE_VALUE)
{
OVERLAPPED ov;
memset(&ov, 0, sizeof(ov));
if (!LockFileEx(m_fd, LOCKFILE_FAIL_IMMEDIATELY | LOCKFILE_EXCLUSIVE_LOCK, 0, 1, 0, &ov))
{
MERROR("Failed to lock " << filename << ": " << std::error_code(GetLastError(), std::system_category()));
CloseHandle(m_fd);
m_fd = INVALID_HANDLE_VALUE;
}
}
else
{
MERROR("Failed to open " << filename << ": " << std::error_code(GetLastError(), std::system_category()));
}
#else
m_fd = open(filename, O_RDONLY | O_CREAT, 0666);
if (m_fd != -1)
{
if (flock(m_fd, LOCK_EX | LOCK_NB) == -1)
{
MERROR("Failed to lock " << filename << ": " << std::strerr(errno));
close(m_fd);
m_fd = -1;
}
}
else
{
MERROR("Failed to open " << filename << ": " << std::strerr(errno));
}
#endif
}
file_locker::~file_locker()
{
if (locked())
{
#ifdef WIN32
CloseHandle(m_fd);
#else
close(m_fd);
#endif
}
}
bool file_locker::locked() const
{
#ifdef WIN32
return m_fd != INVALID_HANDLE_VALUE;
#else
return m_fd != -1;
#endif
}
#ifdef WIN32 #ifdef WIN32
std::string get_windows_version_display_string() std::string get_windows_version_display_string()
{ {
@ -451,10 +518,15 @@ std::string get_nix_version_display_string()
if (SHGetSpecialFolderPathW(NULL, psz_path, nfolder, iscreate)) if (SHGetSpecialFolderPathW(NULL, psz_path, nfolder, iscreate))
{ {
int size_needed = WideCharToMultiByte(CP_UTF8, 0, psz_path, wcslen(psz_path), NULL, 0, NULL, NULL); try
std::string folder_name(size_needed, 0); {
WideCharToMultiByte(CP_UTF8, 0, psz_path, wcslen(psz_path), &folder_name[0], size_needed, NULL, NULL); return string_tools::utf16_to_utf8(psz_path);
return folder_name; }
catch (const std::exception &e)
{
MERROR("utf16_to_utf8 failed: " << e.what());
return "";
}
} }
LOG_ERROR("SHGetSpecialFolderPathW() failed, could not obtain requested path."); LOG_ERROR("SHGetSpecialFolderPathW() failed, could not obtain requested path.");
@ -515,18 +587,20 @@ std::string get_nix_version_display_string()
int code; int code;
#if defined(WIN32) #if defined(WIN32)
// Maximizing chances for success // Maximizing chances for success
WCHAR wide_replacement_name[1000]; std::wstring wide_replacement_name;
MultiByteToWideChar(CP_UTF8, 0, replacement_name.c_str(), replacement_name.size() + 1, wide_replacement_name, 1000); try { wide_replacement_name = string_tools::utf8_to_utf16(replacement_name); }
WCHAR wide_replaced_name[1000]; catch (...) { return std::error_code(GetLastError(), std::system_category()); }
MultiByteToWideChar(CP_UTF8, 0, replaced_name.c_str(), replaced_name.size() + 1, wide_replaced_name, 1000); std::wstring wide_replaced_name;
try { wide_replaced_name = string_tools::utf8_to_utf16(replaced_name); }
catch (...) { return std::error_code(GetLastError(), std::system_category()); }
DWORD attributes = ::GetFileAttributesW(wide_replaced_name); DWORD attributes = ::GetFileAttributesW(wide_replaced_name.c_str());
if (INVALID_FILE_ATTRIBUTES != attributes) if (INVALID_FILE_ATTRIBUTES != attributes)
{ {
::SetFileAttributesW(wide_replaced_name, attributes & (~FILE_ATTRIBUTE_READONLY)); ::SetFileAttributesW(wide_replaced_name.c_str(), attributes & (~FILE_ATTRIBUTE_READONLY));
} }
bool ok = 0 != ::MoveFileExW(wide_replacement_name, wide_replaced_name, MOVEFILE_REPLACE_EXISTING); bool ok = 0 != ::MoveFileExW(wide_replacement_name.c_str(), wide_replaced_name.c_str(), MOVEFILE_REPLACE_EXISTING);
code = ok ? 0 : static_cast<int>(::GetLastError()); code = ok ? 0 : static_cast<int>(::GetLastError());
#else #else
bool ok = 0 == std::rename(replacement_name.c_str(), replaced_name.c_str()); bool ok = 0 == std::rename(replacement_name.c_str(), replaced_name.c_str());

View File

@ -91,6 +91,20 @@ namespace tools
const std::string& filename() const noexcept { return m_filename; } const std::string& filename() const noexcept { return m_filename; }
}; };
class file_locker
{
public:
file_locker(const std::string &filename);
~file_locker();
bool locked() const;
private:
#ifdef WIN32
HANDLE m_fd;
#else
int m_fd;
#endif
};
/*! \brief Returns the default data directory. /*! \brief Returns the default data directory.
* *
* \details Windows < Vista: C:\\Documents and Settings\\Username\\Application Data\\CRYPTONOTE_NAME * \details Windows < Vista: C:\\Documents and Settings\\Username\\Application Data\\CRYPTONOTE_NAME

View File

@ -721,6 +721,7 @@ bool WalletImpl::close(bool store)
LOG_PRINT_L1("Calling wallet::stop..."); LOG_PRINT_L1("Calling wallet::stop...");
m_wallet->stop(); m_wallet->stop();
LOG_PRINT_L1("wallet::stop done"); LOG_PRINT_L1("wallet::stop done");
m_wallet->deinit();
result = true; result = true;
clearStatus(); clearStatus();
} catch (const std::exception &e) { } catch (const std::exception &e) {

View File

@ -2636,6 +2636,7 @@ void wallet2::detach_blockchain(uint64_t height)
bool wallet2::deinit() bool wallet2::deinit()
{ {
m_is_initialized=false; m_is_initialized=false;
m_keys_file_locker.reset();
return true; return true;
} }
//---------------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------------
@ -2802,10 +2803,12 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]); crypto::chacha20(account_data.data(), account_data.size(), key, keys_file_data.iv, &cipher[0]);
keys_file_data.account_data = cipher; keys_file_data.account_data = cipher;
m_keys_file_locker.reset();
std::string buf; std::string buf;
r = ::serialization::dump_binary(keys_file_data, buf); r = ::serialization::dump_binary(keys_file_data, buf);
r = r && epee::file_io_utils::save_string_to_file(keys_file_name, buf); //and never touch wallet_keys_file again, only read r = r && epee::file_io_utils::save_string_to_file(keys_file_name, buf); //and never touch wallet_keys_file again, only read
CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << keys_file_name); CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << keys_file_name);
m_keys_file_locker.reset(new tools::file_locker(m_keys_file));
return true; return true;
} }
@ -3935,12 +3938,17 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass
boost::system::error_code e; boost::system::error_code e;
bool exists = boost::filesystem::exists(m_keys_file, e); bool exists = boost::filesystem::exists(m_keys_file, e);
THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file); THROW_WALLET_EXCEPTION_IF(e || !exists, error::file_not_found, m_keys_file);
m_keys_file_locker.reset(new tools::file_locker(m_keys_file));
THROW_WALLET_EXCEPTION_IF(!m_keys_file_locker->locked(), error::wallet_internal_error, "internal error: \"" + m_keys_file + "\" is opened by another wallet program");
// this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded).
m_keys_file_locker.reset();
if (!load_keys(m_keys_file, password)) if (!load_keys(m_keys_file, password))
{ {
THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file); THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, m_keys_file);
} }
LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype)); LOG_PRINT_L0("Loaded wallet keys file, with public address: " << m_account.get_public_address_str(m_nettype));
m_keys_file_locker.reset(new tools::file_locker(m_keys_file));
//keys loaded ok! //keys loaded ok!
//try to load wallet file. but even if we failed, it is not big problem //try to load wallet file. but even if we failed, it is not big problem

View File

@ -1308,6 +1308,7 @@ namespace tools
boost::optional<crypto::chacha_key> m_ringdb_key; boost::optional<crypto::chacha_key> m_ringdb_key;
uint64_t m_last_block_reward; uint64_t m_last_block_reward;
std::unique_ptr<tools::file_locker> m_keys_file_locker;
}; };
} }
BOOST_CLASS_VERSION(tools::wallet2, 25) BOOST_CLASS_VERSION(tools::wallet2, 25)