From e3cc89846e4fd60221c0032e3aa954d465d4c42a Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 9 Oct 2019 16:01:33 +0300 Subject: [PATCH] wallet: make sure there's enough disk space on load, store and wallet generation (simplewallet + GUI) #57 --- src/wallet/wallet2.cpp | 89 +++++++++++++++++++++++++++++++++++++----- src/wallet/wallet2.h | 35 +++++++++-------- 2 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5095a56f..1352b1f1 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -28,6 +28,8 @@ using namespace epee; #include "version.h" using namespace currency; +#define MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES (100*1024*1024) // 100 MB + #undef LOG_DEFAULT_CHANNEL #define LOG_DEFAULT_CHANNEL "wallet" ENABLE_CHANNEL_BY_DEFAULT("wallet") @@ -1965,6 +1967,9 @@ void wallet2::generate(const std::wstring& path, const std::string& pass) WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(validate_password(pass), "new wallet generation failed: password contains forbidden characters") clear(); prepare_file_names(path); + + check_for_free_space_and_throw_if_it_lacks(m_wallet_file); + m_password = pass; m_account.generate(); init_log_prefix(); @@ -2000,6 +2005,9 @@ void wallet2::load(const std::wstring& wallet_, const std::string& password) { clear(); prepare_file_names(wallet_); + + check_for_free_space_and_throw_if_it_lacks(m_wallet_file); + m_password = password; std::string keys_buff; @@ -2058,35 +2066,55 @@ void wallet2::store(const std::wstring& path_to_save, const std::string& passwor { LOG_PRINT_L0("(before storing: pending_key_images: " << m_pending_key_images.size() << ", pki file elements: " << m_pending_key_images_file_container.size() << ", tx_keys: " << m_tx_keys.size() << ")"); + check_for_free_space_and_throw_if_it_lacks(path_to_save); + + std::string ascii_path_to_save = epee::string_encoding::convert_to_ansii(path_to_save); + //prepare data std::string keys_buff; bool r = store_keys(keys_buff, password); - CHECK_AND_ASSERT_THROW_MES(r, "failed to store_keys for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); - - wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(r, "failed to store_keys for wallet " << ascii_path_to_save); //store data - + wallet_file_binary_header wbh = AUTO_VAL_INIT(wbh); wbh.m_signature = WALLET_FILE_SIGNATURE; wbh.m_cb_keys = keys_buff.size(); //@#@ change it to proper wbh.m_cb_body = 1000; - std::string header_buff((const char*)&wbh, sizeof(wbh)); + uint64_t ts = m_core_runtime_config.get_core_time(); + + // save to tmp file, then rename + boost::filesystem::path tmp_file_path = boost::filesystem::path(path_to_save); + tmp_file_path += L".newtmp_" + std::to_wstring(ts); - //std::ofstream data_file; boost::filesystem::ofstream data_file; - data_file.open(path_to_save, std::ios_base::binary | std::ios_base::out | std::ios::trunc); - CHECK_AND_ASSERT_THROW_MES(!data_file.fail(), "failed to open binary wallet file for saving: " << epee::string_encoding::convert_to_ansii(m_wallet_file)); + data_file.open(tmp_file_path, std::ios_base::binary | std::ios_base::out | std::ios::trunc); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(!data_file.fail(), "failed to open binary wallet file for saving: " << tmp_file_path.string()); data_file << header_buff << keys_buff; - WLT_LOG_L0("Storing to file..."); + + WLT_LOG_L0("Storing to " << tmp_file_path.string() << " ..."); r = tools::portble_serialize_obj_to_stream(*this, data_file); - CHECK_AND_ASSERT_THROW_MES(r, "failed to portble_serialize_obj_to_stream for wallet " << epee::string_encoding::convert_to_ansii(m_wallet_file)); + if (!r) + { + boost::filesystem::remove(tmp_file_path); // remove tmp file if smth went wrong + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "portble_serialize_obj_to_stream failed for wallet " << tmp_file_path.string()); + } data_file.flush(); data_file.close(); + + // for the sake of safety perform a double-renaming: wallet file -> old tmp, new tmp -> wallet file, remove old tmp + + boost::filesystem::path tmp_old_file_path = boost::filesystem::path(path_to_save); + tmp_old_file_path += L".oldtmp_" + std::to_wstring(ts); + + if (boost::filesystem::is_regular_file(path_to_save)) + boost::filesystem::rename(path_to_save, tmp_old_file_path); + boost::filesystem::rename(tmp_file_path, path_to_save); + boost::filesystem::remove(tmp_old_file_path); } //---------------------------------------------------------------------------------------------------- void wallet2::store_watch_only(const std::wstring& path_to_save, const std::string& password) const @@ -2134,6 +2162,47 @@ void wallet2::store_watch_only(const std::wstring& path_to_save, const std::stri wo.store(path_to_save, password); } //---------------------------------------------------------------------------------------------------- +void wallet2::check_for_free_space_and_throw_if_it_lacks(const std::wstring& wallet_filename, uint64_t exact_size_needed_if_known /* = UINT64_MAX */) +{ + namespace fs = boost::filesystem; + + try + { + fs::path wallet_file_path(wallet_filename); + fs::path base_path = wallet_file_path.parent_path(); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(fs::is_directory(base_path), "directory does not exist: " << base_path.string()); + + uint64_t min_free_size = exact_size_needed_if_known; + if (min_free_size == UINT64_MAX) + { + // if exact size needed is unknown -- determine it as + // twice the original wallet file size or MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES, which one is bigger + min_free_size = MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES; + if (fs::is_regular_file(wallet_file_path)) + min_free_size = std::max(min_free_size, 2 * fs::file_size(wallet_file_path)); + } + else + { + min_free_size += 1024 * 1024 * 10; // add a little for FS overhead and so + } + + fs::space_info si = fs::space(base_path); + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(si.available > min_free_size, "free space at " << base_path.string() << " is too low: " << si.available << ", required minimum is: " << min_free_size); + } + catch (tools::error::wallet_common_error&) + { + throw; + } + catch (std::exception& e) + { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "failed to determine free space: " << e.what()); + } + catch (...) + { + WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(false, "failed to determine free space: unknown exception"); + } +} +//---------------------------------------------------------------------------------------------------- uint64_t wallet2::unlocked_balance() const { uint64_t stub = 0; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 7d5cf1ba..e1fd1d63 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -47,22 +47,22 @@ ENABLE_CHANNEL_BY_DEFAULT("wallet"); // wallet-specific logging functions -#define WLT_LOG_L0(msg) LOG_PRINT_L0("[W:" << m_log_prefix << "]" << msg) -#define WLT_LOG_L1(msg) LOG_PRINT_L1("[W:" << m_log_prefix << "]" << msg) -#define WLT_LOG_L2(msg) LOG_PRINT_L2("[W:" << m_log_prefix << "]" << msg) -#define WLT_LOG_L3(msg) LOG_PRINT_L3("[W:" << m_log_prefix << "]" << msg) -#define WLT_LOG_L4(msg) LOG_PRINT_L4("[W:" << m_log_prefix << "]" << msg) -#define WLT_LOG_ERROR(msg) LOG_ERROR("[W:" << m_log_prefix << "]" << msg) -#define WLT_LOG_BLUE(msg, log_level) LOG_PRINT_BLUE("[W:" << m_log_prefix << "]" << msg, log_level) -#define WLT_LOG_CYAN(msg, log_level) LOG_PRINT_CYAN("[W:" << m_log_prefix << "]" << msg, log_level) -#define WLT_LOG_GREEN(msg, log_level) LOG_PRINT_GREEN("[W:" << m_log_prefix << "]" << msg, log_level) -#define WLT_LOG_MAGENTA(msg, log_level) LOG_PRINT_MAGENTA("[W:" << m_log_prefix << "]" << msg, log_level) -#define WLT_LOG_RED(msg, log_level) LOG_PRINT_RED("[W:" << m_log_prefix << "]" << msg, log_level) -#define WLT_LOG_YELLOW(msg, log_level) LOG_PRINT_YELLOW("[W:" << m_log_prefix << "]" << msg, log_level) -#define WLT_CHECK_AND_ASSERT_MES(expr, ret, msg) CHECK_AND_ASSERT_MES(expr, ret, "[W:" << m_log_prefix << "]" << msg) -#define WLT_CHECK_AND_ASSERT_MES_NO_RET(expr, msg) CHECK_AND_ASSERT_MES_NO_RET(expr, "[W:" << m_log_prefix << "]" << msg) -#define WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, msg) THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, "[W:" << m_log_prefix << "]" << msg) -#define WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(cond, msg) THROW_IF_FALSE_WALLET_CMN_ERR_EX(cond, "[W:" << m_log_prefix << "]" << msg) +#define WLT_LOG_L0(msg) LOG_PRINT_L0("[W:" << m_log_prefix << "] " << msg) +#define WLT_LOG_L1(msg) LOG_PRINT_L1("[W:" << m_log_prefix << "] " << msg) +#define WLT_LOG_L2(msg) LOG_PRINT_L2("[W:" << m_log_prefix << "] " << msg) +#define WLT_LOG_L3(msg) LOG_PRINT_L3("[W:" << m_log_prefix << "] " << msg) +#define WLT_LOG_L4(msg) LOG_PRINT_L4("[W:" << m_log_prefix << "] " << msg) +#define WLT_LOG_ERROR(msg) LOG_ERROR("[W:" << m_log_prefix << "] " << msg) +#define WLT_LOG_BLUE(msg, log_level) LOG_PRINT_BLUE("[W:" << m_log_prefix << "] " << msg, log_level) +#define WLT_LOG_CYAN(msg, log_level) LOG_PRINT_CYAN("[W:" << m_log_prefix << "] " << msg, log_level) +#define WLT_LOG_GREEN(msg, log_level) LOG_PRINT_GREEN("[W:" << m_log_prefix << "] " << msg, log_level) +#define WLT_LOG_MAGENTA(msg, log_level) LOG_PRINT_MAGENTA("[W:" << m_log_prefix << "] " << msg, log_level) +#define WLT_LOG_RED(msg, log_level) LOG_PRINT_RED("[W:" << m_log_prefix << "] " << msg, log_level) +#define WLT_LOG_YELLOW(msg, log_level) LOG_PRINT_YELLOW("[W:" << m_log_prefix << "] " << msg, log_level) +#define WLT_CHECK_AND_ASSERT_MES(expr, ret, msg) CHECK_AND_ASSERT_MES(expr, ret, "[W:" << m_log_prefix << "] " << msg) +#define WLT_CHECK_AND_ASSERT_MES_NO_RET(expr, msg) CHECK_AND_ASSERT_MES_NO_RET(expr, "[W:" << m_log_prefix << "] " << msg) +#define WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, msg) THROW_IF_FALSE_WALLET_INT_ERR_EX(cond, "[W:" << m_log_prefix << "] " << msg) +#define WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(cond, msg) THROW_IF_FALSE_WALLET_CMN_ERR_EX(cond, "[W:" << m_log_prefix << "] " << msg) class test_generator; @@ -729,6 +729,7 @@ namespace tools std::string get_log_prefix() const { return m_log_prefix; } static uint64_t get_max_unlock_time_from_receive_indices(const currency::transaction& tx, const money_transfer2_details& td); + private: void add_transfers_to_expiration_list(const std::vector& selected_transfers, uint64_t expiration, uint64_t change_amount, const crypto::hash& related_tx_id); void remove_transfer_from_expiration_list(uint64_t transfer_index); @@ -840,7 +841,7 @@ private: void exception_handler(); void exception_handler() const; uint64_t get_minimum_allowed_fee_for_contract(const crypto::hash& ms_id); - + void check_for_free_space_and_throw_if_it_lacks(const std::wstring& path, uint64_t exact_size_needed_if_known = UINT64_MAX);