From 605bef55c61d1aa7c61aef6ce590fd328f028abf Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 4 Oct 2021 13:11:56 +0200 Subject: [PATCH 0001/1271] added idle wallet store in server mode (cherry picked from commit 7dc93fe1442ab1a2b190bb50278487df6c357dbb) --- src/wallet/wallet_rpc_server.cpp | 13 +++++++++++++ src/wallet/wallet_rpc_server.h | 1 + 2 files changed, 14 insertions(+) diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index 9152c66a..e762ebbf 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -91,6 +91,18 @@ namespace tools LOG_PRINT_L2("wallet RPC idle: trying to do PoS iteration..."); m_wallet.try_mint_pos(miner_address); } + + //auto-store wallet in server mode, let's do it every 24-hour + if (m_wallet.get_top_block_height() < m_last_wallet_store_height) + { + LOG_ERROR("Unexpected m_last_wallet_store_height = " << m_last_wallet_store_height << " or " << m_wallet.get_top_block_height()); + } + else if (m_wallet.get_top_block_height() - m_last_wallet_store_height > CURRENCY_BLOCKS_PER_DAY) + { + //store wallet + m_wallet.store(); + m_last_wallet_store_height = m_wallet.get_top_block_height(); + } } catch (error::no_connection_to_daemon&) { @@ -132,6 +144,7 @@ namespace tools //------------------------------------------------------------------------------------------------------------------------------ bool wallet_rpc_server::init(const boost::program_options::variables_map& vm) { + m_last_wallet_store_height = m_wallet.get_top_block_height(); m_net_server.set_threads_prefix("RPC"); bool r = handle_command_line(vm); CHECK_AND_ASSERT_MES(r, false, "Failed to process command line in core_rpc_server"); diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 3c75296e..b99420d4 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -118,6 +118,7 @@ namespace tools std::string m_bind_ip; bool m_do_mint; bool m_deaf; + uint64_t m_last_wallet_store_height; }; } // namespace tools From d3476ba57129bb7ef612cc21f5a1e88819598178 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 11 Nov 2021 15:16:27 +0100 Subject: [PATCH 0002/1271] mdbx moved to latest commit --- contrib/db/libmdbx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/db/libmdbx b/contrib/db/libmdbx index b7ed6754..0e2ca3eb 160000 --- a/contrib/db/libmdbx +++ b/contrib/db/libmdbx @@ -1 +1 @@ -Subproject commit b7ed67543fefb0878dba1c70dea2a81201041314 +Subproject commit 0e2ca3eb515d6939513a29321aaf13678f682e8b From 8d8ec50ae79af21eed17a832c41abee0d5986838 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 11 Nov 2021 17:04:41 +0100 Subject: [PATCH 0003/1271] fixed mdbx linking --- contrib/CMakeLists.txt | 2 +- src/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 779876a1..3cf5264d 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -15,7 +15,7 @@ add_subdirectory(miniupnp/miniupnpc) set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") -set_property(TARGET mdbx PROPERTY FOLDER "contrib") +set_property(TARGET mdbx-static PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") set_property(TARGET upnpc-static mdbx_chk mdbx_copy mdbx_dump mdbx_load mdbx_stat PROPERTY FOLDER "unused") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 807d2a16..5e71c397 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,7 +155,7 @@ add_dependencies(stratum version ${PCH_LIB_NAME}) ENABLE_SHARED_PCH(stratum STRATUM) -target_link_libraries(currency_core lmdb mdbx) +target_link_libraries(currency_core lmdb mdbx-static) add_executable(daemon ${DAEMON} ${P2P} ${CURRENCY_PROTOCOL}) add_dependencies(daemon version) From f53b696798db4d81c4e6d0f0893b53bdb5f67dee Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 16 Nov 2021 14:58:19 +0100 Subject: [PATCH 0004/1271] fixed mdbx issues with with new version --- src/common/db_backend_mdbx.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/common/db_backend_mdbx.cpp b/src/common/db_backend_mdbx.cpp index 09670a89..1a048c9c 100644 --- a/src/common/db_backend_mdbx.cpp +++ b/src/common/db_backend_mdbx.cpp @@ -143,9 +143,10 @@ namespace tools else { int res = 0; - unsigned int flags = 0; + MDBX_txn_flags_t flags = MDBX_txn_flags_t(); + unsigned int flags_ = 0; if (read_only) - flags += MDBX_RDONLY; + flags = MDBX_TXN_RDONLY; //don't use parent tx in write transactions if parent tx was read-only (restriction in mdbx) //see "Nested transactions: Max 1 child, write txns only, no writemap" @@ -340,7 +341,8 @@ namespace tools data[0].iov_base = (void*)v; data[0].iov_len = vs; - res = mdbx_put(get_current_tx(), static_cast(h), &key, data, 0); + MDBX_put_flags_t flags = MDBX_put_flags_t(); + res = mdbx_put(get_current_tx(), static_cast(h), &key, data, flags); CHECK_AND_ASSERT_MESS_MDBX_DB(res, false, "Unable to mdbx_put"); return true; } From 9c1c74cf7d2a9b73cf9691783fec4de4b0aef475 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 16 Nov 2021 15:52:07 +0100 Subject: [PATCH 0005/1271] rollback mdbx --- contrib/CMakeLists.txt | 2 +- contrib/db/libmdbx | 2 +- src/CMakeLists.txt | 2 +- src/common/db_backend_mdbx.cpp | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 3cf5264d..779876a1 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -15,7 +15,7 @@ add_subdirectory(miniupnp/miniupnpc) set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") -set_property(TARGET mdbx-static PROPERTY FOLDER "contrib") +set_property(TARGET mdbx PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") set_property(TARGET upnpc-static mdbx_chk mdbx_copy mdbx_dump mdbx_load mdbx_stat PROPERTY FOLDER "unused") diff --git a/contrib/db/libmdbx b/contrib/db/libmdbx index 0e2ca3eb..b7ed6754 160000 --- a/contrib/db/libmdbx +++ b/contrib/db/libmdbx @@ -1 +1 @@ -Subproject commit 0e2ca3eb515d6939513a29321aaf13678f682e8b +Subproject commit b7ed67543fefb0878dba1c70dea2a81201041314 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5e71c397..807d2a16 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -155,7 +155,7 @@ add_dependencies(stratum version ${PCH_LIB_NAME}) ENABLE_SHARED_PCH(stratum STRATUM) -target_link_libraries(currency_core lmdb mdbx-static) +target_link_libraries(currency_core lmdb mdbx) add_executable(daemon ${DAEMON} ${P2P} ${CURRENCY_PROTOCOL}) add_dependencies(daemon version) diff --git a/src/common/db_backend_mdbx.cpp b/src/common/db_backend_mdbx.cpp index 1a048c9c..3307dec8 100644 --- a/src/common/db_backend_mdbx.cpp +++ b/src/common/db_backend_mdbx.cpp @@ -143,10 +143,10 @@ namespace tools else { int res = 0; - MDBX_txn_flags_t flags = MDBX_txn_flags_t(); - unsigned int flags_ = 0; + //MDBX_txn_flags_t flags = MDBX_txn_flags_t(); + unsigned int flags = 0; if (read_only) - flags = MDBX_TXN_RDONLY; + flags = MDBX_RDONLY;//flags = MDBX_TXN_RDONLY; //don't use parent tx in write transactions if parent tx was read-only (restriction in mdbx) //see "Nested transactions: Max 1 child, write txns only, no writemap" @@ -341,7 +341,8 @@ namespace tools data[0].iov_base = (void*)v; data[0].iov_len = vs; - MDBX_put_flags_t flags = MDBX_put_flags_t(); + //MDBX_put_flags_t flags = MDBX_put_flags_t(); + unsigned flags = 0; res = mdbx_put(get_current_tx(), static_cast(h), &key, data, flags); CHECK_AND_ASSERT_MESS_MDBX_DB(res, false, "Unable to mdbx_put"); return true; From a2664f046f969d9cc59953a945edf1b83161a68f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=87EL=C4=B0K?= Date: Wed, 17 Nov 2021 17:32:00 +0300 Subject: [PATCH 0006/1271] added zano url-scheme handler MacOS, Windows, Linux setups (#304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Barış Çelik --- src/gui/qt-daemon/Info.plist.in | 17 +++++++++++++---- utils/Zano.sh | 3 +++ utils/setup_32.iss | 2 ++ utils/setup_64.iss | 3 +++ 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/gui/qt-daemon/Info.plist.in b/src/gui/qt-daemon/Info.plist.in index 5cff05eb..8b33aed9 100644 --- a/src/gui/qt-daemon/Info.plist.in +++ b/src/gui/qt-daemon/Info.plist.in @@ -45,10 +45,19 @@ NSHumanReadableCopyright - - NSHighResolutionCapable - True - + NSHighResolutionCapable + True + CFBundleURLTypes + + + CFBundleURLName + ZanoApp + CFBundleURLSchemes + + zano + + + diff --git a/utils/Zano.sh b/utils/Zano.sh index d097abbc..6b3c5fb4 100755 --- a/utils/Zano.sh +++ b/utils/Zano.sh @@ -33,9 +33,12 @@ create_desktop_icon() echo Terminal=true | tee -a $target_file_name > /dev/null echo Type=Application | tee -a $target_file_name > /dev/null echo "Categories=Qt;Utility;" | tee -a $target_file_name > /dev/null + echo "MimeType=x-scheme-handler/zano;" | tee -a $target_file_name > /dev/null } create_desktop_icon $out_file_name +xdg-mime default Zano.desktop x-scheme-handler/zano + call_app diff --git a/utils/setup_32.iss b/utils/setup_32.iss index e57a6838..24c18c80 100644 --- a/utils/setup_32.iss +++ b/utils/setup_32.iss @@ -51,6 +51,8 @@ Root: HKCR; Subkey: "ZanoWalletDataKyesFile"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "ZanoWalletDataFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\zano.exe,0" Root: HKCR; Subkey: "ZanoWalletDataKyesFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\zano.exe,0" +Root: HKCR; Subkey: "Zano"; ValueType: string; ValueName: "URL Protocol"; ValueData: "" +Root: HKCR; Subkey: "Zano\shell\open\command"; ValueType: string; ValueName: ""; ValueData: "{app}\zano.exe %1" [Files] diff --git a/utils/setup_64.iss b/utils/setup_64.iss index e4a7e88a..adee27a8 100644 --- a/utils/setup_64.iss +++ b/utils/setup_64.iss @@ -52,6 +52,9 @@ Root: HKCR; Subkey: "ZanoWalletDataKyesFile"; ValueType: string; ValueName: ""; Root: HKCR; Subkey: "ZanoWalletDataFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\Zano.exe,0" Root: HKCR; Subkey: "ZanoWalletDataKyesFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\Zano.exe,0" +Root: HKCR; Subkey: "Zano"; ValueType: string; ValueName: "URL Protocol"; ValueData: "" +Root: HKCR; Subkey: "Zano\shell\open\command"; ValueType: string; ValueName: ""; ValueData: "{app}\Zano.exe %1" + [Files] From bd3cd91379b71f7e0f00968ca3a0d76bbeffa058 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 17 Nov 2021 16:39:52 +0100 Subject: [PATCH 0007/1271] fixed problem with catch-return value --- src/gui/qt-daemon/application/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 4313ca65..7f509188 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -842,7 +842,7 @@ QString MainWindow::export_wallet_history(const QString& param) PREPARE_RESPONSE(view::api_response, ar); ar.error_code = m_backend.export_wallet_history(ewi); return MAKE_RESPONSE(ar); - CATCH_ENTRY2(false); + CATCH_ENTRY2(API_RETURN_CODE_INTERNAL_ERROR); } bool MainWindow::update_wallets_info(const view::wallets_summary_info& wsi) { From b952183623d4a531fcaed6b295e670a081772981 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 18 Nov 2021 20:05:09 +0100 Subject: [PATCH 0008/1271] converted local UI config into JSON --- src/currency_core/currency_config.h | 2 +- src/gui/qt-daemon/application/mainwindow.cpp | 10 +++++----- src/gui/qt-daemon/application/mainwindow.h | 14 ++++++++++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index bda8f652..4dfa5e12 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -211,7 +211,7 @@ #define MINER_CONFIG_FILENAME "miner_conf.json" #define GUI_SECURE_CONFIG_FILENAME "gui_secure_conf.bin" #define GUI_CONFIG_FILENAME "gui_settings.json" -#define GUI_INTERNAL_CONFIG "gui_internal_config.bin" +#define GUI_INTERNAL_CONFIG2 "gui_internal_config.json" diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 7f509188..acdaef1c 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -387,9 +387,9 @@ void MainWindow::changeEvent(QEvent *e) bool MainWindow::store_app_config() { TRY_ENTRY(); - std::string conf_path = m_backend.get_config_folder() + "/" + GUI_INTERNAL_CONFIG; - LOG_PRINT_L0("storing gui internal config from " << conf_path); - CHECK_AND_ASSERT_MES(tools::serialize_obj_to_file(m_config, conf_path), false, "failed to store gui internal config"); + std::string conf_path = m_backend.get_config_folder() + "/" + GUI_INTERNAL_CONFIG2; + LOG_PRINT_L0("storing gui internal config to " << conf_path); + CHECK_AND_ASSERT_MES(epee::serialization::store_t_to_json_file(m_config, conf_path), false, "failed to store gui internal config"); return true; CATCH_ENTRY2(false); } @@ -397,9 +397,9 @@ bool MainWindow::store_app_config() bool MainWindow::load_app_config() { TRY_ENTRY(); - std::string conf_path = m_backend.get_config_folder() + "/" + GUI_INTERNAL_CONFIG; + std::string conf_path = m_backend.get_config_folder() + "/" + GUI_INTERNAL_CONFIG2; LOG_PRINT_L0("loading gui internal config from " << conf_path); - bool r = tools::unserialize_obj_from_file(m_config, conf_path); + bool r = epee::serialization::load_t_from_json_file(m_config, conf_path); LOG_PRINT_L0("gui internal config " << (r ? "loaded ok" : "was not loaded")); return r; CATCH_ENTRY2(false); diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 3c67b509..57fcf129 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -8,6 +8,7 @@ #include #include "wallet/view_iface.h" +#include "serialization/keyvalue_helper_structs.h" #ifndef Q_MOC_RUN #include "wallet/wallets_manager.h" @@ -60,11 +61,20 @@ public: struct app_config { - std::pair m_window_position; - std::pair m_window_size; + + epee::kvserializable_pair m_window_position; + epee::kvserializable_pair m_window_size; bool is_maximazed; bool is_showed; bool disable_notifications; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_window_position) + KV_SERIALIZE(m_window_size) + KV_SERIALIZE(is_maximazed) + KV_SERIALIZE(is_showed) + KV_SERIALIZE(disable_notifications) + END_KV_SERIALIZE_MAP() }; protected slots: From 493855b6b1d8c1047b528bbdb49864e4b862eadc Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 18 Nov 2021 21:11:16 +0100 Subject: [PATCH 0009/1271] added missing file --- .../serialization/keyvalue_helper_structs.h | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 contrib/epee/include/serialization/keyvalue_helper_structs.h diff --git a/contrib/epee/include/serialization/keyvalue_helper_structs.h b/contrib/epee/include/serialization/keyvalue_helper_structs.h new file mode 100644 index 00000000..e0e3f16f --- /dev/null +++ b/contrib/epee/include/serialization/keyvalue_helper_structs.h @@ -0,0 +1,40 @@ +// Copyright (c) 2006-2021, Andrey N. Sabelnikov, www.sabelnikov.net +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Andrey N. Sabelnikov nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// + +#pragma once + +namespace epee +{ + + template + struct kvserializable_pair : public std::pair<_Ty1, _Ty2> + { + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(first) + KV_SERIALIZE(second) + END_KV_SERIALIZE_MAP() + }; +} \ No newline at end of file From 2c464f421827c1729d8bbd1e6df67193ac7d3511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=87EL=C4=B0K?= Date: Mon, 22 Nov 2021 18:32:23 +0300 Subject: [PATCH 0010/1271] added zano url-scheme handler MacOS, Windows, Linux setups (#307) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added zano url-scheme handler MacOS, Windows, Linux setups * pass parameters to executable and print console Co-authored-by: Barış Çelik --- src/gui/qt-daemon/main.cpp | 4 ++++ utils/Zano.sh | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/main.cpp b/src/gui/qt-daemon/main.cpp index bd1dd934..5e19390b 100644 --- a/src/gui/qt-daemon/main.cpp +++ b/src/gui/qt-daemon/main.cpp @@ -26,6 +26,10 @@ int main(int argc, char *argv[]) // See http://crbug.com/436603. // _set_FMA3_enable(0); //#endif // ARCH_CPU_X86_64 && _MSC_VER <= 1800 + + std::cout << argc << std::endl; + std::cout << argv[0] << std::endl; + std::cout << argv[1] << std::endl; #ifdef _MSC_VER #ifdef _WIN64 diff --git a/utils/Zano.sh b/utils/Zano.sh index 6b3c5fb4..33188ef3 100755 --- a/utils/Zano.sh +++ b/utils/Zano.sh @@ -29,7 +29,7 @@ create_desktop_icon() echo GenericName=My Application | tee -a $target_file_name > /dev/null echo Comment=Doing some funny stuff | tee -a $target_file_name > /dev/null echo Icon=$script_dir/html/files/desktop_linux_icon.png | tee -a $target_file_name > /dev/null - echo Exec=$script_dir/Zano | tee -a $target_file_name > /dev/null + echo Exec=$script_dir/Zano %u | tee -a $target_file_name > /dev/null echo Terminal=true | tee -a $target_file_name > /dev/null echo Type=Application | tee -a $target_file_name > /dev/null echo "Categories=Qt;Utility;" | tee -a $target_file_name > /dev/null From e2bcd05f6055ba61d41e98b6240e911aa1d23a66 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 22 Nov 2021 20:44:31 +0300 Subject: [PATCH 0011/1271] stratum: support 128-bit rate in eth_submitHashrate callback --- src/stratum/stratum_server.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stratum/stratum_server.cpp b/src/stratum/stratum_server.cpp index 2b454c47..5e239baa 100644 --- a/src/stratum/stratum_server.cpp +++ b/src/stratum/stratum_server.cpp @@ -951,13 +951,14 @@ namespace bool r = params_array != nullptr && ps.get_next_value(params_array, rate_submit_id_str); CHECK_AND_ASSERT_MES(r, false, "Incorrect parameters"); - uint64_t rate = 0; - CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(rate_str, rate, true), false, "Can't parse rate from " << rate_str); + struct { uint64_t low, high; } rate_128 = { 0 }; + CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(rate_str, rate_128, true), false, "Can't parse rate from " << rate_str); + CHECK_AND_ASSERT_MES(rate_128.high == 0, false, "rate overflow, rate str: " << rate_str); crypto::hash rate_submit_id = null_hash; CHECK_AND_ASSERT_MES(pod_from_net_format(rate_submit_id_str, rate_submit_id), false, "Can't parse rate_submit_id from " << rate_submit_id_str); - m_last_reported_hashrate = rate; - return m_config.handle_submit_hashrate(this, rate, rate_submit_id); + m_last_reported_hashrate = rate_128.low; + return m_config.handle_submit_hashrate(this, rate_128.low, rate_submit_id); } bool handle_method_eth_submitWork(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section) @@ -1123,7 +1124,7 @@ void stratum_server::init_options(boost::program_options::options_description& d command_line::add_arg(desc, arg_stratum_block_template_update_period); command_line::add_arg(desc, arg_stratum_hr_print_interval); command_line::add_arg(desc, arg_stratum_always_online); - + } //------------------------------------------------------------------------------------------------------------------------------ bool stratum_server::should_start(const boost::program_options::variables_map& vm) From f9b5b6c51e9dcb4bb4fddd2ff8b5f1be49e2766e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 22 Nov 2021 18:54:19 +0100 Subject: [PATCH 0012/1271] revoked wrong commit --- src/gui/qt-daemon/main.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gui/qt-daemon/main.cpp b/src/gui/qt-daemon/main.cpp index 5e19390b..a37f6241 100644 --- a/src/gui/qt-daemon/main.cpp +++ b/src/gui/qt-daemon/main.cpp @@ -27,9 +27,6 @@ int main(int argc, char *argv[]) // _set_FMA3_enable(0); //#endif // ARCH_CPU_X86_64 && _MSC_VER <= 1800 - std::cout << argc << std::endl; - std::cout << argv[0] << std::endl; - std::cout << argv[1] << std::endl; #ifdef _MSC_VER #ifdef _WIN64 From 379ca875b4da7d00ba2a21e32281e57a1eebabb1 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 22 Nov 2021 22:57:25 +0300 Subject: [PATCH 0013/1271] === version bump: 1.3.1.134 -> 1.3.2.135 === --- src/version.h.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index fec98663..2f4959ae 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -5,9 +5,9 @@ #define PROJECT_MAJOR_VERSION "1" #define PROJECT_MINOR_VERSION "3" -#define PROJECT_REVISION "1" +#define PROJECT_REVISION "2" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 134 +#define PROJECT_VERSION_BUILD_NO 135 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 189191e6e5f2880d89d0e3e713296263a9fa73a2 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 22 Nov 2021 20:44:31 +0300 Subject: [PATCH 0014/1271] stratum: support 128-bit rate in eth_submitHashrate callback --- src/stratum/stratum_server.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/stratum/stratum_server.cpp b/src/stratum/stratum_server.cpp index 2b454c47..5e239baa 100644 --- a/src/stratum/stratum_server.cpp +++ b/src/stratum/stratum_server.cpp @@ -951,13 +951,14 @@ namespace bool r = params_array != nullptr && ps.get_next_value(params_array, rate_submit_id_str); CHECK_AND_ASSERT_MES(r, false, "Incorrect parameters"); - uint64_t rate = 0; - CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(rate_str, rate, true), false, "Can't parse rate from " << rate_str); + struct { uint64_t low, high; } rate_128 = { 0 }; + CHECK_AND_ASSERT_MES(pod_from_net_format_reverse(rate_str, rate_128, true), false, "Can't parse rate from " << rate_str); + CHECK_AND_ASSERT_MES(rate_128.high == 0, false, "rate overflow, rate str: " << rate_str); crypto::hash rate_submit_id = null_hash; CHECK_AND_ASSERT_MES(pod_from_net_format(rate_submit_id_str, rate_submit_id), false, "Can't parse rate_submit_id from " << rate_submit_id_str); - m_last_reported_hashrate = rate; - return m_config.handle_submit_hashrate(this, rate, rate_submit_id); + m_last_reported_hashrate = rate_128.low; + return m_config.handle_submit_hashrate(this, rate_128.low, rate_submit_id); } bool handle_method_eth_submitWork(const jsonrpc_id_t& id, epee::serialization::portable_storage& ps, epee::serialization::portable_storage::hsection params_section) @@ -1123,7 +1124,7 @@ void stratum_server::init_options(boost::program_options::options_description& d command_line::add_arg(desc, arg_stratum_block_template_update_period); command_line::add_arg(desc, arg_stratum_hr_print_interval); command_line::add_arg(desc, arg_stratum_always_online); - + } //------------------------------------------------------------------------------------------------------------------------------ bool stratum_server::should_start(const boost::program_options::variables_map& vm) From 42335023a97d98b47f231750909a9d5c312d0edd Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 22 Nov 2021 22:57:25 +0300 Subject: [PATCH 0015/1271] === version bump: 1.3.1.134 -> 1.3.2.135 === --- src/version.h.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index fec98663..2f4959ae 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -5,9 +5,9 @@ #define PROJECT_MAJOR_VERSION "1" #define PROJECT_MINOR_VERSION "3" -#define PROJECT_REVISION "1" +#define PROJECT_REVISION "2" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 134 +#define PROJECT_VERSION_BUILD_NO 135 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 3e74e4d58160cf0628fca61d4ca5bb4da6d6eb8d Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 23 Nov 2021 03:22:51 +0300 Subject: [PATCH 0016/1271] build: change single-job make to multi-job in Linux build script --- utils/build_script_linux.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/build_script_linux.sh b/utils/build_script_linux.sh index 9ffb1d54..c2156216 100755 --- a/utils/build_script_linux.sh +++ b/utils/build_script_linux.sh @@ -46,19 +46,19 @@ if [ $? -ne 0 ]; then exit 1 fi -make -j1 daemon Zano; +make -j daemon Zano; if [ $? -ne 0 ]; then echo "Failed to make!" exit 1 fi -make -j1 simplewallet; +make -j simplewallet; if [ $? -ne 0 ]; then echo "Failed to make!" exit 1 fi -make -j1 connectivity_tool; +make -j connectivity_tool; if [ $? -ne 0 ]; then echo "Failed to make!" exit 1 From 4db29d337d8fa84b096f0b75273c8541201ed3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20=C3=87EL=C4=B0K?= Date: Tue, 23 Nov 2021 22:59:43 +0300 Subject: [PATCH 0017/1271] added zano url-scheme handler for MacOS, Windows, Linux (#311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added zano url-scheme handler MacOS, Windows, Linux setups * pass parameters to executable and print console * added event filter for url-scheme handling in MacOS Co-authored-by: Barış Çelik --- .../qt-daemon/application/urleventfilter.cpp | 18 ++++++++++++++++++ src/gui/qt-daemon/application/urleventfilter.h | 12 ++++++++++++ src/gui/qt-daemon/main.cpp | 12 +++++++++++- 3 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/gui/qt-daemon/application/urleventfilter.cpp create mode 100644 src/gui/qt-daemon/application/urleventfilter.h diff --git a/src/gui/qt-daemon/application/urleventfilter.cpp b/src/gui/qt-daemon/application/urleventfilter.cpp new file mode 100644 index 00000000..d7e4e35e --- /dev/null +++ b/src/gui/qt-daemon/application/urleventfilter.cpp @@ -0,0 +1,18 @@ +#include "urleventfilter.h" +#include + +bool URLEventFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::FileOpen) { + QFileOpenEvent *fileEvent = static_cast(event); + if(!fileEvent->url().isEmpty()) + { + QMessageBox msg; + msg.setText(fileEvent->url().toString()); + msg.exec(); + } + } else { + // standard event processing + return QObject::eventFilter(obj, event); + } + }; diff --git a/src/gui/qt-daemon/application/urleventfilter.h b/src/gui/qt-daemon/application/urleventfilter.h new file mode 100644 index 00000000..54341dbd --- /dev/null +++ b/src/gui/qt-daemon/application/urleventfilter.h @@ -0,0 +1,12 @@ +#include +#include +#include + +class URLEventFilter : public QObject +{ + Q_OBJECT +public: + URLEventFilter() : QObject(){}; +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; \ No newline at end of file diff --git a/src/gui/qt-daemon/main.cpp b/src/gui/qt-daemon/main.cpp index a37f6241..f32dde0f 100644 --- a/src/gui/qt-daemon/main.cpp +++ b/src/gui/qt-daemon/main.cpp @@ -12,7 +12,9 @@ //#include "qtlogger.h" #include "include_base_utils.h" #include "currency_core/currency_config.h" - +#ifdef Q_OS_DARWIN +#include "application/urleventfilter.h" +#endif int main(int argc, char *argv[]) { @@ -27,6 +29,8 @@ int main(int argc, char *argv[]) // _set_FMA3_enable(0); //#endif // ARCH_CPU_X86_64 && _MSC_VER <= 1800 + if(argc > 1) + std::cout << argv[1] << std::endl; #ifdef _MSC_VER #ifdef _WIN64 @@ -62,6 +66,12 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); + +#ifdef Q_OS_DARWIN + URLEventFilter url_event_filter; + app.installEventFilter(&url_event_filter); +#endif + MainWindow viewer; if (!viewer.init_backend(argc, argv)) { From d8fdb7358cfeb2e762faf345ec23395821cdb26d Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 24 Nov 2021 06:04:30 +0300 Subject: [PATCH 0018/1271] build: made multi-job in Linux build script less aggressive --- utils/build_script_linux.sh | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/utils/build_script_linux.sh b/utils/build_script_linux.sh index c2156216..2cd431a2 100755 --- a/utils/build_script_linux.sh +++ b/utils/build_script_linux.sh @@ -46,25 +46,18 @@ if [ $? -ne 0 ]; then exit 1 fi -make -j daemon Zano; +make -j2 daemon simplewallet connectivity_tool if [ $? -ne 0 ]; then echo "Failed to make!" exit 1 fi -make -j simplewallet; +make -j1 Zano if [ $? -ne 0 ]; then echo "Failed to make!" exit 1 fi -make -j connectivity_tool; -if [ $? -ne 0 ]; then - echo "Failed to make!" - exit 1 -fi - - read version_str <<< $(./src/zanod --version | awk '/^Zano/ { print $2 }') version_str=${version_str} From c4f20daf0661218a32021656b094b9cc409ffc5b Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 24 Nov 2021 18:29:14 +0100 Subject: [PATCH 0019/1271] updated graphics for UI --- resources/dmg_installer_bg.png | Bin 41752 -> 0 bytes resources/installer_bg_164x313.bmp | Bin 154052 -> 205384 bytes resources/installer_bg_191x385.bmp | Bin 221816 -> 294196 bytes resources/installer_bg_246x457.bmp | Bin 338236 -> 449744 bytes resources/installer_bg_328x628.bmp | Bin 618008 -> 823992 bytes src/gui/qt-daemon/app.icns | Bin 68465 -> 71298 bytes src/gui/qt-daemon/app.ico | Bin 142967 -> 110252 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 resources/dmg_installer_bg.png diff --git a/resources/dmg_installer_bg.png b/resources/dmg_installer_bg.png deleted file mode 100644 index 9c88ecb688d065c659b5003338566d2db2b0f0fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41752 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 diff --git a/resources/installer_bg_164x313.bmp b/resources/installer_bg_164x313.bmp index abc47621977a3f10e89409f5211f9ddbabe93424..80599afeda4c30ca327c7ca81d47efd76324b6ee 100644 GIT binary patch literal 205384 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 Cx#>>; diff --git a/resources/installer_bg_246x457.bmp b/resources/installer_bg_246x457.bmp index 865b3de39c5285646d4f91274646ce33f214a238..3e20a2e98f6d666bfd927ed957158ab84ea3a406 100644 GIT binary patch literal 449744 zcmb@vWq4KB+O~baf4)E8v!yLA!Gn7skPxE8-QC^Y2<{YZyP>6}RH(bVyFp7S#idZ( zAq4vV`ObUHIYVgKkG$WzkK@{7u9Za^$U5hB+Zf9);>Z2}kLdrVq7mpC`sRO${4W{E zmB=Q(e&_!yM&=3r&+i|9mwhBVM*WV=qMWX0#f0x+qf0y`f z|CEGocSvIQJ0z*+KP45V_4=n|^twZ```jV9eeaaKes@V>|GT6ZmATv{6$9^*>OtM4 z4%H92OBzwj(C#vOcn_J27LMp4OGfsPWn+5C%CWuV?lHY()wo{rhv&iB&QaoU*~aUBO6Ii*!{sNmuXLguZJ92IDksbSq6u=KXrdhK z)MPnaGD(ho1q*&ytJt1C1^%m?f8ZY^2u<0?pSibA5S! zfLxvzAXkO8Z(>0P>fsM!u{npH5v_=OadI zz2wT;8C-kGW!8&}+~s`z1UXkdPR`ejlXLZBLGGs%{V!E z?>IR)+f{bu50ssG17v5x0QnjHlxt}l>-V`X@?B1U`6kOnzDVmQU+1~V?lxE1*)mK% z$>=Zd#`Tf6&>QIWm_G7KWFL7cqK`a}o(=CU&xH4qts%YS(ZF8vLUdpGIAfr^9`7O# z`1O|ie0s9p>$Xli$vKc$<@nbb{YPrOT7QPViy=VQ7_ z?P%WjqwbLM5qC(j>m5=!^iIhidWYlqU}7l4Sk;t*O?aApQrYduejdXFidtfyTY}L zyb}%A@Ty$D2G=jc^@}k5oZ-5cJk^EkXW;z()B*BlqKj0 zEzefJ4n*qN>er$8wQ{XezgD(mxL3q==Y9gy%5@@Monm`-I-Z@?oBI%4=l0|0?+@Fk zL_Hg}tMKgF!FV=mK+VIrzYXW#V|WjlKcc5B!mpQ%?jg&^^pX|mZv1-7)V}g6e*Kd7 z0C^Oze#9NGp59-c@I+qyKYZI4 zzlOp4+6+qKkgOyIJ&Z$bv0}+#(x_o%lXRja=v=JoU6gRRkh>g z3~Z}T!}sZ06<)r0l3aS!Q!d=&CMWUoV%;STlLu7yP zU^&t{Tu!c^AivEYB|CHQY5aHxJpYt!X?r$a4b$Id4v=p$2FTaw^As2PJRL8u8X`Z} zyUJ%-17Nzhya~&1#PpR{qWT)9pNHS)(9@y4-S($HMh+xVD&9whhyFE0qkpJS!t!Bpwk zk}20Wr%T7?G*%VQJ(PlyQ4-H5q6Dtvc|G@lhb(nG$UWde?g2&-tT4X`J;e9kOkBT_ zn0+<=eK($MxQ5XNH#uK50j^aOjn39ikTd8s-hHNiJWLZqSB~M|VZ2;^aJpQ$&mF%W zE60jpTG=ie!Tk}g@o&{(NAP{HY^dxj9wLY8hRcc7^{n3V z*sMPCPQ1nS{cyb=k2YQn--hdHaE(_lR*j_fq;(8^1{qSG>y01J=ZoReBui@GYuJP&@kaGPpe*Kz1zh4;LmW5xFGh18_ zmoMW+$X9Uw4e|DOtMJJlhbVh@Ejm#XU~$et*FHx&60DiaI9=M^STz)%zXgPFRh4? zD-Wc~m6g#Jze}Rv8o}*${W=~< zk?R|i<=Vz1x%RI_l)!7_<=VaQtZ_R(Yx^p^ntM_Es^Bhy<5kKszWuLYIOjDFhDiIp z#Of=3xc4|*TReAiT{B6}!1$S(iNxf}_;_ORG1MBy$;EZa3;kjerdZ6rp+n-_jr>ueU1D^an`Zise zw!Hc)9)FhHAGyf)`2CI=xaL0aMkKWwV(!->`pHY-}Ih&q#L!f?!@92+jEJ-7r^$SQ9WfTTrZ_|wj4b)g*coT`z2y+6NghX zdmN^>db-GyUM|GpF7haQ0$$A`E=w# z963XdmCqn9_mJP~sEd&wpC`9HLymj~j?c8R&L%c*3AFg;@wwI@xzG|K7nE&P6M6EA zIQ%*tzm9`t>Sv2%c3-MoTM4&(KT9#v zu{>HjmPJcD_lkDz5$%i7e0Xl0C0A;RtyT5-F!ehly{?}rm+NN82P*#vp!ggXDYk z4P1YfHc-Ay9VlO-k&hN*sX^wn515Pm&`GFHScNl zYU1x2;_vDaaE(fb-zmk)wDD?u53bd#wO*F)LLS>6uI+kRf{DTZDbc;)3~3y0>Se^< znpcP5(Ve+9Tr1OB8}qkgaE-%#{&9P*HNSS)R<2bJ*G`@t`# zl?zSe(JK<*I#VvMN`Pw^UJU1|MV79xI=Z?jp66A3ZDFjmE7!cXV-0L?%#x0^X>x5j zzHCIiy_~Nt<#pU2+84pMYCiFGV<1dZOIOtsW7omA5p}X!a_Ab^uBHB6HA5~{!}nZ2 zd|zX8a)HIv1ghNS0lseh|iJ6u=6bOkEA9oOX5x8+)6aQv~8>mD}OPHz1_;M(8R%`8t> zzjk8r5U&4AuBn|R_qgfT>G-zR#B=&sy{>#by9kwZ;kuewycWN%rykza8H@Lnd2qdO zbWd3_*7EC{Tn~opLAGCa<$6G;Uq5U6^^5rR3x0#(dPo6_BWj@o%mJB+t4h+`#hUQd2~e~w&VPmX;zIrR#1YLzm+l-Dicbt;YHxvyMp zqz6X4aJ31>$@z`Q_pccF$z^JKmm7$?tGwhAf7dIE0^pi>Vg-G;s%dheOg)?aRi!(c z3gh^;dNv+@2Ax*H`blNHak5-kHG^1&`bGXI{CXt41pFG7)vuN7gN2q36~c8feoef- zm*9=PEy@43_QbFb&t=saL1a(?nk-E7t=ot`i2zNAUyYGhXw3F5^_Lc|Hs}b}|BVA;JS1(xw%d7C|75MdXu1#JI z*K_gfIn=*f;kpT~8%M!4UR|T{w=&(8YiebAcy%^jokjeuT&MlnuVK2AYva|#%P_4n zb#SL<)+G*Cul|c)Q#bR2Ykgkp;ttp9*$&r#j>EMk9!q|0#p2}GaBXU5{7hOqGdZ@+ zbpcFkzpKc}vFTrHAKT)(n=}p8x;Xs-nARM7A%4AtTDVHNetcI zi#$f(daKs}V)22nJy4YEXUVUh_Zv(cK3HB27%X4n$J;Wy_;mtY6MKKb`W0XMItjL6 z`TLZyvb~e*ad7QUeyv=4$v!-JKRRgSDF=CPA13ZrwvSfKglliO_NFH81=rNamPOI8 zjf3lOygEeA&Iv{EtX#K6z;q$6}xAGXQ0ms2xa2H(rcvH5=2mhknZh&cXQ3%PkCJul_DE8mUy zw-J4G<+=$q`oT8--QZ1L&i#OV{32d{zDPZro)_MIt^%&<4W7fl&!RI`Q;bemQv+uf z;B-A)uka*(nIgvu;5r|!3rES}!qIX_MOIe+k!LpwD6YlY{~C5&BI1Jbx&B^B#if=L0RKjaS3_ol>6KIA8ZCq8g_DTECw7y$6UeK_cav87)s16$ zU&FJ;;FYKXpDv?TRz&=*d3C<#*7$VRz&j;lz@3t6>Se^?egA24>jb30+pm>tmG-*+l4EzLXO7~! zk>}&r4%aFCTxm$T)*L%WdtF_)R?n_LhHE^#9;O?S@oeT&=As2!I~(0g7Q^*Y)RpT; zJo?KPdR)r&<8ZBdLfNJs{tQezT)*HuSYGiTEMHIq`yp$*dn^) zCSK0Yi{$+qLF^d`*GQQ*Tt~xnj9i!<4cDjzHBlQ|N$k8SpITKWo|ppHY0|zFElEW% zznImC=N7A+$2Zq&`rN!k*rrytK3A@9D3I%GiOZKN+r;Qg$-TKx7`_+c-Oc3N4H}Qr z(>8+f#vogGH*8;SfNi|nhE^8KuPa;cr+*@vjR9fa+j`1KD-zy99HFri`-OjO{epe*_ofxECC*akI$goWeo&wVvi)T<1&!Q%t1J_y?SHCW` zxvs*mwJz>>b_0Ih;@ZRXvFA|_UqCFrU{o(z1mE`?zt+0hAbA9yANCj^kC9(nIrcz# z9Im%gS9_|H>p}9IFZsCt5cw==EL=~NPZP$-N3o;L`Z-L0&huX;jl-|U$+yJa-=vNw zo|{NaJ^{AJ%dZ-1x6P6R^*-dBk&}KlN}_>I>Jw_;oy7 zC&}3b(Q*c!&&(y)o`c$=Q4F4~OvC={IJ4^Yg=X^UQtHVtm644 z=}1>qrd(T$oX3lKuB#Stf0&ZO?_OoOjo|X0REfEvm2OUX;il9 zpEuycb+FxF`S+E2W?mYok<~LN)1cD$oL=@EzD{565;HXy;QC_8G`YyV@InQ?O|Ehl z&d>7rH2KVF^6%3XaLugGDb2wbF|X4^A0d~xI(v-#mN!-o!}uZM@Mbt>QDY14FZa&q zZ9H0eUNWVJEW)ej;?;BDx^+CY-!a{#aWr!oqj*ola}_)rrd=(j3r+lOF`aGa)!MU8 zqh6+5C)r%b;nB*pQ!k6`%&lRN7&%C}#%F`HUS`+Ky2jvI3)kMYH$3|~eraMxf$KPDo>XIThill*z^{#GGk2B`*M)GceqBaQyn>o|H8NZ?o1$Dd z!*v_|uDSU20%Gy`aJ>MoH@Xpr!}LqU+K)_!>*>_RVf#_f0Whsx4 zUkcaxaII|TbgF$xHXJ*m=54NXtaUNZ|5*!o&3w3q|Bf|qeP1!Tw*GmZTa+Ok+!Hz$ z;@?fw((0*=H=ug>HnRCv#_L0Sff==Eah0#~Y^4&4Kwg zSVk9hot=b|iNndKmt{-)0~O3_7aE@1my&BQ%_9~^OLC1mSUZ;F!DpUX&Ev&9_ZQK} zUP$~s53UzeLt9fM*Y7E&|CKA(7V2|o0kM1&y|B6%xmHh|yn&d!UPZ5**xaa|nKt!r z{$9qz>tLK*<0`d+_GK}|=G4v#VY+yRT%f*ip>&3v!yxaH`}Q?C z^&{lfj#odVT;tUnJ$o5ne=*2K)=#6}GnxDlrWX-^FSMC%8%LZumiI0*;0-WcJAxe3 zj={@@;bZK9C_?#NxXz$XrnR!vE?%v9wR*L3t%^bsFs(CYnpbPQtns(@x19O1S$5vs z$u)7edbKYst6%>+uGOcNu7K+*jm6=* z9pFY1P1Fnin_7}qZJbZc)x%Qe8a_%zf zXZ%@MS3hs(esZl2w(Ifj2G~}<>*^daLHItPA}-&X zJ=SO+*L!kC$*!!C#Nv2(#RPsAaGeI%F|Zmxgttk^Js5eaG*UIyQ`1B^2zTXqBJ@D&U{p7_!7g-0_i{aTY zZRgcitqiXoO$-jx_3G8A(vHDPhT>y`$w84bhmxg!Jpis@I+fVliNP(d|7Mtuf$2yT z4%gxTH@P--aVr-8uef$R+nGH})EO1H*7+2N>nv(#xp3VXi>qHdT*G!}j*VYyEZ%B+ z_8k0rE?lpL>*sB*AA#%5(*_WaBhP`<#BEVGd%_#frf&X>uX3&HVD8%%*PkUR*JBLl z>fN6vQcFu3Po6yi-=2hLPbR;1qXq-pNt0zq;S4!IZ}%YDTj?u%;dy^W0P;6F2+xOL z`A}uB9H|U}?GU&Qmfx#G*ymf=^mAYZP{RsYVH z8>=hi#y!>2v6AmKmmIx;`gk2|*OP14Q%7r1X>3m3U1zJ77~R&DYWy3vuTs-*r~h^t z#xLfx2c!_Di+zx{oGWIpi=z^Y?Ncy*vKX$J%{w>8Urtg-KSCUJB%N84tO;;UjBVH+ zEBmtGTE#rk9^&*}ndI2ncz8KnqwVR;3q}o`-P2hsek1Bjq?|uzZN# ziyA2Jq7U)$uQGo^XlHA(%>I&lTGFO@pf ztd|k@5_`w=B)6tk7Jy(gmDa_LXY1@4`E{D+*vRT->s+bU&$_X{tDC8x>5NJx zd~5xzPJ7uf?Zo1zS-!ELJPa?k_FCWH@qeeQ8SPdmkfb9vy;}eL*C*s+YiNmMh+iuihCd*Ix9^}?= zOKo^>m46qmm2G^yg0EEs!F32+hr%_g2%+BizC9-mrd8=i z7g;a1Wf)zW&2>`>`z?u+H`Fs*(#Wh>8SE6JQv4aVZFMY$eOuQT;lZe*i>&A1eL;~e zi|y-k^W^$`*j`g9H`dk2^~Hs9y`K76t+MU#O^+MCuhrt=wRpFxhS5qda;j*CoPzCBc=$>3@Kc(9Q;$5!yvvac_QsL> z9HRblFq6HonXrw2??Zdg?hFLmzof%8dv^|$PlW4<)WJu}mys|XJxsog9VVZ_^C!{D z^bq+l3Z|oo!QuPe$bs?>`T!sQGTl|)j~gOS!tqvbxMqC}zPE6_WhPuBW%>b6OB<&5 zk$+9&edotsL4OxnJ(XGyEYC-C(QJ*sVY-=Gndw)&2+K)|c@F%X5yKt?wGu@MI)y4nBHGZu*Ttp z>1DUk&z?P!9`^V?@{GqIc}2N~>4)8k!{J-G-ePn8IQjMCGY9iKgJ-yYiuFnFq4Iw8 zSmJA&>-h0-J;6ws{w#4KTu+oQlHgi-PMZqXu$?qTw&hR*Czm|j9433no%fVEejNzc z!EhY{*P%AoVQ?KrEFRAOk8n9bYt1@6Z)951W#kMTD z)Rtp(u{BdJHB&EJ1h*Snq+?SXHLqHHx*YE<>r}_03OKfOgXgcK4x@6pzL3WYOU>Fb zAGW*b`aC#p)3{uHyGH&%*Jl%>*UMRh9puD3XEub~%S1K%~uHZ!a6eWe<%tBKKD z@O9#mOSSai^Zel252k&M&KCOET$4xR-=~V;8~;A3jF&M#Pu;@entkkP8jG`6E`vRD zc(-a_`UKgH_9)ls^sDJX{tDj*sHg8Mnk3tpKln0&_&b`JZ}eH@5cw2+gg%TKBJZ=l z8v)no9rOWy{$`WCKZ0D>67ucq5R*K50k5>-jK4nVn7*j_w_*@5ZG^$>Y%+CbUt4%6_x1=b(&g!LH+<{w8}XTmc+{RB*_-igru z^km}gN%-{yVzCMGVZub>aqWFkPfKuvZ#QCbH)as0@>-PS#y*1SvbzdTZjF#VRY7na zfL8>eV50*teXt_T=ukxjo*lv20FiJVL5`h3uPdKD*!kqxS=7Wc;W`VhvyINr$&m|l za*Zy+ndfQOk?ZSJt@hf2=eBl{K5mge);7xx zTYs!>gk7|%QEsei;JQI>tgMym%c_XQd2T&wBR8+1?^R1}ypG;>o%%PbQ4ycR_O)te z)6msw_J&up-vnLd-qk)ojX4##&ZXCt?+@G5#tYcnQpg@Q*glPipSIUi#OWsrW|Cv$ z=giigsHB%bd~}Gq#-a4da)5j9{?tjb2klOUYqSe5-<39Aeoh&KXXE3=lVwlgWcfZ} zw0s`M-n>Y>8hsKzOg=^*p!Xw&$a~>K z`Te@l%kDj1QGJ!pdGZwQ>B_Y`Tu)_& zX}bJUN)Fu`1=k_;vjYv!d&@)UXNTa~VbssU@oZHjTt{*?0IG<>w`1khyewwUO5_|l z@)0=V17}VLA`4^W`GCG^ao=%`SlJB2)<1dCYgw>zac{FK=Kj6z@jY*TMH1 z6>P7CXC?wjMw1X)hZaSW^T30Vq5ucr_R?tFI_J2_r8z=(|G}K&7M=(K3nJq*X&JI zuJcvCa*FE{`QC8NoGtrYPEdzD1lI>sC(FTf_SLZNbGS~KD8Hgz=oi@Dkup|x!uQ@H zH`!eFz>rlKp09Aq)A^HHl57Y0#^Shx#dAUq<|$&pcT!^C#Ilg96j(S}Q}z%ylT!3B8%=>P4SeeVTk)XUihgtC2Z70j8Ag zu3Ve`796Yhs!v;9O%2@6tN%~A)_#}Ho;h4=p6$%27_NKRT-%;))zFAR)U)9_TkGPQ zXTx<7Tr1n!%QkZ=FkPo?!*wHm-GW!QkY~428-IAJ=GVhG|L8yG`Z3$Dw|WobcQ}-p z0%pvJwZFoTKa86wAHeoUaQz`Z{c!?ya^!IRdGa(o+k+a~bl9H889V-Rs3k`B)P%#e z#k0kA1YAeLbrejiV&FO&u462fMa%JqRC?Hz?Aa`V>m0bwgX;pg&WGy)!?eXU9M36+ zYnW~=mMgPMP_bNY=DLyVrebQ(dyrkYzR9c^efY_=j@&2_AQ?;2`pwFnP4Y*)i~HM4H;eYJ}D z^-B0g?F~Ff-R)8(Gd4MaaLxX-0`{d91j$+WK7&s4@25;#T>DyF6RV$P=H+CW#q|O1 zw+G09_QCaDxZcaUoA_!MKE8{Z<}b+;We55>b)xLfgX>)Un)#y7f?+z8y|>}RZ{<1! zrqNsI^xO5!v zMYz`a6IIhlYGukb^|5MYTD=;si{V;lQ1W3p&xQ9pZ0l^9#^9y~4%aDgooM23<@#?o zxhD2@xYk*4ofi#4%Ch6vdTrwAE?jG^%oi_qa%;!0z42&`!A%@){Mz}m__m#6tM^;` zu{9nipNDT{S`|SL+o_A|Y^hT}Q>Im>pKZ7%7H9q}mws11wX;Hv$6?yk#+7aMY^rD1 z!F2;%H^X%^TsN@Z@5UKNp2XP7HH>d|$FK45hl#}>#;YHmF@*Y=-jB(lV0x>Mt31y7 zLf}aG7@ohui$9E;B=5$;bUZP6yc_)wH{x;PaO2nRc=mMm_<7>l9?XtUm+d+9v>Kyi ze_bSVrR3RVVR&^oz8wkIQE(m2zTjwb>{xnUaWEZ^64(zCOI@7a*3wcrzoJUc%`ak} zl$abZJ~yX`+`5?BcahP>IVEsi3fHWyrLbKN*S28sYLlh*X4r0`MvksEq9&MDHNb8I z%$n7{uII6;0gmfoxQ?}swHB6Z$+2sgL332gwenp@-VNU!HMXkheOEKbRt?|Eb!7@X ztKeJN=DyWVjr~#uvo+c5OUs4nyb#2^T^@Vb;QS08ekzaJH|t5_^y9?qCklzv>qF&u zDfi4|_Q{b0?I%{+$9-3|8~sXuVwZ}#$4>MUwUD1-dr$5Z`d#!aVwgh+V&82DOvCnv ztRIB9!ZUeS@KAXhy%_}4D%gIP`24fj;qoSoKg_=UhuG)48MYr}eE_bFPs8!QVEVqP zmhNFy=^0y3F}u73uA5ElZ82@Un)qAu>PlEHN2MnI=Dn>)s9%fAhXiMapzC zK5e|3HIDkX*1*kPOyk!#&oDFfGB{JFof@_kgIn>m@oV^2uAQ9P^6S5uIQ-9CJ2AVH zXFK0hzrV7rH8gXcVHd8W;MvTm{5!6xn>pv6<-m0wvAA+w1kdW(+Rv^W+@1QF%{4i; za@`2m_4K{&o#;ZZi~iMg7*?*)!_x*6lf!u@*Tm^~w8b^vK698n?KhIz8C<8j!}Vl& zH%7U}yW=Mt&;BG~DqK%9OgmhEmEsB49`b#Luk5MDmmBbGa_HS9;hc{Y3EO|=`VjB+ zBly{G=x9YEb@2qwRLF+wYB{&O8m#Wy#?s}sN-&Qr~s+^;GTXnFmj2pg{cja7t8`kUS zch|ypjV;49zHPJJUXdnOD^g*ay(h|cLx!}IPh2X8>rA-LVZUiU`&wZ8Y%WaaDA$2- zO^xgpt~p<#jv7P>IZh(`wLH_bm=Z7FkOn2 z?JC%=8QKHS=HC&|R<7%)ldm4n+#g>39Be;4b+BxjI!GQ;rl;|{@_=u&WroJyuGBl# zs~ruOw<4J(OPNOeJ%wBguZ~r&Ra1FxntYt-PVLNtc-@ownJ2lp7yCiHsH6MH&n4v1 zjfwQSqUF~TYG|e8+r-)X%46{CSn6i6_;ox=#Iuvgtx;79dq5I7lTT+!E7+IVAZO>7 zac)X6eq9FFJZDrX7m1xOtB9>H&93Geo?EN%>{_F%Ewyl62iJA%y}DH$E#$~8yZgyGrBG=lH5=u8fK&tdx%jGxMdZFHPG{Dj8nW#pmoJWgh4TX8Qjv67a1r3!4IUDZ*q-@`>Tywsya(y3r5blNRd*NA` zUI)|bsevogkMr6EV>NChN3>N3*VS4pgXJ>%SvptNIfJ5J4bPokt$VicY0IlQ6R?wM z>ScJe$*m33T93vvsd5wd{o1LUIkC9*vbE=>(s?z9 zYcr?9_c!MmaxXEtHhI401zMkvVy4vW2ceg3&NS?GC$pvOM}zA$l!3CDQ_04&tyz`s z)W((V?iSaB;CgTm?u8nQTQPX;aNXO*904r9=rNSDh6l?7$~Eg|Jo_QoerVcI;&Hy_ zIgEIGI9!i_?-A5KUFFr#vEPnQp2`V;giTz{546Ru~{ z_d?0u^t*y&KQkNqs^jtNXv6fLvKV|j4z}azsU{j7Doo%nt!3ocRj^$R+tqMgjZfF$)3q>di|ebcb?{s-?X3+keN!D=ceFI( z)rj?a3pH~Uk5xAssf#r@s+Ak{s7_VGbu}xs@LKxUwalj&w()S~yPDowC4Dg?`rVcA zT}dw;wU;yBTA_mRvJAOe#{5hzk6Wnsl`u1xK}|f1J?Gi%SIc5=DvY1bQPIoK4nfqv z(eW%+;`7tg7mwxh@0u_}_QomO9Bt1{q8K74shkHN|>HCjJ@~#-u;HktNz60aQ{*G2zeF8 zH@Wv`e_(&Z^oFVZ@aTScw6!YJ>zP?zXKOw4P*1RT@KOHETxK8ZT&eBas$oX5l3H)M zGEG0H2o*AqkxzdoTjOt7&ftBX#+jWd)RmIRLsf~)izdQy98#vE;8|evyX9iq7)-Xvfty~+gruOZ`;7(qxwQw)aK>Bk{-0^JZp77^A*~)fj&kN7i^Njz> zwdL75f2K3z4%h128K!=wGiX{{usmDYE~SrM0oPSy&2{W+pzrjOt?l{VEp+&=FISgFNph;X~T6)7p{3B-feIB zHkIBrx$^<~x_ipwWH(IjCV$>r7SDML2{4_6lI2i&3S6fd{f4g{#p8~aXKP91MH#nlNRnK*uit9RfuZ8bg_^wsHb;ZYPh|{af;g~-7Eo?KhR>8bmxy`onU4~DW zWx{n1KT{E0Gdq_N1=s93&t}hB7W+|Q`*apuqf;<`lKlGwvbbg*kDA0W^6>p}?5Bx^ z>o^bD4d1`Uxyvs`Qw`fY;@}x=k7Z7X`07`Bk2~o_enF4p{eThj!7Oqt>L>5O^;>ZL zCVIo)(rc`SYuHvF|0ryPyn=t<57Qgn;2NekD9=c_z6Y(J+=Xe_R>rro*Z+~JaLw#v zjWP|-s%rKYSHQJ;b+Iyy3g9}ISxWV3)2mjl(XCu-PEGGfxrS$lYf~@7tJNnRpEk9! zzi@5!qLoc&w(MpN-0E*RTsxk9JKt8{H{$#2y~BygO`eTk8@}y4Tl?72woLtuSlpaz zsP!{=?#i~-#&vJDiO2hOr|*St!*w}aR}Sn!p52ogT2E@@tgu}V+nQs)NF4sC`ZbI{ zG}P)C*V0(oT^a}132=>%8?IB?mz~NvD(TeB z5Ii3vhW#Cn`@JMXj+f^!OA5cM7s!Q0t(X5S%t5?A` z>ZoMa#jtIAcR4d|Wz4&kF{@t2Osf%l%t~RKx?4N-`pd=Xd_;s|0?2Yx5Ut>IESIji|g&67=)}81lVyd6G z|89#>&nCA{n@-P*oGEOayz4hY-t!-cPmh$h1E`4^uXdH!{f5iyzQg4;zhRtRIaFRk z>f;}Uj$%LlFxfa6p3z1)*0qyq^=SNAz1n!TUZcnG>do8>W;6R(1SY$w8hcyk2V0(K&P^cp?wl8O@@lP_ z83wIdw((k+HfNaeyvE;GF^JD#mlK=*YIqu!{8dGZLS}g zIg&o*D7<^Lyc|TYE6J0+J{~YW70KKQya?MJ^Wfclv~p3N*N zHQ@vG>2RGuJe~m8NpPKv((vmvxX$2=(k#wU$%N}{&RNKX>pVWwg0(nTPT+gz=fUmW zi{-+?Ie2zGXBF0QR!S3Gx4<>RhcCnImDV;i$E@wF?JaZQn%T4FHn^Tc9egg=i1oUP zYon%l@T^k4+hDoXQoXKlUQ0i_hF*53@bDV)@G9zVsH3vVmd$rL{qwRCc*e6;rKpI$ zdMR@=HC$7JyIcU*Y3wD>h=XaAsa(gvw6Yy3C)1g~Q)Q4-r&9;dfp>D4WAMJuaP0}# zGc2y7r^_z1GX{QHe~N}}?=V!ri?K?u=^&e&Q7976`&u_r`>pcIu z4_y1ewNDqWgGR|qJg5GA?<9O0-3!9(VA0d!*eFFcN*{S6x*vUt}UNduT~kZ@oTeZ3tr4wD1YKw z&jdHR9oOpDHq-x8uB~|7@@wa_KyK&PU1!F1PQ}Wx`FoIKJ3TKwhuGmdRoUk6or!b? zEf=oyiNp22S4^H=3fJYt;#F{61J8A?y_mUWK6eb~9>MgJ?!!2v6sA?mxM~{D!Te^p z-r}k8H@P)jKQ?m|^9Q5k8GrV8BzUry53VDL$D{CT-ajA2c;eZf)X(s1xc&sDKTq(5 zZGZCXK<)(r@@0ZQ`@7=kcV)5%Bpt4kWN#@Qh^R{Gr7PEE1#n$XKf6RO=F+E6gXawPm1nXSH6vEe;N557`V{f`$@B=L z6Xf8(!}sy*NM^<3=@seMoJ!Myvhx`&{F|Azhh;oN#clm+%YCC-I zqW}0)!gTpGXq>#`1Ji!QVSc0JO=a2EYli9JoL@VP&yi5JsiWcN9}urU&z#b|lQ|<{ zVt=@1MfVt{`?9yFudEr*syw4LD!ASP|M&Bn25Pq&cdJj|jK9etHK*44H(Y1KwK8q; zYUP@9z><-fLFr}jY{lTzm^B86XA^&S;o9`Jh{es!sELoQ`c`KhTfG`-zuL*GomJy+ zBXVmezt+c29Nu;QOxf-li_ao=543CKLBtBG&NIeg+L}MJYU8>uTiH(NaVPshw0@>+ zQx}A5y`LG+?t7P+8@KjwD%;f1)UyZmWWLmn$CYasZyia`k@)**{CUgNp~O;J7at;< zrYPI4)HsI`kGsMAH!0}c1 zb=2Ox5U%04Q;T4Fv2--Dq8p71Z_yt-{-a?5Y|pb)%UUxBreVBR*Ew>dns~hmzN_%> zD)R3tYU-8nZP>1bYy7*6*u1O)reV7j6_?1>LYS`Lx`~Lw+O=|2Sa0yzMlV^s3KDK9_Acay9FV z#BHj#G-ikErCcrf7>T#^L^m;X1Z-(m)__*#PE~n;g zL{4qR-{jQPzIFaY@tEcvZwZ{ApGxuTkYnl2t zIjzmJ=G9umvii$6dscR>pM>Rqq$(j3i9hPrv-;27qdUi5g8_#CV=tG@BYvU*n&n`s8_;o2v zm!ZnRy^Lq8ch|Xc*6W0U@-*K3C?37RO_^2=qs|G}(_HcG;j(%9$eUbyje+km%$Saq zSAv)WkDDo<#(BxxyocV6gz>1E?7fHSSd|YmEdKPo0u0xm!}XVmA;jV+K1hDfPn3hq zaqJ^+{=9WC=BKa^6de zmvN@&8qP9XNlknKY|mxR%x1cA0cVU^YR8+eH7mo)GSX{wo$KojONhA_cj}LNn66i* zQ61NHx}q8tK3=1Y&xK=R^(y90tMKhgR6$L>9JY<{?sE2WmcTW-rYd4CrU1__VQ*PI z{kD2){Mp%ZAtec}*`t=8BInYQP@ z_b7QCy^3BTHhY=6%1hK#Uh*0)FDTd4P~Y+yfv=CGwhh(_`3w2%Za^}X`QDkp!Tgy=b~(UI+Olw8d9cH@vkIP`=(C~ z(^@ApO!xSQ@o9(aFvGN+R~w(iH#Po-FK2&-*@tQSw6fTBw#=%T-DFyQTA6mZ)*Sm* zuAQ9xKg|A3bDj!3>$!)z*URGCy055TJN0qxr<>VQr=N{y>pA0kZmIF?o?7$2i}*zU z{@mM?YumGP$+1nIJ)nn_DBA;jQXlWdzbPUP*BpBhvG^0j;aky0xPHKG7`1W4<4x`( z@a++BJ<8}|kI@#>USr9*$Fld9x$syo&eoVoERJXMK6*dW8{dZM7+<50;{4d}HH&yX z5Wa(m$3r+fD@?wQ3*pStC~9U|@*6W;drPw9*Mbb$Q=G$ogj~+U&6PvNd3qX3KE~GvM*IakNuX237(N@D!deTd&dBb%*e4{_=RErQy*DOX{E7P?+u4b)SgrGjNrQrwu_m6DPk6;kQvqjX5Dz)QQHL5P53so@D%osr>2o# zv+pzwu2b1#o{}tQQWE7_AGr3F-4VXB3%-92 zp9#|_9A6Iilx?9NM&E@@lWlOmGu~Ui!@uA09xHEgzgD*2q;~QeJikKD^~#KqoU1dE zxx^9pG<^_H+pppJZM=Ld^;Tti9lA$XWqKSu!}O}rh+gX5ogxlrztMyG8rBMU*1C6@ z)_`ET2&VP(F4T{7Hd>iBKCOLfnASPVB)mDn__X%4h+&b|zawBm<67m!%zwj-GUU|1 zEuXgQWZK(Orj5gx_CahcA-lTFK!nCdDIOx9R@7`+Q+8^4}9+;II6T&p%K*PeK_*Es6qXeRgh z@ab?3$I-A2*YEK@dOyk+wtd;N3)`^)oVfwlad3@Cf01Bu{Y^q7d-%iojEMw31EiSG z3n-S|#o4l_ARDgp;5r|!3sDiDSy4hfUP^7eRDLflXJ)bj!F6esoGh<}?K;@5VV^Ry zXeIS>p{9-a8}FXCiZlM#qI){~qa>vzB@AwakH|*44~;qt-P@uUVVdBCeZP z8U4|;N{pKCmjB(b@}?SBaJ^jqcil3WUkc|iUkmf-2E5;>g7+#|S5@QX6|?bddTHhK z)$sC;YU=RB@z<)Gan%0o%t@ zsFNkf;McLtr!ccajctEa5M0A_m>+EW!8J2Lu>Dh*%1eF>n;}1hddd$W9`arAbo`na zJ&ry_$aHzj6Rx>Wzs2wL4fuYQJnI!Nxb~zr2GcKjz%}c09>eLMxN;vDA#ZpS!;!bG z8|xx#k+Q8!uNiAGeK%aM9Nkw|!u1NYlD_Ji(d^rV>y@lJ>#Qu7z_r$a^z$Thk?uXV zn8vH|=`?EJ$;^K1XHmu*IdwA5`DgEhGT|_-T(3JWZ@4z~aeO)kruA&YIGB#3eiq;JF5}rI z&+gNWTwB@J`(Ageh6dM#cy=*d7Z2>ky|NdxSxS_{Xw)Jd3FH22QqUQ1lPgv zAHrt{gcFNL((j7mGqWQ(Q!83_=2QEwtB`}#jrJ7ea<+9opO39f7o$?RE+rN(<8uPa z>2p<5GpmB@D!8s;ze7DtH?R-6QO=e&!F2=Yi`2>a65{eIa%;T!YU|zX4xck+ps`T7~W=Mqi1R!?!ZN zjQSajSEDNObo^WyzfnPruAJICzTQ!d$j`6A_|=>;xtyGhSF>+Cjs2$ZeLfYgQ`lRc zl5TUYY$wAt-kg*m$CH?=O^lV}G3B-$==WaxQ6LafBBVlXQ-e2jCO>0%a0*m zvP~5_L%t93l<#1BJNMun`g;p-mp7-6l{aUMlh-{}W92pO-LHC%hUby;f-=qeBDvVJ z4-jN)>-3j-K(T~ zmP}4fy<7b#oi$Z+YLjDX9f`bJnI`6rV~v4low3$BnTfygI^)&&pT@bCS6k@QWGXQw^kluVYpNnjP<430q z=S)NV+Hh@o_5<+#;IxrwG@eb(%tP~Qjl(D4+Y?zQGXKx4Xso|{5aAkA^3D;2p zMjuAc;vAhIJUf&R@{d3S=K~=|N?> zxD2k#`24tXK1Z&K&x@>q?HahQgX?;u)1{5X;Z3N8GkIG%d%Fd$n>o|7SuPb(8!wqH zSIXycHVKT^E+_82n;3kxbiidt?Mmju?nc&ngKJAGS>d&M1$;V!+e%cS!lNsQ(JSD$ z0-r7?CNIOM%V4+!k1l4PcQJkNV&d^a_J0;I=TeAY^YdIQYLxa|=3CR*W0ssRS5nwp zn#LZ}RQ8&tFaw_q+vptreMZ?%N|jSdDG0U`Q{?x=BsrFVcc-T@pOPU5qnWi23V`PT z*%dO&=x5m8fq(xP?Pj^!*(!rG`##r^r_zzdEH~Iyx}?CaQzyLzsm3V zW$xiGaxZ@Yo}WiAxWhH?g%_!}yyG>R=XtM87=Tyzm$joUt!7o1V0rn-KC(<@ zxMsGL*Uul`+nkY|N8GJ@8g$Q+?s2fTpOn0T4$_v9#i*7 zX$?y2QOdN#bs*lTJS*3FUV^UH{%ZUhuK(K8(s|0E0o$V&q zdauwpysKZE`-|4^RQxPm>u2HS?{XWiyTdh}?Zo2Bb%uI2OlKk|9xt%DE`#d|xUR&n zmF?PL%oR*xri@&A!$h4m7>-wuARZsVd?`#%!LJS1W687eYL9WumX2q3d?I_c$8!#` zyL^m4e+J9%h551vAFd;9uA^q**%sHI!u4mja2*TVahzKnFWXa6=v~8bZJq4HpZDg& zbx|p7SHN{STvx$$4P4j4bsbzQ(+$+j8Z4&En(5;z*R$n(IkRYGZE~>`!Sv-~de=qt zu#55UV(Q{W^tp=YZxt_~CT*?Pix$8jDuGSb8-?_{(2WA>Vo28;`S9wffY>^pJUW+N z7rLGUx7pOo(Dh8>>`Zd%OzPnol}6cy^kc%*>Xfu)Z%W)aE)6rY-#( z;x9kJ_4YuxHu8~e@cnI&dbYRhAP4^*4}X8Ao4m&F^9_EV>esJMqaHeK3{1oIG`NQ6 z=ee&x$NCKS{TE^T-5I0h8REEgW8r#?&Gaak9_7->^>VmYmOHtIZGG)JxNo7Zqq(%s zMr!>h53aRNrt=q>sI&g9c{TB90=ab@T*tt*#@@=d#kE}nx4G8%ThBC5zqZaM(%O}s zS37kwlV4jtZF#gagYutq?c~>{CJyIab8&}ly;msHPCRa6ay!pq27`ZjEr48*H#woBEwhfv=b$DYh7 zFbvcGg7FRL0XO(|Q?^HuYmXt<;_rLMdN}vJI9~>_kQHr zfz;3f4bvY+hZ3uYF`EY0v5~~$(VS5oNBt}guH!kcG?DYG6F3(W@2RMf-)bA-xm@<; zm&gHXOot0$x~SIZXi=T~UQ~}7qXSVi&c6J-vz7%JZAl)053*{?M}w2f#<<_E_~;} zceaX}nJQD+re+4)9T_OyV!I<9w$t!w_JUrk9i{J z!LXiA$dXeD8FCUGk583jaD9S2{A5O+9E@PbKA2iK`W5{WM4usOmh8a4cLe#%_F4Y& z!z@4KWAtqR@p!Ng{jUJ|dX}fW<2gxQcPIXyJ^`N|FE8WQFS(DA7x;avo}V&Wo-=}N z-XAaVUU(ZXf0CSS6)dkFHK0?<^@@?4nJ}`S(b5sb;KTdMVzhKP{V( z9^R9SvZ?cGtygQly5~WC+RT1ytqjG(v7YxAjiPKR1L1f5<1ap~ePSoCR+et|so_oI z?=D_#aeXVJU2A1d&8+LHT&s_F<=VN{?`N@X<=KXB{(I`%`m^XgrYqN)Z!6nYZQQy? znY~`*+RAl|_P)rqVRCJqL(6y3SiCp&Gin!uEtL-GLoeHf zIUx3K@w@xiL|3B+Cga&?6MnsE%2?*n#<4eK9G*>X?P0~>k9xV$-=55wRm`4|OMf06 z%>LY2cy<6>2a#t76OV_GxrVfs-7f~Bm6%uRPx=~1-ypXyW`Xe9S^YQaM zd_0#LS&oY999Yk$kFDOFMQsf4zNY@Id}pxlB%M7dsq7U^g>TrtoI=eknSG^6>={pj z>qKU464+Os0NV+i4U>>7r{c3wrksppK0Y>8jwix+IqPw2;40rEqDFY=M^SikYdzk_{cXLx{o>N8#5aGwCz}lc4hH337t9Y#atUu46I82(=X0|KG>eEgf?mT}xt{u<5$u|G3zggaWJFYFht)JgI zzm)rw-mALCJepcQ8xr1&nqayGwZipmIl+38IQtYVE6-=~V%2$k_+rUo&f!L^mtplv$x@hJ zX4KAlt(0851QjpGvsb|O3b|24{mW4yoEM#71akgNP&iD7p-|b0Xa5usDBJx5Sx6oj}PoiOx}-PVqZS*XP`VZiJ4JY-Y{VV^MT6sXmaf_Mw_^YZ{l~q z*MuPJ<1-*oox1<{+1;9RU=c{>!pjvxk)iXc9R>&Qs?)aLq& z*m&ahMEM$yzl&3*(>WJ2Q?|vY%XS{`$}Hk@_~4h>kKgd*!+G^`1pQXf2;0i_Y&lNO zd;*;$Up_^?d6)$C<)KcnaFb%_3VY!|4TFDBSUTLX_x*58` z<39?ilNDIH!D^Hb+xg12&37Jr=fZa`b+a58&qi6q>REVpHZeNt$fE9@L4PX^zSH13 zjeTTk>=8|c?G*Ndr&Lont0E4E?L_vF!}a-uQkb^bJ{w;sXJGqOT%MeW&4Ov-aboo| z>CETEq|09a2-zDDDSKu`%CGSK%d9ZjIV)6to)s)V`UlB2!!>^GA0Xeu_cwmT;xN7g z?!Wb=#yQOmt~CyyEU!+T1k)4b1-A+K@;Lebm^-WZs;+f!zt`vM-YqU6E<}hY#9bin z3BfhELxmzOEmn7-Zqz6>O5IBZD!3JBDK3RVsqB4jzULitj$|f4_darQF8;qU*GdAB z^{oGEW6YQMo3BPMB&vwJ6R4dfF^@+6{UW*9U6FyZqHh3R9U!;B_6itZhL-l_EEKp_ zo^N1PEvDzP1l<|lM;2129Yr5jWA9OT^l(JI+vj{l{z!6uWR+{YNS4G zds!rNf`1OBFK8 z*7!t!ZaY6--yP33TjRu7z@7+*R_^*sJ7F z3tYF%hwBA!y$G(aljAMdbGDx{eS^_ym_9Rp8C)-eYvxN~`5be?r*HWt0vsy zXgoFX@${_GKU?TsjdS#mvDC+nV16u&8^QQ!_#Q=Fd^C0OQSdzizDMBQ%JxY0ZR&9& zV0#$*ONOYfg=yGshV8*u!u4$VwF$Ndu~)oFxt<2sQ{bA}xq8(E{2GSq;JOB`t27?x zjF_QQ<#63-*&wc`1=NJOQSSqU0G~4{PB2ZE7n|#`Tq(VfyB9 zWtue%uEXFOEe(Tf>adPa!*mE|D1`KurFix&Fn%R*;&3?D9<|A-VOeWIIxpGGYqm-2 zL@-@PEm&XADt&EN8WE?`7qUIAf0t|ZXd^fO{hx5H%q!Pk+4lzfUU5Mzu3YnfJM*Q^ zoT+c9dA8R1EBKj}ZJTE|z;y%l@ka9=*bU$APCVWnwu#4)diFTD z9_QZ+-}YfPfSd=l_>*V%<;)cRevc$X(#MFvvtgXS@q=h(lH=Da@#}|E5;(&&k^BGj z=U$vC++Q~hwo~P)^bF3|U=F+nhT-|0l2U5q<#1gkA63-i*){TMb-k>wYT_LFL9pG- zy`hHU+3M9JWK-j4+1xaS^E}4LR^rc{)Qo?C<-=1KbC&KBYT*mH57jm7OC$~-w-Bzc zmt*jJoSgS0`R^%LKNB~ff#YA$SvWq2eudZbaC@HhcQ|$g*DYw=E!4=EOKDNTx2u1S zWkv=4b1c1T^v^M9G&3rrm{A#pr;j3T9|_-vYh{}pe7N#WJ#P3SYG$xKL`A=PFndIs znL~l?^G$Q)*G6W-8)wV82F`XtXByZ?UJu)K4%;W|Cd!FwV(zLj#Nw0W6tVlUhVk;l zfJWJy&pC0$bue8ky9;aJx)QF-WgARy%`cTrxh3*#ZZR4lU$bt^B@Rbhiw5A?h4NPa zG+3s`l?2;~iSlYxU+H}Ue7{la8fC`^Y1$W5#( zLj7gA%kS#`1UE>V1f#yGf2tUM5HAK^+;5XOD($a@`ZuwoVx>WnD@f4%7JYFYtR7j?cDOu9-0%e=D<` zx5Dym@`nnR{}^{WTr+FdqPoLfZ--@OP>}NdpQGX12*xejBbYlIuEMv`Ka7achcTx* zgneRzRq#D{zWg=_HR0P$*T6MwH?q%E*{+`@XX|FlnL5shsO8MnT4rKvC&`Jb@p8PH zJiB2kvntHr!2D0e>`Tw9m3@T`vbU%Kra7CUuvT^yFyE733Df1W1-8G-Ersb)`3Al> zWEZ1C*_=N>wiK4grbF-N1T1Trc3YwHU5%=401U#~DxD zXnHmJdhFSa`%`J17oK^|n)sX7vW>shr)#JUJAG=WZfwrw?!dIhwc6Lxd2D;8x2~p6 z2FI2yyQeybnKH|^seijnE31}UT`l9@=j?Sy-?pA@SNreV|65lxhsO8i)X^NSjc4=o z*!^Cmnr9@P8z*x1#{|yAo+XFqH=USvEBBMUg?ia?IW}$yf_LiEC#hkb8Uxql#HY!Te}U&; z@ZGcM9M`|Ho`>P{u=^YOecT=JjNtf>mOIV$pFIA@IOfk>{pXlFL3daw%LHe9psyGb=4rmuzRd2+siJ>m^l%Gvt4aLxQ{ zEpxC{)8u5;6ofz5PN9!IOHPrKAFrDt2aAWwkNNEJDBw()LiVe}_U;0>E`aNN%XNip z$*q9vGWj;AOuosnTo)6gmmB|nJ1IlnNKBD8T()0r>=D2YN&iwJp#|huWLrLFL)g1>Wzcz z@$5^O#94cAjds^hl|A^@UUJa`^o@>C13NzbE;%{<9_CSQqh_{TPExNvNqy@iIq=V8 zmU9O83htY6v(Y*H_8h)@-slebZS0-$JNjcBYGH0%HHPca%#V(RYaSc6dHkQF@$FH! z6K^A3N5S`Syn8q?`3UlF)iCn$Vbt7)EtfxsEP-YAi4E3OxhB_cqG#T?u$Ap!8|KNm zI%eVO;2NfXshurns^Qv*^V;f|g>7ceX3*7m_GCF!JX{VG43ZxUIgbMEh3h@}O|mPm zUUufz$ab_fuS&M$!F6_}Y|5#C>2mohs{~O;gLC!r^%?oHE+JLkK(8mJ;?*hgYJ9T1 zoRCE9oe0bHEaT$k1?4(AMxKj_f$M&MVxKwCQDO&qwoV zYGmbjn)0l%s=oehP1(fWZcoe3eVf>uoHkblLl@M&;m#X-+xS~~R!+TK+w1hp{+es^ z{^0WLW!(F|(vE9lam^Q$Yr{4_la0qqh{a1a9yhh|PO#ktw!4sPcj5Qb#&v_sbu(NK zCf7E3wogxLl6ZEX-kibU!+BDDh{yf#Y}khJ*`Yzy&Z4QI#jyu38pSwl6RSO(fL|vi zBDn6KDvzh6vCoqjJT*g}NzYVuLx&bw^jJ~7Jm;5M!+;ktU1 zd`*4myIQ!eX^}0p6CAGLc_+E&o+i{dUA|YgdAy$-^&qw9WBBr^DR;`x)9#bgQ}1UU z` zJBk)l=QA2}k^6YWEua4($Gnv29c>X5i z1IU*mIU@ysewg>^yJKVMY2(?-HSg;WCnn?B$~Coc%XJD|r@=KmKbM&g<9VDbnuHAT@0(`f z*Ry3`BdVJvKh!g;f~OrBPJRu)KTo(DuJ4sIQ|_0ulON#B-Uq0I-%l)lFR}Q&oH2B_ zoF~3M568c$c>Md=yWtv^$KHePMfbt8ipS`mWA24(GzyKl8^QSqI3EG$!&!$Dw+|yH zA4aWB*&ae&eeg{%y~42lCw%|ed?Q>l-)21fI=E(@74JTWeuZ;g&o(j_*Tjrm(?U5@ ze~p~1WM9jGv2wUzm>ewN%*H~_m?#`U91hocaGf_u_T;HwH{#a~vOT*Fu4~}B+Hn1C zR)uWLAP&zg$H#dNFaIbdPu9l4biA^iE{1C!zZ!>MTdrf^+GYAVxPGP|{p{E{S%-%| z5gCbB2g|bHAi0HAxn71}FX1(FgO_Wr7m?2`fa}Hh#ZtJQ&-JKYJ;)8UR;GD1F}Ul~ z4RBqLj9k7EGb#Hqg4a>Fd8<{z5<9|+0ZED}HUprpicK)FCQZJYG+Iq8n ztgCqrrqeI`598&QYkl7Pd3vpUJO93o^_|t$$J_a};o7Z{ zer?$9Y+`c9v%6A9Q?9#GlW=13W`4JW(NMS^4%egLdNf>*^~1A?!6)#3I}x?O^-TVD zcSKVQ#h>pdF1v@n?*s98bsROb_#}KgSsqP>>;CE7mot-ler0jqBtD&*$Guwe<@wA) zS(TnIFR*?vu#WTcM#1(l&YmB{y(OAC2XmNwQ9eSxtQaL<(Z~6^dJLa4j#zvG=WtDe z?J0OReqBF}b8}}BhtEQ@xu?Zk&cMG?er&i(eyqPz4&ZZ#$W@OHxsEfymUEu|oz%ha zgX;(7*U1mDKFnDpk8qCI!}9B-hs^r>q(?Zn;pN@^uD#<E4Wtw8i;H@t*gU<)2CLCE@19NWnyrc=@@_8oY(B>a2agnmdBRqj;rx$ z!}b4@OjhsLc)4!%>{hld<88UNt)Y4Oww~?0M`?b+&uL>4!FAmX@@tsJpZC-7WVmjD>v3p|)gxS^(JRq|hV6gA_diCV5pX^H9=w|v zU>N!OFk<~7)aVAo^&y~{yr_0I0iE=!Dyc{c_w}p-r zj+4XaU_NI-!TFE*BdLXRRupW1k9Ow{mR&iV>6+an+p-#DOJ<#H&Z?E~vT9^wdWCGt zgz20H!}xm%IpoKg@^*YSUJci=a2@M#{c<#1N5geATt^d&N8#B~@i0x#3&!7u>t`aP z;W(6BIz(<(rh}L_3ks4Y0RgZZKnxxr*J&qI2vXH%o>1KVhfAH6Qvp2&Odef#985- zc|*@=p3f+h)mg=yoimWLHU`Mc z+3fuo&`b_J)_C)$rNg+7<8VBCv}~vtD;q0XID2aXTu+4S$$0h@*;+GAcGS+~JZzYz zzVtn{<9+p4$$nzhA7J_?!}J1Xr5Cb)k)9Vij!rZzk`u(Xr-*+~RNfr@!TLv&ihZ0$dQDu7{!}%h24d_6>SGP$(hcO%_0-4eS?gG9h__os zt*naup;hG6mFyL*$Nc%@@h;vKAa|NqEq3zzpRde>nPBLwj_m)~&HO zIU%bu?c~%B)74(C@oDY#Xnv_OwK5OW>TQN+!?f0!omtI}OeI zjyo#zZ%#bk#(w*F+_G&~FWX+O^;*AQ6Kn8u==+qOb!epTVftQX_;$E1>)e_CeP?=K z`rg--_rR|Bb~objZpO14d-PxifLOd|PvUXdhU*dP*=V${FY_vRwx2(q9SGaO?EMPj zej&kfXTMnD@&2%#B=^Q7a_&WcxMqH|BiG4r-9KNRO@V8CTK#$rHQ6;;rJSWx!Z{q} zoIgK^bMx@!>WT6tOs_8)Azzh_mW|~toRv8q&z>Zk==E%_o+ewXr^EFO!}V_JOW)UC zNp5{LT+f5+Yvn-WeEEs~(LwUmBXE3_9QHUm(MZnSc%z(TJ>7t~KGV3EJoW}e>d0hyF_L;XTt62<&kMcN zFIk=ri-F}Zxg{_}Zc(lS;2JGKHyEY^wSHae=X34-cf9|P{XTSczBi{n@8#OrtL@C8 z8NN+y;;^kb_CS~}LFN38%K2TH`Z#Mfs#UhT@t)b8nj~B|!?kK~@7~1YK2ALD<4Zga z+rGr%e#&(ay{}*x50a~bIA4L9%Cq!E9wrC7A3Yq$>}Vo$W=Tln@T6?^gXQAcdF0rI z@_cF$=aUbR)tRN7dsk+dekGGNvqC;7WUoi%1Y+%}oM$~=zA70lUzLqD&pBLAmCaSt zWoy+;&dbHGYi7&tnkzXQ`zr2dpAGe*9~xuKOZ4SJ=(xr8amzp(+x}DdI`NE zW=aP!e}>Ky1FO#Ay}vf$)$N35H{#D8{n?=YOfAevuU(&p`9JHB@?Hb))$9SQqPK;9 zt6D7QtJn`(#lFxA_%3sFwwyCk%jU_MlB>{_u&wd;Y`C5&CyTh3VBs{pnzI-SIOnNg zvK-E{T(`hA=R)PcbvEZjp*>k6WLMTO*_pvCea2wfn${$nVR~~0{p=i`!^^)+E|+&> z3*_B6Rld9h-)~0e$Q#jF@_N4vc?G>}xK3;3dNo`>3(qg&*Kfe|%RIg>n0i?N`!WN< z&uS9U*#`kmJY+i~3COe5v(Qamc7nTF@K*xMU_w{h)w z_@(|Geg0BhJ99A?=UQWNXHHIQ2bUYEXKP(ypozuXxYk&_lHZ%w(A2YQmFsSB-4oA- zX_e)AxM91E>lR<)aQ`6YOM{t3!?(GT2XR&@I@idY z3ObK|Bkul9S9rdF>w1{BBL3DG{LgwQN(9q(Q>o#YG%R%`^ruJ)Xw}vWT`(fIPd*8X#5S+%Jo8*>uY?; zvEg|sx%F(~!9iNz=}O&Jx#sn#UTwKnudaq`^=sw2tOM7@<~6Rb^){xJZROd@a&5iZ z`gPWSi)*L$?e%G8+H!4K|L<`9SAOmE-kqQELawhceqGA%stg&fVOqJ?JiEr_x&gKu zU9N}V*F#}?Xm1~8Rp>?VH&MTC;cqs=Vy>)8hU@-t-QV@=RJcyVr>W7to>|S=cr~(?b!|qCyqh~nHk6H*t>n;K zYi7!K<@k5m6wc^@>x${@|5vVO$*wBpdamQwHP>)&3%IVuuj`l>uXDMsgX?l^TE z=EYsVZpSsgdlnh4SP8a!p>1SI_Iij5v%h@b{-))(fV|rD57~-Bts~ zZ+M+n!L`Pi9l3VmZ}l#nCv*JF`n9Q%X`RgD*B$qBT#Reu%Xq4dp_NZ7hu!}$JuS<% zcfFA93*&L`ziWNl8;jd(%Xn)%t~rH~vaNM-r#4=MUpsZQPL6NeSls2i3a+bp&unE| zV04YZ8CU>AZK}PQ7$>BC}-) z8TfW8IXCt2_$=6lYy5guQh_`R*U$AYhV22Ip+kP1TEr-&e_Yym@okG|teRB3sI*aSrxu*;#cZY+ot6tBJ$mdSA`8vY%S= zk2UP`s%0-{?e&~FaJ?KM_dQ1LtU5uSd=mYP$No$_eHxvqXJ#CogVS?xtol`XZlGSR zs;6G2;@YSV&g;0YWv%h3np&EwioKpy?D?!DKd*%EO5*Sem{!5G>Kr;-LXWy+f&4O% zvs{Wff35gx`MC(cF5&TxW$8x93;hZUQC}*-9%;C(&9QM9swaEUg zak4LKtbCt2N_J(8kR545WqWF~?9Lc2`*Oy~uFR3LF_FDBF$3kJSk9h`DUlCi2FQCb z{!TRK*hJ;Yx_&vb7QG&sEw4lnhexE7N2kf^@KkvroLDSendXdAIDarWn%FyBZViY) z;c~MdUhT(hxGyod;W}8Z?*r3D0W!ajzg&YBpykBqGx3RLyri)!{ViU5O{f8e>rgGy z_`6a??pR@bT78LpvXjcms|V<79Dg>mCot^oXIZ8#E8f-H&$4`ZnYJw3HACmg+}zpg z&-U7`+Q)XJOgmibzhQZ{t3GG{U+?#2uP@;G^2^~`r8P8{X=R(=hZB!$o=pu6uFG7u zD`EQ5T=$}e2G{uYF#ZlB`}lH>1zh*BTsxjUjrZ*7{(XtZLuGC-`?JZtUP;Q4r|Frj zj7^ir=%GAGt<~{s57)^9<;DJGa9z$>coh!U^k3efPWx7NBWGJS$=j^&QRDfPKF&9# zQ@FR(RkFKw9`}}-%UQX^;%Ixt9GB~>jaTo3>HRee;Chk6HFc}Qb&KKpMm&2ddnA^! z2a1_e^fMf*PNOq*aE;E@b>x~Dyn#M*J*s0L2&*HQhU?#J5nQ*bie9wkx)Qc4E!&*M z0@J^hb3O|^tJ=6;2-gc>nzI$*`ZQdhE_Auh_i{Z0uBREUf6AFi49+>s9Sk&1oIQ?O+IZq|X7&;}vjUbsjp2;Q*iysy`_aYnZZzlD^n>e2xQ=qUj#94i>+lR& z6P^y!sj{jsXJbUBQ7cQAXG0U^R)6I=LT*R5_z{Qu!nLn*9Rk-O_;rZk`Z``Ki(vU$ zA98CSdS3X!_4vZr9=)Z$3;k8)x{Gp6Jnk~x#x?aaUauASlIGTyYxU{@#NY#Xty`|0 zxZCMx>GgkwYs;@?(EB*^Z?^XB)Wj^q9aqP@+ql-pnYcTN3Syxu-aqU3JvUH4YgCzB zyEk)veK+!;=f-x#S}J+U2@GT&KBQr^7Y9)^%Bp@)kXscd7BbkKW5_mXC9W z%U1&?kXO%z?Q7|6A@b>+RdZ#h`gR4eIK861Rr6(Ewaax4evJ;{)raBw2=(h@=s5ZB zN%S+Zv+6WbrhmbMe}UC=^?0=rd%W=I^9{_H*0U!Bu79tEYn3wYK5k`N`Btut*u!0^ zQnvBz@}=@y8D}k_U&|K5^bOR&uak2F@#}%ild+yI=4?gS{<(@=0`weAsWGycf+Lc$e$9;QEcoTv-d-uWKCca=kh%O`Z!Q?ha3r_o9^b6uCD5 zul7@>BMsNL!u3tQePKFOmcsRo=z64HeH~f|+tpPQIccJgp`JAl^+edPl3D4!+)!b&bJW=SJ*<@N9!c_lnc*7Qx6=fmi8^;M>`Wi4z!92hIND${2`+DlFi+RnA?%GQl@#;Y?*A1PSK|!^=CN=)noVv0T zuT5UJs!~*f2D&UO&(8g9d41EnvimQMPn%wr!-TDowKDCAf0ZZ8wRbiAnYi2WX)niK zex2tXe{SQrBj3uk_Ovhb?f(|n#-rJWmYRI$UdTcA#p)y518>VLwm(4^osHH3jjpYoxEO~<(*^BXc@a2$Gy7q<=D8!(qmR&QI!cZDVqE_W*JoV6J`2<5>Thr3T6w-0*VR1# z?{Hnl99ajh&kcm@0TMXt?T-jD!iIABE4Kk!gVBEM|rsJ3)f*;@=_T2 zHGcheM3%fCnIlgIC&6=++=1@&>nC?yifbR2Yo8!l0N2;R^}S^ob!Tr1C3?fv;*aqa!M!@A|#nOFHM&;D0_t)8uN`q^z< z7xI3m(mJ{3+UnJ+QuS-%AM4ka>sq*OK#e`rw|l`gb#eYC!-=6Z79UMaH4d&@`q1Yh zmzqLutvU7#@~+u|kuoPJl6!PU!gjno#k|8?$;_Z66md>Yo;(xJTuR~qc|HlhPQtH~ z@N3I;YONTq(;MVXdNXgs^}B{^_39DwDf1WWRXL;NiyY?An9cZ(e(sJEW=P?9f6Wr= z;49=O`b`JwmQgcXA_wv6pDM4HgXl2*rK43#9JZ-hpRC5KRe1K#H8;y?^b4`{S^W2G z4Q!+H#N6jue^YU7R=qYnyQ}Nfzf}{5SK`$b)~i>@Z>8J^tc0F;DKjo5cy=kXZKce> z3}hCjn7UaJUR}hVjw0^WjaUDihhOKzbv|6@;@3I&bq;5vXU&jJIKW8WSN3%xD?Y>cRo3G~A znp<;D3URpg>&5u>BHsJ2Me~u?)6~0%>+8yRH9b{4q!Fg;T-7Mk__WG$ZM|CkT7BB8 z$TAJjdF0#nEF+D-ZQZG(U)z}1u3n$E*KPT9`~H>V&uyGrzqa)>FVo&@<=Z@VJ^QcN zw%?bNZ+p)!cbGO@x8~X8AgxTdaXrxF*sf>8x3XPnVsXc>Te=oGk~j%Uv%@0uGJ1>gOsl}E|K5Y7{iVFnqFU&W_a!S$;6Le9J+ zzcyTxUnk+$DOK`1^Am5R;@4>o*YD7OeV6%)59r%`j8A`x*5{0tFPNX)0Mp9#2I@oK zWVf(~nElwr%$=1jk{_yXpk}s=-t|p#nEdz%dGHa|qtv>O)gm}PPJj8NvQ5qU6go}) z`ZT%l8FJ!ZD&ZTQRS{GF3ajT;T(_^ZUf|TcyD>}sBf1_^1 zo~%L`R{yd!vbJ8etvxYYM!apMOnGHlwlVQvF>PaQhv$y*w#%-YXJ3?S{g{Vs%eVc0 zTA6lp1t)j#vaRn|MZkv6XbFF zqi-jdab`}bJRe^~ue(@YaJhb2x$Y0wDati=Uj!S7*e+{cKkk2Tyy|N2hq;ddCF zs$+k4y`$50cM*r*McwRf&NEQmMQxltA+`75+4thr_aOFjs(Ai4eXg3d`fl=Q75h7@ zxYrl!`ASrACn7hmpzc=g=+`pdBT9L%fcLWlnQ=qE3|NYoWrg>j3z=It;#}l{MdaAb zvKK6rllh#NT)@_=ug+~XS~_u<#d z^xb~=wJ-av@oR_czHl7|*J1Dr*S#ICuZ8JFXa#ZkHLQcXxL!>hK1gffu-t$w*N#`y z*XqRllwsQ8TJvhHeH*XlbzKP81#qo-wT-i9dG8~QU6+k~*WAPB`XQ48gn#nY3 zqci;QY-%fW1NzC;K`|&6|BjYLu>FW(yPUYZoHHy-LF&& z*uUBMuq*a_ajp7YRj+!8{uioUY4ls&N@Dc~5qrdp?t^{a1FG(Yao!^;cwZ>LlUSU+ zWM#KAKT6CG?`MhC&kQ797{GZh#Y?G)-^gAM&QmSmY}Fk6IfMI}q*E75ZIL}m^r_=V z$_`ZmGbjneWNX|I*%UvR+`3u5jw2RVuA||)pUZU=ad;G5M=*yU&Y6+n_;omOxH27{ zk5}i)tLPm(`cu684Xz&!VCLVSJ-Ght?_kwfTzy+(@LT#s%FXDeKH;*gcepI+4cGYe zVmMyVJ4hDxVpa(+zk%n*cke@OOnX|?Y?N=V>+$NEPH>HMt-ONQ;T7~?k$Q9quiFw{ zzn1ADUeASpyF&8eT6xyqkM(F>?Q37X&i1J7JvfwW%d&TMSZb@0SynqTd9lY{zMZdY zxmgW7(cwx2EcVqEikvc6qxYT`OGPM(cQ;ku09bER?( z)9tw?$EM#nh}xOMwO=dOWBB`xBhFH`jb~FAoetNt;Cc>RUx`;=1J_2>QWwJZJ@ipt zC8vHrh553i3VAWHlvx&X>_leHl6g+K?%ybHrZhQzojz3F&wy)U@iwleesq1*AlOY!C~{)ff|1=lXJyZ zoKs$mCl?Zfqci#JCCk4KX$+1x=W&mr+-v1{4)-0(ChpE8r%tE#olY*D#=QmmQ|nG< z{siqy9xHni;X09Cb;3y59zR01#t*}@$*tqmtGSkq=q z;CdjjHn}xizm8YGP7MA5x%T?VfiRvUj|L>eGJEv=6XgLv_Tl<+h6cWUmoHrR@o?=E z0oQ#E*UIoBjlp|!b_(mwJU_F0fDGzPtp=ZNMopcG1C?vMx<=z~m{xV<+W0i=8lNTx zErMyowd>RQrk2#UpSm@#c4Jw~hh00e_I?$=UXxJH`L@iECOcfOgX_0b>0P^An;hHq>(A-Y zeu2J%?Txt;Ia?FI&V}pTsj@W}uHkv7vJKOFV0tgTqJ0IbIkKPL(E)1B2hkyF&#J@d zSRrvPIzj#FBsxVs%MtwHo2QGeqqfBv2ZfG)DWH~Zbe)_n;H(pLj@bDZIMtl_7g#+5 zt6rVXrZ26^B!|wt5;6asNsl^%GZWHh$&obf^P4tJ4ySP6-~P;$rEm{mnEoM|8589? zsYSj|g6pI)#NcD_>QS;SZa7{&Og6=8&CKC?WAq@rx(p@xWwxARs{=3`mrfhU++o>ppPZ$K~1wzeX#1N5F6! z*9&+}+|VmTZsoNwkLwX#e5j8p)9fdL>&8yXG_w+}PbY-85@ajzG9S@vFE`1x*) zT>oF!v;Q}_);SgBy4d)2*T3XidtQxQbw=MunoT_J$N6%^;&45JzuRcnvz6;f{4J*t zi%+M&IZL_5udgC@n->7jc=e*-L|G7&AoBwg;0*nrMg_h_WP+r<%9GQ@=?Yp`6OeEe8wDx<@#&puD;2cjAt`D?%{g7@$6Zg9fDsQ zuGz=Qtj7=d^#S_C2hky7T-9NCK3Yf)i%w9RR<=*E8o}z%FngNyOgq6hT%X}%hGS(J zoq_8!d91k(*Qa6obPmc^Ib5I0a=1R>GTq8`DqN>9QxsPbl zc!%pmxQ>VGc$e!~<(hjPp>JXa%h&yyWJ5oP>-AB!FwL3maQ!h{e-K_S@4@w3VFP4c z823QLvp8c_J`H9)e|!!Bs2$6Y%SQ&Gn7M;aV?StUk@_;6~!{v0eJW zwDQb8A`^pOn(K17E;Yh$EZ6GS__XC(&lAzy+Qj1jCf8nm^xBcb^ItLjU*Xz*u6Ok^ zZLjTnfnncMN9*{W)B2ut!TZ+bPW?>Z*IcjG)y$x=7E_mS#P6_ES7LF8?Q-5nEAVUe zY^|SZU0ml>8sNGK&mKgc)ZD|5z3`f2!!=AR*JJVP@zg;l^7ovKUr+O;eg@mK@#`!7 zW94eNo`+9sj=eY}nfp8>$pRR^F@*hJQRH_?HF!7u>STC^^Vjg~wZ!6Yr!>oZDR7-a z9G*6edr^#(PnfGRTxa0dnfNucRo`S2i^KM&oT;*fUaorfcJ=H$_JzRp_jvVQe0m=q z{R6$>1Js^>LWihD9Y#ms`4}}S)p30KB;I@i>3R~6{uz%xZ5XyJ>$=z#*TmHAln2|n z$~JMfDu)?Sxc)htJ{CHe&Aof^>=PN(yfZle8y!ohN1e+3yHn_2rP9+^q?Q$JELbky5X5~6&h3Ol+2UU8+4NyQ2zm7sUMJ0+skx%TU3AqL-0^CgQ)^ybbp^c`RE|n7hwaPR$G~fJ zfciDBU&FP@t$8gQrrWq~^=h8aapm>vHjXXNx;puE$N2kFOj;H#n;k!|qd(iv_pbJ9 zz0cVha_iULRljeC?Y7+7#^G8QH??tAt)Dq_Xb#)XelKNPxmIbO?c~_y)GC$jYIxQe z6=k~~t{XKLhwDN7y@sG+$~JXQjm5{1PqpwqJ%PXHq&|I##ltzbDvCV2pUg$-+1CW( z*@5w#37;f41}Dpskp7%Mo5GsP{h4w&?~;22BsanDV0dnlb;;z|JbpW65NtDZmIBwQ zF4r0OHTqK7X3pyCEVyQ_auah{o3qKWVS5{F?_gF#+1`^!|BJrSUiw7)^RMRIpn04h z#5qa%_%>3$k5G3$ijLvQ$CYn%0xv!R&!^DO`L6UDo#wGsJEn8FXQvVKW4ZLCv*#O~ zVm*~bZyKGzJW-c9)WKm|^XXfC`pVoMfijf%dvF)_1j2PQp53H5HZgc>{Cx#^HL^^X!E_l+YYaX> zg-;h#BP-(dYvS+A;Q2Bq{x)-!*2~~IyPdoYw_Q8jw8h?*HSg->&ilN*_I_Ni_4$sg zea`#27yEk4wKD8wTCXp~bvC~XRqJdiTzh@n%%>R7?#vARzr!_q>bm;hGxDT-K}Ti(ErJRrBlxaJ?9=mon?H45pW&<>+>LrYp6#l8C>M zQ-7X59G@N{Z}xANclvV{aeq8Jb%cDJPE8!9KZofr&<18Gzs`hfW+;{GO>n*WVqDV; z-b;_j;Tq3&xjw{t2=6|ub!X+8I9k`^@O=UwJ_*A{7jUh0FPH5+u5%ro!K+W_5W8m+ z$Gc1`+pSz@z;(LA_0e=@$qdsD*G>!$*TmsJB~OFtsq#b86y)Wa{aF{|n*7?!^%s$r z>l$Kk&Swwjo`-$Ql(wf=GF9K)UT~q zE7SP1a$TgoYF@_$$auA`?(4cWw{G=mhi{utcjU$RwDy|ZoZ7NxIkb;0kKV`j+OGC< z+L+c__=S16caN9%YubME_?X{QfEPlo;`##3cuYPT2(5zpx7NA1<2v)_^DXd?Y zQadXnRw}2yQAs{jg{qO6Q_)^FId)Sw-DAh!jX8s%>68G~-W6y9p z`84Z?aQ&WPo4U9gi+|2+^p|Kurt8_6bb{73EX0W#95RWsLwJV1`+RSb3L56K+ zJNBa=(ZO8m<9Tq+Y{+41T}S8@A0IBn>BLF0 zH(`Q&pFj*A-y%EX$I6b_F|s{&G+sSYw!{pVP0_<(ntPey)f>pIzl!3X3vj(YqK+6_ zJ((HOh#H=+la1(8;_-E%?7a!dl4tzWWEDI=HW`e*G|9uY~Id@aubw zS4Ybo#NfAT9Ns%pmiLOlr`gX%?0q+^U*98C#&l*@v=e9T5PJ{N+`1E9jT$;J6M?)bD*FVl0t+kE=c^C!xOk>S~?M|-*I zc;>W7FVv#&kEaK&(4MATyhp0i(963 zEmE$jSCp7MyBjsMZg5R34$qctXZ}q0!4r?e^dL02o8_9iIBbvYNh}W61&tUo!jmOdYRA$e3PCa`gb64NM^rj5XKV#hj*ITn^!uBlL z3D>)_*#`#K-{aZ)a^M;%*9YMGCu&=VU|MxJ53b3HRm%1W))O#%Li#w4CKh*derawcUN7Ty(82=j9zl`L5Nqy-% z1TmxUpDoYA@^k)~tn8V`ub)t^)vuLnAGr2${n|&ljx#a%9r*RFnpea0(w^+S=K5aZ zv1RZ*v5V%^#NNiMsg=R=Ak>KJuV7D+jl(-|T@KeJms1D-8}Xx!ztyXeVOqI%y;|8; zrfoj0YipeeF7(>QwR(P$BPXY}9Cl>P`@H4Nu3l#C=XqEAZ`sdpzy4q2n)lHTT-U+0 z=Gp4k%5^hb57F8g8g4v0SjND$=GhaqZlYZCzOG!)hU>X5*YoI~EP(6l=#Sn&KV?Z! zf8y|T?h^sm>^HcRd8fPJ`z~gk?hj?Z2)&nA;%o71_GzY%la2JYG_O{kRbQqtdzMC? zoyu8BX~g5qZfwH0w`93oXTml8q+QvLXMfL($3E&-`_Ydu{bMes|P@;X2OYdNW*qhhKjO(_crDS4V1I?RfQtP36}YXb7*#Qo?c(dul0EogPZp*^=j?wIdQmM^?j~2$97}!qANP#*VNDO>;YOk!?R2I zy_fR7T47>wW>i>fh^Z{s4PAZMV@J;mwuf}n`kBjh5Atk0dmOz>$Fr%QF^^>GXXM#) zcu&7l>u31&eAr$@Z*(zi-x%1Rdqbpho_xCT?7Ks=XFfZ{?M4>#% zyu>TaM1Gt+j5DxXOg{Y${BBI2ARE%jv01-yx&98%{w{-focZaknVfIPx-*M-JZlbT z0+DCK_I~C+et_*C@NDJ#CuJKQLWhlr$*Fr8--h2~Dstu%Ry08@a>iIoJlWS zJ(_&lD4mrU{Fo~BN?2CSLEJOYFg??FwP8AO8u9lO*=M|ZqU?#AD7)j}I(DoZe~&U= zy(wCm9tOJ(*Bjyb>we6fMGuwD#OB}i;~aK8`<>7-Ji18M1Qy6kfw}UMvhA;It7m7* zDnHJu@=KQ|{qXBP)XnhfNAT*0h`}Eu2EQM#zMDAw4!FLhcR#$EJs+_A0F2+)QCVdbPf; zdE6F*xA*IIjF|HV@o%$7>%#ok%bAxo%iN{bj$haD@xSwR-p^_GJ-hLSKBsZVrMR~7 zxXDe3#SPmYt_!I_77+s#!?pIjF5sH}gu`_&B*Y(kd)p9_vf|7*WbO zxOMV=;xPFPuJ-r;&r47qj8SlJc>)6w{JKZoh>;8*$H z62sh?eoQ?a*1w40zDQw}@+KbrYM{gP%f#O=!}e;pegVI3J=!pGq}Fb z<@&bXcr{Glj#kq1x~+GVOz#o|)0{2ZiE}sTR}Y8lA#iQIy6FnyaANRUyh3YbHHK;G z;Bc)>t4h&8xYnMQBPRy;a&78FmTNpbTV;LBy#AdSyd5Kaj&n9}+g#bj7_`qRYu;-w zd){mP9Q(NAwf%a#dOy!T=lvS}xc#1OE!^f27jVtb>-6@Vy11>4Yo4t+b^$fV0__QM zUC6bU>k{(o5^85<)Hy0(x)Q&xR?nv2Wj(v8E3r5-v3PfX=2SEur#24PW8k_4-=0X{ zV>0!WY3kXUXOoxBf$yv6XV0U4wgA7@JbSS}u{fT+9IkIx&vx_cyF=8k9kw54*6}gs zCmv(p(PLqS@&xk~&qr|HVRWs$ozN_wBy%nZarh?oFm7W{#CBrt9putGbj_a4`S`Q& zY~pda-c3xto5!j>S=`Ty$NTxbAG4S-MF*G}IY^D`AbsXT=rA=e)e-XKqgl+axH^U> zJAz&E=i{)fI;Q@NHy=|Oj$v3iR-RMvXml8+5A`SZMn5Gp_YKbnk{p)zCrlxyo-BI} z&l8MK?~WU1nBEaXy$tPu?;Ww+|2vMonQ^dAT&_HS6E&228TTa(t&w+vOXZEAVtE~( zUK=!kwOC$J&klfV*Q+%Se-^DWTyw@GHM1vt=w16bak%koV(@$6`u1M2#NAPH4{Wcb zmwhWt&+N+I9G*v0{~k@fOuc%Ta*cO4!?R_&LAk!d%QZb~%kYn|DNEk#OR?ASbL?YXFZMO| zIq!P0uhEY?|AsSvX70)9#un!LcD>qiu0NC4w`~mWtXIJL6()``Gb+6IY5$ojH|}hHd6l;CsA!Hno(g zXd3;G8RXit$;IX}V|+EU#`EcWEn=?eI_4lW&t4X!o=u(|M4lazX=-Qp1gmf7%7bAA z@+kWVpMdSBnQMNA*{D_YV4vgqd1j?vVvo|==oSjNYS0Buo56`SBu1&6uCm+G1j}dcg9DWo(R_XQeOcir# zUS;q&L;2>uLB^x0lcjNwFvGa!)Nq|L2ak5uAFdOrmnF@hR)$!AP|r@7O3#{mglasF zzSlfE4*%Ae9OidXOWTfzZ;c*pc>X-RQ9cZ0}D+Y zzM91)BW!?feoMf9gt zg_q$utgUr$muYXU%&C{bEw6d69Dla;GF_EHr+)RXcv}eOLCCX5 z5{r)_&-QXX#d3{r&t_I>F7ew{ussj17r^x*xYj(o)w6@r`OdKv7lDNMv^VZwg zN3n~!u02^=59gjBaQq{6G1UQTV+ZrDr*5{`Q4ZJC#t!Fljww2t!+8woSPo~J<~ll_ z&6&m7+-E?Q#l1wVvh|vMSeeYWWFXdK@P8E6kHWg@NGhD8BdN@&_vhY#=pY_`5HJ6U z>jTN`txnRod?qs~GvIm}Tyw8)m{yP80?S`V4Uw<o4NjD|3dOISu9X+% zs3UuqdTgK9)$-?k&OUFSvuBXn$KLf~|8K{Cv+c1mt?`BPUS(tPw)ZTpBe>EULp!No zv)7IIyUnweYptL0yH(F7#~$e6x`Mw)1)i<-am#kS>)B0R=~W`lvxjw~mkrk=bxsAp zZs{3Do=xuqO~J3HlWWg{?Kv*nSChA0>z}|m@kyLfN&Sp?d>MK6P0T&r8k8Zo2T>Of z%949nA0QrIiDy3)3frMY@)T@4eqF}d*5xqGzEAWrT)(DlGkdX)`tMs&HS#{S;E(D3 zd`fTk3uZSq#1EHm6GzMDMDDqp%)JMg>DoaZdN(tid#Fo)pT^!V>RJ0!xi=?sBm2>h zT>qFdo4r+6ac&9zoymFR@OqTE+sNhBFss}WgCA9ohTX$y#PMmw^Z4~4I6jyH%gGvt z&xPUHFpOWL1IaLq_7RWoBNpF_KktkgPs~k>-EX99iljakL47Q|S-$MsAfNT64^6!N z2|Rxs!v4!(=JhPs!R&>_tKSGD4o8|_t6ytw{StnyxwVPGed%%eX2>e?>Zkfxzh>4< z`&-7V<5=V5={`yDo+wMYN6HlP>9L(TleRNw$s;HJ))-uKYw~LRdXR_fI=HUZ7~JLB z@oG1>{u_00Q!nGShesD1uXcEDjlp>?SL1GtzdPuH*jv5ZdY-#luG{#rG4O@ldO7UK zr}ty_-}0{B&$riq^)=qlaX#;Vk!#JhHP2=qpd;5>6E|_VhwDn$vpaI#to1Wf8&|H? zv*~A(XOHhmFPnILaxcy*ZDm_Mn|)sBTIP=Dvj=dIAGLA#zLB})Wr5Vj@$6fO$!{kn zzcUEW4#Kmkr9Bv$FOR_W+ z`Uj1bVe&`fXB#&k(EOObv&!Vk3CxNSNB?`8cyhPWDTzb8-L z6FZ)Ndz|dRhj&CXAKH)aA!?*-hUM=f2Fu0>=Dqti$(LdDrbGGOL+MSUPeYl_gYA!z z@oU(AFQlB@y3Ewe9Itk`e#LU_*PiQDeYiJyAN(4wpTMsl!>b>{r&q%BjvZ1>eCnD`tPvq;d%gEJ2CkG zCf81Udolhl^_=C+yV}pS&)LV`RX^7DxP9DjzpmqIKi_#?zgMl@t7pTwCl+_S+vC^z z{?^W~9skaEJ=>hOpgFdeYi9ZeQnyrTZM+O={j5S`aa0Z4b-XXvn;hHm>o%^bpQ&df zQ$J(=Nb6_H_7vEj&J4pWYG~@&SHbl>xV{#y7qYkDdf2`J&tA&jgB5{kax+}t2G@7s z*Z08o{f29LUU>GSu>C|>5%G92@%TV_ftY+XbCPSqD>+M(GaA&dm22iO-htuwnbUZm z^+VQAn4A8TIgQWI=hT?A&ZNEGFY)w^=xg+i_I;zc2M*edw&)tsDBECLdr#V1-p3r+ zKJr|Z&W>2^i(}@Kc-gElyBB7y_C$}9Ju!}U^<(cwztKoJMsU0hj<=#sk?gsQ9AfmX z;hMc1;q<2a4wBFD>d(Uve18VlpM(;FqYu%0aQ!Y`t-1BOpn*;d9st(?h4KpZvX=w$ zWwoE?*4gqrF}QlQGW~QPYG<kC9tHj87}i&%n3(^u6#szel)?@4^`xa6PdzXJ4Ul zhHK8cqh_Z4E$v$m#jCYmHV8FdPF{Vvn^$XYjjE`HSD&$gx}VY-JnYp4?OGXVk{2qs~HeVf$+8 zs`IGBEbwIpjofW9vx`e$dpWWAP0T*s%B;lg%rD;w+xKcb4%;h3@a&L6c^tlb z>Hp&2I_t5?)fU#xQG=;daWC3LJQvU2PfbhXWvyvBHEd_yM;^Q%?IVtM_@0PAJ5pbE z#EdtL?}~0xQy4d36ux5nNZVEW7OCRtB??6WZD$?#~$t6`e_ z`h#F zFZ!m)vv~Jy)XHWu?>z~QCw1nGGNjiUgKNEP4EgmaR-Fe|rUzfa9uG7KHNdpVtJSNG zY;Vif%A9oob#OCdc3CHdEvj;@$))9ZvSsf7#N*5TQ{-m2zLnYKI|4JPp}{t~AFT||lShJK8@``} z?^U72@?0o;7^nrm6vjP7VEbkE8NUwSZ@~9k%t*fj+se0k_eXg3$LJHZ9;s)45eeVu zD;VF1KYtC!-?;jY^;>2>w$dlwoiLF-(A2R^jhc8mo?Mu?dasGEm0zcB{XH!2!MmLp z+vORr-mN_m_R-jAM@>(^T~?v5C4v?+p|AOfDz#_+-N6<+;C7~dm4y*`v!!^Ggk z-|%dhCcl0^qyna?fh*UpSFeTZ*WmgUxK^fD`!SEAUhV5Jtv>xU{pzRCD*SqlFZZ)2 zE`OvqH4ozMsa;^1y7#m$oDtvI;d>&U-2%@#2X1m}R`qJ9w?%G^S8FZ29>2C+SHX29 zuOrQ^b>CK7y^MMeJUcNsy;nFk;`OS%tvnuI%%>f{cKg*Acyt@+$N@J;<|}CpJAV za&2XM4!zK;V0#{H&&RhHTF-{GHQbP;Omiy3y%uGETMDHt@-d9M0 zJRO2>htSgw;U1l>Y}4QFTfrGW#N_DBzSPnBs&ChlXA_g7kI<*+GiEqGC(ix?Dch$e(u^vA;5!8BqLr zXEZ%%c-}?~uG&sbY#U5(jT}KPt@*SghiUa`t&iXIs@&a+ zb1%A4=QJ#H-!;~0#;Y~{cIsv0+JFzEI^J--po=pty`n0nb(_vbBTi*Uw zF>!b?Tx-otds_v(K2_?|)~mf->vh{}+Tqvswd`u^X8-PtS$n417`ANc$Gz9~^DUe9 zWB+RPex3JgJ3imeuhqLfdA1Xmdw&PcwfP;n`L^Mj*j%|*-!@zm6BROhraiBLhHcHY zof))p!! zb1r!ZXCV@ozZAy3I`QsT!tm`de4E*;b?9xp`(5;&dN(tcAEA%K;hVg5eK_3U-K}}I zavlNW=&Qc<@I6>|YAj6+Y&T5r!jE^s@9tJn|Kd3_^J$qj@wO9p?||p+%5oG;Yd(#( zM5>(FTkGB5xY`gt#4xS(GVNz6)9W>-4%7JC;aa`gFirgZF150^m@U)1dM$DI>;8DP z;X2=Nt+OaEFgLnNnI`^Tjn=~W8_cD=!1aSY*LX__jU=D8E9r#n8vfG5_?a^ zrf6dSHH;$OS9M0kVOwWZ+*q7EyFJ&;mfAcUziw8~Ht{&~ z!riH#q0!xm$KkpKt|!2?@odAkdN#8vuze+LJDyDq4Ysexw{IXOU+Qb)@l?6Zu$^Jp zz8CL)K-mt=fo_LaVXrH#bH+clOZf8GhpJD3a6xv!nA;y!oOwN*R#JpK4i z*Q0mf)7zpnhena?v4m&UCL_F>ocddGYUO%kU+QFG?Bmd!8m_fQW|%g$GL64EUnWRZ zWol*b!1ddKWo8D&*2`YQr%hgsSFhF@IC=GIV(^#It6ab8tJmD?lsfn=Jve)=EBR5^ zFqs95n+?7~<8NkBE_d^4!?mrMwddN#-~%pW zPlBm|6CoQ9~Qmo!L^Po;|+1#^QLksi850hG)+r9-j-}R~xo%4b5TuMjMY) z$GMe!TjTLN12W}qxV{hFAK1()FzjpHKV42*BS{c19r>{k?TIa#>>0w$ggK1kU^KxCy zEQ+atd;Gezb!L>k!NlLnvz7X^H~wy|m$|*`R-Wza+4!~2f7=;wug_h~uiNL!)*ro0 zdapb3s2{VkUu!?dd2D&MYuo2v>g$~Qg|vNs+rQWLv)DM?a_!W_z4bHO*Kf~tTRaZe zcy}T1qebkQ(;QptXIeK?rgbgX`iC2jJF}%u{Y*XEoNcHx<2t7T+wHlger7zI*;1{a zxm?dA9-jrCYP5gKt zug?dmlRrdEu53Sso`mmbn1g;cnBF#AKOc&3v#w!PzF(ui`+7(jad|m>SIFDUa=hD@ zSuGg<0LDLn@elFwPteCk4&yr0rL&rwV@AXEMD|sUXAj0W*+!gg_{O8RtMoBEJNYvn ztv>B)3tVq;WADwzr=6O&se5akOnsU<*_WDAn_e|FdyT){`u8Wwb1=PXvoc#2Nc}sI zS{Yvb7F?S-6na{(Q6qa5>AIGh*;~w`yh)GiWqkVa-kePX&)4;clxvB(uj&#ZSK-xH zcMFH>FqxzAH!^v3rx2KC)}Nkr3o-Z@@@mbgE!P@@H+#5l#INf-^)k(^wYO#CaP?}( zuhpxayxMp*wH;@VM_XQPxz_z1?c#3_*ACMca{X7iwU%O0fAu;4&c`gr_TTe9 z{#Vz|zi)flT0_u&wsOpMYp#7^j;-%|-rBh3I^SDA)A@U?i@P#8w&vL`+sb!^_Py|J zQ$vGm!#2L%L@aJ*(3EX7qMOF!)Xn-6j<*RlnX*$glh~er zXrARfeNNxc`}s=7t|U|QLs#G1efpf|obL0U?!JBRi}d=(mu zar{qlw)#(TcI(f09<%nDdET1Og})7-|Bs0C^sm3+{x#Ole+QrbTRfj)`a7(dSK>X( zLHD=V6Z>2EaT4d+O4#@QFWU3QoSS>zKghK-_Py)u$zOdx=iXQYXx`29Cpaq#fBv@o z8F9My%Kq&85U2HA3$YseWZ!~M{|RFCpFIWtS&!#je}Vj4@N4dWKZrf=4?f|?aAxdZ zU3eE_^v&?)cRbGNX2$EsGEV=%h3m2JeGL-#zu$m3{W`?y*TAQ>SB5xEpT3gw@7e=@ z5qz0(nm(;{bR&kV8p z=f+$$du7S(8(vSx50Czo^5hruLDC%C=l)`K;Tah-Py1sYew>Rl#jmw@uzdTG{W|k; zuCp~4XRh~Z+OMC%eqqeU`uf-C)9f!j)5d(feEWHrlV8F4IQ&}Ay|9n=Qlu;KZthiF zXT!f={Ww1N!h9U*YRt*sh&}RaaL)8P`1YIe9`9Q&#r`Fpi{fOZY;Ed#_@DAcb@a@lh@8uUBM*0Hgu+No{D1#ToE^d3`|l8!|0{f#>2G+>Gl_o8a^(LN&!Rog_WGIgZ29vK!JiSAIp=Pl zo-vy1>Fj&+tSDkM=G~lo|Hb!V{~Pn~zrg(aFW}dIsf2I+EC1CCf5vCm@Nqt`{v!PO z_nruUzU0R*+yQ^S?eYKe!YxR*!mn?A+z-pY-;Ox_HpJ~4;L}`NUsunx-hfzrHGJB8 zW$^2NhIKXP-&jvyiS#nWX`KTvpN?NY7k>The~Nt=__MxAh_N&M+IwX=hr)F=&!Tv3 zt#cKOJBPI9;Gp|C#V_xL$B$?3bEBNwo}J&uhvvH)@jcg_ZWDh@`_Sfp_xAGrHy~|~ z`D`=mQr>=TUB45*rf>fepL6j!1Fq@UPvo+~&f4Oi2-Yd8X z{(Tetnsf5R?c4E=|NF3)_I~W8-G!Ka51!9_0MA=Lgt+|??EiceetkdV_EWHzhPeGH z`1EIR#_Ds3VP8ND`yyjE(w7m(zKZlU#5u<9KS26JJolA<>zo$m=kjl!+rm5@vGLE9 zc$N$QbA`R>zs0`x|AXgNe}glhjNgjQxc*xv%(eeUDSKkfuR>ysz7qRl*c9>( z+RwEZt8umr^Y7PU?)`5OpXt-DP`t*PI(udCY3`SK{*8D2;M4JIe~WP615d1GoaVDF z&B5W z_EhedDaCGwvnr^&oW5Q0`f1n~e>(Tiw1?)kHs|B?>u1$`{5KJ+Rb=Mehd8jZJ3ka zh`4?8rFc&p{(URwti$7;^MUsXPR6>`Svmms|e@tX5*&%v>WigopK)*KvjI_$af*%rS?*z0PHr^EbP z=TQC${26J^zvJ6;4nE`ZdDrnD=ZV|rKjR`@{OougUbBAE%<^0_pY1|F%O>rl4~_GS z`!#*qd*j2eF_-xT{7%kwHs?SW^=tIeGm*~o>*wHH!t>zgSIDo?-#UlJ@6Fn;uY_M; z1;2hJ{Tk_a_}mNWb>-XFVy%5WeEUuC>$h^P&6th%es9LJ?RVilgxlcP?}1;pZ~w%F zI}y9@!F>FKoRcGVe+21cn48~^cZwg7Z}Weq@E;aPpM{S4kzg_tF6MpK#hcCf@&R@cJD1Yq2 zUC8azKYHO#`1boB|D%f8jMJQdzYBiNb@fe()AZ@y|E6ER5k7r2*43{?yyp8_+AC9> z<{bR2Utghh_4I3>1&?3ne3|#l{^5Sz&!|t&#dqKMZ|~Q;{PuB{Pk)~Gvkm9=y-sM~ zUcX~w?`-ak>lq+?+WU;{+kWPS{R_nG%Mr7gbhead^6~Bs&yCZs6SMjM48MsrcGlV2 zL!)0;%)SEqDtt$qd*d&yJ+zm>w_jfK@mJ$~;%gAMUyFTm#_emck9J-8_M7F`csGP` z`w4jO3$goMcpu?bq<16Tj`{f=n3La$=U{j785ri}_hMfDA)KN5FxK85MGU_m{{2Zj zt9<}_zz-t-G}32r&Di~U#_sRI`V-H1zl`)1oVENK*WQ??e;w(Y@N=d=K;nCjf6Tq+ zr*OX+34Z-Yi1puk$}@4c3v*z^?>|MJ^dDHpd&GP1RpVV~Tr05+(zm{cXSbbrew!)h zJ->l{uCG1$X&1h7+2t3$^rTV*d{{TcZ3Ps5jg67NL+{x!h z4{dVSAI^(Y*V;SFIq~GQlg^VO&m3p&ohfE(txdmn;`=twfKNXIzWl4mnSKq|+85We zui8I@U-Rtv6-fMFCe6nYuXzrQYi;hK(XU^D^h&&&|7xVy;JpI+_UpCQF5kWcaU0KJ z-io#M+u+w+Z@&Y3Y41Y16|wu>@ay;Dec(Hg?!1O9G>Ss{5?;DZzFyq(ZA*6SdV`d>+!E3eGM`F8+vz{XSbfQ z(jRawi}PIH{uF%sCoX)X zeEI|M>3eX#glA0X(|5q9x%d5^ntR`}eENs9rhe;pBTmDY`Aq8?ue^T9p^J`j1BlU*V!-Ucd_`*4W!>e+@@daoGIfr_Ql@hj6Cy(@38|%zhZ@^YHO6!XKIF<6nYbGJP37{$=dFeGU27;pbn656|=s z+@DEU^STUe{sj`|&fh@#I(UB-d(>Y={^ckA5}rNb44Bd{T=?Q;PG7(}`Ey90MfwcV zL-6fS!N(s&;`;dk`12U%H;=e#>UeLL3F^l9&VbMB3OGOnlb zJnIIWD|-uk`8vjE`Lv&Dy}tIpxu)i~P_(XQoaX*_`L);8Tw6OEeZ&38Qhar}DDVW)5G@l?K8^J4xEv=4QgqyO!`w)0EfY;)>* z%1N7GIo72$w-6alpU%Oq+XXFU0xr7xH^anvZ)<&bWOQpMAlnUyk%D`1osZUh%b< z6JCv&{YJ)Z`1bYi?KdN4)39{dV~HJD%_(7v70|-dmC0ed&+lztpjphV*{; z_nlAt34BivaU1V1eDE^N%aQIw`p9KJgYR5F72nLoe+BW`590QNn3I1B=PN&h^jY}$ z!-%6ypGS=SBA*c<{XYF1`InKtg6prqKb7FG^v|#1{#5!Z=}3e5^jDwM>C4#T`toJ) zY0R&`bQu!z--l0s;fcR+;qyqJy%hfZ#Glig`%{{C)1NW-e!|aGoW}h7lAq9ensadO zk#Wv_H}=Wys zx~}Wrh?MO#o{OJ}+naAYZpW{ad*0K2&A2Upre9}Hj7!Sv0)Q zBfqZs_zTLnUjYAp5q#TmyUwd{e;hvjN~Bj++E93FjP$3F9>P5R zGw^k$&th);Fnpfr^W3A7pZ^m48$S4CASU# zu6=LKyRq+$82ui^=G(YO_E?@j!Fn2JP`LN~R;;7t)9`EUee?VY_Q*K@ey!%;_y!96 z+0U~Wr#=6EDdygKU#s@NpO2j1OvwEE+3;n?>BQ=zU;DcWK8KS1Z?38NT-AQuld_>v2`|m`qB9{ z#}3jKQXZN}`{KV!_sZ?yfI&$W48rOu$0Uu&(6>nkuW=-ZClJg)-Z<~v{UZG4O4 z)%b4KYp_qQeYD3RZew5P+M1KU3GV{G8S`>J+rAOL{q`r|yeZPVu%C7t=H>53x*fj# zKD=Z2{!2YCzZ-M&dvM0;14ti2x)1a755u=ViunC;T;EUMM*MyN>GzO61wUtc2!1aA zhL3-aJ`Uf07(V@Zq%YuF3D;kg@50v^b9s%rnZBq!FYbGJ?~6YDdHD3hN|`)B`Bs9@lyUB@bE3#O|6cE4B3|dY7X4b!s&xh& zeGZ@2c~Pv>^d4rN1K0Cv#DZT&Oz3#+eKM}AeGbKYX4&&$jPW-U=-WPraxuStq%)&O ze~VA1{L$poSL4I8Z1R}p$LmR#>(J!>bbj<}uBqeC$-T~=^KsfQFRm$;l2st_H$}J{yfaduaIx!**5l5`OX)geZ3TCSFXbIZTU9* z`c?eS4t)Ey^lkX{>*U*rD~Q|I;`#RVNN>S+^KL+T8|LOW#kcVs>{g`Pklw?%4d2$9 z8~%OgrHI{6#CJb%{`6j?4`Tg&A7eMtN0B}bAHN^z6NuptV82Q}j`tWph4c`7{L}Pt zyHoer$iXFJnJUer#W6jOJXMiT?aa`11X=hUT6( z{rSU4-1pYAEaZ17@p%^a$9U$XpH1f+8PAE@r?H-{xi{a@^4Syali}S<#Oc=|=h<)W zeRH4eRo{j4CrDQzEuY33+CH7{8*xqT_qBeDXUjDI-p`nR$xh9` zdmQ%s`(o$IJP-Ff9`OhI$-F-LP3lv=-+O&N_uj~Br`hi#>r@^Ysmp!%h4(CDgOO2`1t*p|K86zy5{Lv(;|jH$T(h!*AL;j z@u!hKQ;FA1+~?}_RP1@dcTx6f#ANP$F@0L;XK~GWc6>Q=ZSIEw1LoO!hV=yaG7{r6 zpJDm=)sJ1cNBd%o(R_x5=hBb!-Wb=?jMLil*4!KC$PlOBhB*D!@BRVrd&9To(>QnX zMkJmQ<$1E#An7?Zd|GG9%BNrUU4E~c?`q}j3HQJCUPtYf@qI0>srik6ok78v;4>}7 zX}w?F-$L;_)`$$tAYif?=tgFxS>**WunfS=@{*lU4&-~bS%9(Vz zK1Dyv`ugneN$O^98)W}~`nhBFdL~%=60u`^JLgq0Px4+zt+ly7&NFD}KgR8~)~0V` zKK2`m*E}DLvuH^AW|H>N`0R^&Y1l`je<8^PP`;gPeKaBUBK8i#iPaOZG;y8RyIb%BJ%9S2Ou7vqB=g>20jlB6U{g}_H zILG$0DnG9pu^B#{*nA&+T5D+Tb#q?~iF59|;LDtM-vNKVoj#5Iu-o9rOt&Is-p#f2 zjhJ`e0H5Z1I&*LOGvDR#Hxlwbhxfhd)1H6x88yFufAYWd_T3IXVrtJa^(4c3j$hIli4|TNjVf z=lLXkCO&v7k5A3^DNCNZnTOY;N&PJ2wP~~dX6~}k?K$5C+5I|p@w;9%&ndsov#;gX zvaz2%=)4O0Wv#P0AIE!~^lLtooX@`aKTE&P{gaxDXFeX^=6sz0nfd~*)hc$s1hM;N zd_So6$zOr}n^$uWhjVi5r@an)<&N96=Egax>k+%(g!C4^vw$-eH{vXnV>f&o`)ao! z-3A}0f4>L&=I_IMiFY7>KlYFBM7kUM=1Pd;_cCFw{z0VskUq>f4*&iL_SHX%^f6q2 z9O-_fPhjnv^hw-*68B1ozn@%*b7?2~ZYQply$7Z>Gp;|%xU6~h|LQX)*bk#W`&l$_ zE??%kF|4EE%iQbs-WYuvF&gu2`tv*B*YamP$MRab;xyjl$oWvMr4gUG@9p0VEB@0))8T&}Oi{F||wKK(5E^zdtr3!eSX`&!z2o7m3f77?$ z%JWlLbKl5$Inq0@xArciTM)Z%y##UX66~uXy%%RT?tqUoelt0K)6cQT24BAy>4R9) zeyGxY@V)!s+xBt9a`^JcQH56vKJ!^L=U{=7VNGY@Uba(~Q^ z^|^Q6&%D|mwa(`H1iqa0^>^ymPm^DF+5A*z(}LeR z;oKMKomj>?#C|6DG85<4Nm?&s-oWS2_hS9bxi+6e^K7^M`A2FEjX8JiiE#~`b0)VS zHuD)4W3+sl^KSSt*V6Q9o+I;_6TC~U=hNKx{$738pYLb+U1~qi(lafrsrfx4?vuR) zeywjBXDZm-@A0HvOCLeCfGY`L$v;;x%LTa}f`8ejGlnJ#yr~4ZrqTa_pnYui@YD>sMmE z%{BM$RNTHA{>}CF8x^ec+NSy=I!ufp2_|Y>YQ+o--W*Zar`DA zAqF$ahw&S7FA~p&@f{5%$d)hTcjo=T^nRQZy90A=enXJ&R^EyI^zw0 z=6?50oOAQ637<{FpWlR}IIVZ6J5KZ42(RTH8RE5k8n*6dS)PCM-OCr(np)>i;MdQ4 z4CdaC!M-Z+0VE*k`jef3JoiVV^pv137tk(P+adwU|udDM+%V$Otvo98}{U4OO zPtsTD2l1Kxy8SBkp^xv(`_Sw>cK+wl%<|bj<(;3!6fDPfzW4e&Yi!4CueW6@Jp0Ob zll&h?_HFw0(YL36>%RmrC+F`j*V}w9sb`b=o%VX0ahrQ-Tx&D+J{o@O;nRBlRlfav z`Zw3wocCc&X`dWpj%#hs$zRTSxqO@B5x$MY*v)nK>#?u)2KYGl%dbUJ>_*Nx`CIY6 z(hXX7W6jC<4ZnB?(mRoEQT)dKG5q^Ih~c**y%*^YCHObvIDC!iPOQoAL=5HJm_B$9 z;_E%|WhbBC$?vd=}04?0qr%bG>5;KVC6~z|%xsR6hb!@!< zo+fVltP0oK_G^6SqJEP#_d>tsH{0{^_%-9UQ~Wx)-s^;q^E>h{;`;>jZS2dw1kX}= zcI7JhHuuT-ZLNyiwWszveE;_K^lQw=ugSa|G5k92t0{KVzxh2rJnz)g-l(5LQrAKK)pXP&ao&-ZJeTkrkxv^T$A zXXkg1dF+&!oi#Rd+Br67?tAnJ_RW~&*XX0f?8I&Qw$6{Ee}5IvzvA1^;+&lOXi9wF z$7fABAE#eGABq2``aA2=PoxV@McjB6ubv!dN6ZgF;K_1_obV6JPmfPUdx8V1O``i5f7~j)a zXG<{8)_fcB+0U-%&-ne)IvW4S!SD9NkD0E+&`C;U5k6V2+7<(-z;x)&K_rLddJ2KAB z`gM;_%`N5U?d$Q0jPu;j++%H%l(U3=}TRqTEf;`y5^hTot$EZ>2^e3rhWgVk9Ecyzj^d$#%Mjm!g`wDB;&cz^=uk`O@HP))OnACbML&* z@qDbKxt{hJQOv#3Z$A6Y=a+p()c))}743W1Gp*yhTKTqr-s$l9?~d0RZ}59RtIjj& z&d-d~i_E{LKg3U_U(B*eJ86#Pxo=-;e@?sP7vn!^!gJH@^nHd@a&-@N36!#BRN(i1^Jt9G1z^6U$=9y6Xbic!)Z=HM>-=TJ# z=6~ePb0_?ksC*jx-h7{f-$?M8Z_c&s*IHB8KG{5fl4sTaURkfL}9XIHr9ezDen*emBVZp3ZIZtbUOZq9QRJjY`HUa?#M z7x#S^-iY{p4Zj7#gneVC>oHectG2EVeO^ISF8vz!Mbeqv3|_Z;8J1Rqb5 z@5|@?8^G^;GU>MfKFi+){WhQ-pEIjHely<5`2qPurV`}79XPnR&EJ|E6q~aj2CUrU zuGoAX<|UkS^BZLJX|ADP58ZvvgnNs1&KrA=dM>RwcfMc5|Iy-mSv*HJzfFd{%gntQ ztI;R^?uqxj^E@m2-kgVNF1F_2?)P<0wD!#Sy|SZUJ65z`^E^tQEjt~jJ7#mQ?DT$F zk8#gqb5H-s^~`5^d?@v~pE>K!Ym@RLecGfx{FA4Aw##*BjyzJo=Ui*^yAo&Ty^29{Mu)Mx-XVrbBzt(*7r-%ms)SH{qa7t!Z~@KZ8B#2f0~%{{VzWc)wd5hCx=fb zb=>wji9WyLd3pBKUXDGXS7JQ-SvSAQh4_s*y5{Ea0mg5pH{zO!zI|P#H^G;f?Bn!v zJ~QI8Bd%wENc+z8J-+Aob|%F2q?;Av8Rz*8Zuq`(-=~gceD9e_?Ui$$&@%L$1N`pL zpXtMV_N?!CqfGt`pYCrTaL!%dHR1X06`%RL#{aEFf-iGl%-f4Vv&Lpl|JEKF<2L#ye*JXpoiHV4>pWWh#&bW-`)K9Y^li-l z_}|*kM#|Zh=fS_}*V;$pe341>$69kEhVxk%)621UPT%J9F#0xQH{+J(<~2X(-Z}rf znMrGJ#O!O4=->8p%yDte^k(=R=ju#vW&Ff`n|&SUUf|tH{7;%|Uc z;{T1PW1|0ZUah$`=hv0!%X#k1c{bP1`8H_2`Rmy1?+G$SU!65H_qp-xir+!dH;cwv z+V4(tzuPg||I6X;obWjo{n|c_7|l62=H7fp?YWfa-kyKMuMaVr|G|?zGR5hC!nt?N zziTXHP2FRrV}t#fvD)AFKl*j{c;nN4pNamr``~%wd>`|@CXOE^Ctbdma%?m2bDez3 z&GmWv*Hc#7Gf#Q9@3JG^k@i&md1p&w!};86#_pUS=zgM~drr>vw!g#BKF+O!) z{O#Z4Hs8nRT8?L#bZ$j)`;rUv@2t76fN#H$-?qgX@kM;*ik$B!@W0;}yS=x@?{PVP z^S`8c27^A%Zw6#f>GgQG z-_mDFIrEfp-xz+DL_g7;tjPF%?pF457_r|i0=68#@p5~tS71|razUA`eyg%*p-rOVm z4W!Jy*K;iRw|=kn+gR>d=)+zaz_Jnn51v-ZOjm%SgB zJu&$-{M!G+;s2u6-WYxZ^?!Kf%Z$V#Nckzg$GY4zpY$nrnSUeq-^JnUC;i&b!S;B~`8dxI`FC2+zi{pM zLzw?l{JwL}#=UZ%CC@(UuVT%{b=$L;%D=Uy<6IDaE&s;coM%p-$CSA_a-L)1^Durx zk7Jl;D_(**D9>43iTySEH_xgu`5SzU-+E6>=P4QU=;QVs?j`eGCnm;m`FH#czDeJX zA98NY#Q(dYkMbM6&f~YdXB_tZY8QUYA%}Zk{supOS1L9$COa-??abdI{ND!lwfW4- zd)%0lz>k?2qpw8nIXAz9KznfB4WEWT^P8bQLx$f*`7`W^oM*^5H`2Gs9)mqhCj72) z-py|q&9h|Fr+>NT-+b>e-eJkSgI)dK?ooi=3i>6Q0pON^k zhQDj#`Ag1@Z0@{1@TZ^lkRDzc-2Bb=>!yy!X?1=5(Dq#kFF0#chAr zlH;vncl;ZEt-Uqm+*i|{dCkokyS+E$@$Gj%8Ncb{o})94zXE>!N+$Trt12;;D=s4E zeAmx`E1ts_3>X#@6-QXme^04naqd0_*^RQK=a&olGn_R%{fm(KhC+5ysN=) znJ^_bGd}B^-mrn*#o`(oiD$gIH})LH>ajPbce3cuk3o#qe2LE>*#}5tk4$}(7+veC z(=K(#dzW)h;vAozw&on_kl0_xKIHu`a(#s{(9gii$5EbnocGgwHjQH^ zF@iDN&$#_=fxgiY->$hi=jVAA#&7a49x-`dp0&64fH_C!c{Tq3y5qO!=<;v)IG+pg zyZ7{S#c|~S7HinVa{4#d&JO}4CeD8v7Jn!%C8F)_*vLv(T z*o@7I&wO6B-qGMZ1p4s2SjT6d74w{%^KR&0HhEs19rL?b-s{%9Tl*NCb9>&cGokS5 z?gKxU%zIhvD}C<-zt5Xbqdz$}%bbn*91rc!n3HQwop-hN@wU&$J^#*_J?3)^&+AFY z@_C=@BYp1Aqfa~LlQ!3nrhM`^(t93a-m@;(X*=bmZel*xr`@CR>lw3ihWtFg{sr_Q zeOqT%xKEDjq}{K*-ro1qGB1x`w*s+gsina_r)_0rcLX z_RkT+mw$KsUU3}0#W>EyILSDEjOUyWXMf%2KRi!PoR6P&&OPhw$^ygf+U*|b>KiA@zoBrHm&~r!cU-_9<=9hC! z&vD)3c#nyHpYnVo<=EzUA0LYR+=uSeJ?oC;DNDQa^2iDOsr&FwU2^ku+|C|kY#w_n zKe3m_zGECe<>P$5&G|U{KFQ}!JvU!#?y;xFn2~uPbFRI4j)jTwh0nrtwh`}#%{_Cj z8P_wh<=>25j^o*5cwTWb@iKE^?m>6*84&#r@VDR* zQh0E`D>0aBRPiO>8U<1;HJetRq_d)++S9h)(y4Q)@X zrD0$CGwqzc%x9`Ec7Ns?DrZgdz9suJaXNlHbBzo9p3i+UKBLZfI*zl9x6HjWR_A!$ z$Lm>NnQh`H$2LcPsZZPU^6*QZcDWDVd4IeP?_>G#e)2wE``*v-kNl4J&6mHSezp)h z@BQF9zpw2z_w;L>AwqxqIk$hq+4q+tdCfic)aII-^E|ELFuw3CWzGkA&Ar*|mos)J z>5Mt|bSq8Y*4`S%f1d4dZb~25`IcIT|2Fs1wb#aXAUIFQ92ZG@ZtzjY$|S!3ME{GQ zAM@q2XK0>;HcsYYKIFwc?3w7tFVk;M?`ad`Z{y78iQ~D781{n{U&1buhX7m9WB3xKlA_Q`JZ@sj^$@rm}`ya)4b+y zb$t3b_l|FK-u+bgwG;QjdH!S{TNz`?Io2{ZXN=DH?7HLJbw95Xy0lAvUODR@sqV;w z^eIbzsw{QPFDBzim;1@H+y8z2nzCv8w*STaI{W1GZKpin?q`xXrPcxb0^$7`KYs^lip(o@@5L8u!+ogYnPxcjo7cZH(o5K4$;m zIcdc|l)XgH!MPsinGUXHJ29Tq2kqyGtGJKPr(7|(d^L5ZGrf7358H4)&BSL@KDP}Y zW=tkNy|3!uAf+DwqLUW3b&ySzq2kq;K@9D?tU-$7Iv*X)}-CTFq zynLIJb1ccX885i*M*OHTH{-YcdwOd-^r6xsLOE9C1VYHr`juemUc|o_TY8BJuo$ zziG_x8t2=_IA4M5oLQq^D|RD(dykF2?PoxI4z&9=V>i!FJ9jMS9$fhy=owG>KUGff zPx>g*^jYt3bzSJSk4Ii;uk}F5;o4v8fAD8{{5bDrXs(UlBc0{e{MzqZSvSwOpRIEx z^^Tn8+gwZ2K3G4Oy=I)IP3_aPapvAWXW)G?uBmHXjlS~S`|SMtlwW85?YTGS--*|q zw?D_X&-bhNRD33R#%%oSNSFKM$95@?@1_1!-n8w@k$a?{w3B9*^E&NPp8MpJrpwOG zV_0_{kJNSkKh=CZ=h9C1N%qN~ioTB@r>}FLIU4i%tTTJ$dCtxM6XCVap4Pma&sg}p zg^4jHvD-fGG3$Mx?D6hC9{*PC=3JFA4n7heXU@5L;<$VY{*`>K%jt8V%lR$F)aCc^ zPp*G+Kd)7W-;@_A@pr^$IqAodk-0VRIlpG24<`w8%|ARBsThrQ2V?VZ;TiOAGHFk& z_r>NKI(B33=U4FOH9z86lR9G%U&cE6a;BPF<(p*b%k(kt)z9cRKA*Nv+gG>W|1CDp zxWPE>Se>ys^Ni;nm%BgA`dpv#hqO;!@|2%ydmfIoNqyft?jLziy2$A^`|rdfm-m|E z6yL~BUGgc{=T+?2{Cz$4lkfdpg7wGe+vkmelxNNDd7tNc_VvVWulE?knfP8HeOv3n z@^RkN$F&Bp_{4J-T8HC5e6`1>wKvz_PV|kW?78`@i|4o8gG%Ds+{t@zT%*SymyfEf z{Po233*e(p%|rehIo$Wk+#0$4*uLxkp!J&h*_?;ed|U6X*B;{9-=_YE>E{nr~yz zg8Px#7t3D8ufPv~S-+#)8}n}}eVKi*;xp#eb8jra)t&bJxqzj+;OY+bi>ra)>Hb+^L{#4wb^i$T6=d4ScSvKiYeu^%5QkVO* zaqeed$NouQr9Aoc>+bh`9?tTkU(3HSmN-X`Uvn;)7!tpB?6zNL?Y+hD8q@Qu=}r=^6VN&lTG%rt54Lt{Kby?8q@aYjo!A^XFfSUh87CXO4DEe&5+X99f$`=eMW) zSaEsnZ};3}o#*C!o2h=|`gLskboj0J#>%hR4=3i`>9d);j}xc4 zX3M^J$Low0<{2lX%MJYjr-<|K^eOv&zzHETx-nl!de{Z*OmCp2mW2L&im=!OQ)azR{W{_ zT>IU!uKTM%Xxk!8#69z&ixqVOtv+BddBCy@A@~H<(zkC?wuH& zJbv81JpG#2KKnh#T93D*kM{Tuea0&PzenjD|5|jT{$gd12E(y^+9YRvaL%$*?Ph)2 zOuAWr@*Y}Rw?bnIpj@4)P$~i`ShNZr_vf?(!n&Sw^ zB4dnWw{v~3L+_Dzem-Lq$GOHhV>qsL{z5U0`@@cRdRB(H?Y`&Mb9DMR&$#*f+2w=u z@k%-ahijeZ8s&Ods>)dx|IK+h|3;8E=MVlZVXpUG5T7&0*1K)M$~gxU{a7)XJ`KJ0 zGh)5REq|^(i5a8m+f2J()4#c&$#Vvd)sM!n*{AK-91k6%xO2`+ovDq+VlCubmqt3hW6dX3x1k-Jo8`(KF_oLTu<+0P>1@h;_ub`Ez8_xp6jN4 z=*vCthF>dI?{?;T+UacHBxcXGm7isC-tD<}eE9q^;&GEbvVDA=-s24o$6v0K_n18Y zH{#eX_s6#6Im>b#`qW4Esr%H;wx_N`cjR#~+SGlgZI7~YPriN4IaK?0`g6{^W|`;Z zI8TUiPx^AtuX}u*ElVBq zq>Rbbg;&bweXi5?yf&%N+Pw3Y+caG^`OoVk?Xiyc6U(FzZD>-L{8afYOZ)IoKFg0~ zoRc%ff0?u2l2V>~uetcUzWeqZH}iU)ebCrLJ3b%l`6K6!NbqaMaE@OmkLUO|b3Y^N zIIg+*h;!3Nws@$xPG5IS?{Bi@Tgl&99)9#+J%=j4)x7!~-wj5`X0AE%K6LMeaqZl8 z==ePSSl4Aseb)s38e32M&+8SV(JpK0-e>7C;IhoKdrqD)aCSWPKH03}b;j=5aktk^ zy5sfad8+QnJ2YuG^VA*lNyF=7eeTbzJGRgLY?rd|nD?iy({|>eW7)jU{iJmnpK-T8 z=Q@5K|IR)ATm6smr1cs6a*Z35&GD32o^hvdB!ZrE^OX{dFs6@ZQZvTw;~f9?1;#b{ z2j{Grw`MLI|C#xW>Gb>fUvfTIdZZLu<&ZmhMi%D`$9Z*d@m&li$7aq)yav%;1Ao`X zZ`<0BShk_PIOp-%eC>;2KJ<%7yS?YV`$cOf(0Q)ScQ|HW+lSM~v+vX2uG_~u$JP1c zscAF*LZ3FXJlDr@x!rU9J!DRPDQ7-;((^j^$-_JO zq+wa^FGhc=?d-$!E%Vvmp<(&*ZH|}nZ~OS1pU*MYF@-+f^K-;-jNQvQei_R%j&EjQ*CoV;+Buug*Mm$2OB^WU-9bb6%b6%s+y^bDSr^ zSuyQ9jPoS41??i+;J1*^5ieJ=EzkGovU|m5&aZmjP5*Acp8b2O-(A0ttK%5U{c&8S z{8WCVontq3$FlSI%<{CIW%GLSWcexj&?KL9k6iXYj$b!!Kj%Kxzq2fU?e}{A{(jBz zxO^L9E@#&+9>2Md?qq++ep~$A_wu3IL-yR=XWV!l-@90T%6CuE&w0X8j?Tn$?NITU z{!Gj{7uNoq`3dxb|31C;9B2pq#v#w%HgoGzq__wPV__vp9tUhlRH|803kOucRne&&Am?ES;&su<0+O8K(= z*?w(19{0s^&wUU6&c>gaKXQC#U(?T>yN^%pcV}E_+zp?~I7&Y0a?QG2JMVLpyzlz6 z@$Sg?y!+-c`L!SJ@*B@7en;By4(-f${LOdrNc&lKY!{v?|3~%f?19Yr_w?aM_Uri# zmiRRDQ+_?mGIsL~hvna#k24LwUTaU3wU0QK$IoX>bgUeEae5a8zXAV%{26G=blm5% zEc$Tp`1^n?Z``@2Kl?lheL4HujMu#OyOf!8(|2RX`0?@n=-2jX&bz1Y=6c5L>DM`H zavX#6ej9^_7&hs0eRf>`f5Wd&=^34pPu-5!^FH)55C2D#KT^9{&ujDa@1)QC%yXyS zQ{!0hc;J2Z*^>8qEp`3P2A(%}`g!E%rC&f@(l6rLsopQ)ILG)ulTg0q?ubh|lYz4R zH^BFbjTPVfx1iVW-)Y$?Su8V_dhe}p>VK}z=8FX#*WnY$#9J(!&iqHNn z{yFqh*3#*}y$`$p=*#Wr$1`L#HV!fO(Tv5=^f+@oOZhQBj?G89zgYX!g$M70<5;%J zYky8{*R|uF+E z>y{NcmJfQ``hdlHSZ*+MyV$`zf2Q!SHqrgO^3h-9$#zN$H_HltVKeWdW0~5R#ykdn z@eA+RH27wDWT$UZ7yGXJlBxTHUdA$PC|c_k*^6fBZret_)qM=u^giiJU=ie#F|CjiczR zU9g#N)%iv4)27jZJmM%koK6 zwxk||E;cZ~BaLZyd(N`RG=K8|2FP|B>S~^C(=H1R{Z_HfvZLECY|Frmud|o$C_aSXD%7|NI#bYOOTA$c1 zJlVF}hkll=v2w5(X-$XrL-*ifUF;BA@n3w5p>0zABHJ6k?c8!2U-a0q2Y2(Fbfy)q z*m~(;8K?LMv)h?p=$J=eU$@?+=-5&z;9{+4H3cA4$ex)<)kDGW8XMYD9a4zpc&T6WuM;aA(@ zFT2<_ZfBh{j>2QBlWoy{VX)qdf9apT39snT_OzYZ?ufs203OO&IE{}y%&*#tCiZXJ zQMPfqY_%zRW3etvN7>ofgsbkYpY5${@?hWA_=z6o$-1G};1Qd;p6yMS-&>WhveI{9 zicIsTtjHtZjkk0x-l7Yh(S~w_-#EfA_HN!SPd1e-wk>?*FN|F#dsZ2BGVj7bnJ4Ow zHi=HrOZ`E+N^bJfXXxv`q&%l7t_*5Ipm2IRWF&S56r+$@}e#UG1T#GjR#HV;w*^aa19_S|iX^UK2misSq zQ#W&7hnMnUqwwhV9tWY1?&jCBNH1Fr-r_NMFIlwbLAS)PjG=|G+KWGBlusL9kI`Me zu`C|JB)K(C7KVd<3m0WM5qDC`&d6Wz@38g?Pea$(AhF2V-QB#tH38erTBIx_QPnt$XMqPx*mYaAw>$Kg*x@!ZF%!e9JzT zX$g&L0QBj`wSXkY#h-|@-i-p#$%-0QkT#rpX1s(mJK9(U@AP+*=?y?e%wFH zb}DPxP470QV}0>}tZB1g3C3EGBU-_S^hvTbCEpKRE8X4^$a8n%_K z3a{)Eo472tQoF%-VvxUWZOyl7D9biz`PAR`hz#eJZ_HIMti;2*;F>aQr^S2FML*>O zfB3M!C;niNEMh*vOLiY)VDT#Yskg_IF`JilvOSGE^yWibS+}Fj)t)j+kF<+jj{0x+ zQ?l}?2U)Cp|>r%|AJ}KOqsDybP^rp1bg$HGWPn&Ts*43&hkEB9{L#PthWt<-!^f5 zbZKlXPn!1c>xE6W0J8 zC{JBv2wT~A`Q4(k4#6y1@!stg8L9VuVOV`qeO$68P39HphxE_gBtU+QmLtg?ZxY27aJgk$k3o5&8~UG^O`;#I#P>I?lQlugpMWD76L<`|+5mE6B2 zBW0xLwRjj)?qfIMC(ptqe!SNG(6MleUgO@fgU97nR`!$J;+wG~zR{uNO6I~jc?4JV za&BzY%{sY#T`zg$gEGmObZLir&~CP=d)X*D$d+vHb_?sm+j8fa6dh!%opM~0zxXwV z*v0lYjmko+eDY}?X|v>}O<@fm+o|vuP2{?ccCJ)m<}LZ)6B@UtzL5vIGrChA>yxK_ z@fYs!-^(Z`dRb=Q#}2{GJo=|>+31W7K)Y-iY-N|SlVq3tg@Jmg-gb+;*f8?eeenub z^N9}Dm36X>WEq3Ynr>o=Zr0Z{wr%X2yzQ~@l&u|8hkVK=*6M5HEL_AVY%NRthP_2U zXr^7#FKH93+juBmmOF6K-j!mL=x^H6weSb0X<|$2Hm<`@ZK9)XC0QsxGoIO|^jq=| z_e*ECAGlj5$|`--)|h1XV2T{ppWt({3~eO4=w+uvTjJU62if$~k{9f=ZOWo|ut&G# zDG#0HoVF`zQ?K@gVd4vo>ut+mnKI3%>>2--K9IfiZ<~jQ@T%T*JI}IZ`DF7Mv_bt_g%Mb9j6|VzuU_4tE@1z9%w@x1Dj$mAy_2N6Ocl>N; z99v#vW9h{Ek`r9PD;kbD)Td6@vgfc*VNic)eArIHtb5ZM@6eTe!E23c@~N_wmky?5 zy>$xy;F9jakzBkZ*}3o%&*D)wvt5t65B+6Fwrd+lpXe*w zESt2=SDWII-@D+S?SnV?ETimFwhBMy(pPzK+2&{XNN3W}Zf%>`%k<%g`eiHglg%XG zb;7>zRN2H4d6hTjwtegsJ*#wm=O&Y|mijS02yeztv!hV6?-bcpW3 z;`_8mu5o~N!p_`0g3;xqcU|<${l;(65Ie`w~5IiM&*daU?efZYcjhwD8 znUXzZiKpeHO>~RH507;{?FU}0pLI=dTX$XV z8}k}F8=rx_>?$4{6NRT`h88*XDn6!j-5f*alky(RELWRD`&mvtk-umuQ*EhJ+c!3F z9pr?s#s}N94eZP0ZT?Hf;$3|ZyE89-3*V{>M(bu?mM{B&E@Nr_hO9EyA8=SN@RiJ} zU-Ea{q1kN`Oy;pL1v7ae<##`JOdeaQ@6tv%ZF}3G?P>bl57}(Ha8nPbGk8wOShia{ zEZch8Cb5(7NT*5MQ58*cI#mN zO_wnpJX3BkWo%Dd2d8vc@+b$qaevIkYx#}sVOfpEG}cvotDhocjc@&ixSe=V9?J@c zunkPQkG&^N${>gBg_*V*G)tE?WWyCUQ;uF!+ zcw27tT;;&g_;%c+CBIWVmrS-V43nqYQpc({pU_r6thUi<)R%5*uf8-d&F5#kMc3FZ z(|S$0*2gk(9o^21+c|zBf60nHcf8SA_Fx<8PaDqb*mK#X_44>)8|z{l)BffYn=d;P z4|vD^(sl5)PONX87Z1^~oII*tdN%#uE`4M?=96o;S$ze5(cRdl4#unc!a)6^(~?OZ zRURIqsq)yQ`#1D0+wEO`HKTrj}P_OD^sgZR&RYC4cGcy2eEOwwuQWdF(X8d!p>f*Y-l& z=r-DjXO&xajXlx$dbLMcFvKp~@AksGkI&F%jK*f+8=ifizN5Zl>j)a^j9C`zWEV1 z@mPPBHxG{q(+0EkY`vwYco!exZ65xOcU@#9-mH7+Z9Rl#>PUXoH`AupH~N4-X$r@r zk3Pb!dg~ZoTYc<5Y@l|!kF09Hcukw(Iv9w1@DZkJb5?fjUv}H*!k>B4ZWHTZy2)GD z8?VtX)#t5W*L6&=4)j6GCe4m@>AT4p^i`hr;=O3eE4Xc6_u*cqzLoBlFJ2t`Wnb5) zuI$cwrQD~!vAMnPBX`GFZA-Rj3a{*tK0cIXOxd5yh6DfX7sbBV2zlfOkL?DGrHkZ} zj%`Y=^ibR2jjlyU*`yVHc(wgj|CaotjPTD`5q|KDehZ_@f&p^sKDqf=uJt3W=@xJ5 zHOjWOtDJpHy3kr?Y$kk57t_S99y`FHuIxPJ^4j(0wc{|q(1(xP zgg*Tm-E!a9Kwtf9n~@LF)D8EfS#64*bSY0hb(AdHkak;o?Di6_syF}Eu`##o@L#go zR(RS@i_Z~X(HhHH?9m}uBcJ!H&5$Sh!814_U-}oe)xO53c^3xokUq-Kj-N@pUst>6 z87#c7Yty&Qc^`bjFML&B?II)nw)7&;!nl>mZ+4lXbAMUS^qDbGCUxalWBrb+ak_2f zHNUbGF_#W!+6e1jZ`~qmx1W9!YTOI!(zof{zA%+7A~!ZCo?NHSxFI)~$a}*co@JB8 z<9Od#q_^<4yk(1$Bl+`Mwla^#w%eooeU=+X`1?1l`Rw{NZFG!1+@9^)78A$Dhc*&+ z@|##}E03GDhuaqKvfq+JJ1sj5n(Bk#Bv0}YPRh?U@-A=v7KZRoF8ap2_*8#I&&9L& z8?W2NW}%HO7Ox@WSVlfd)Q5UZUFJ3E3KMy_opm)9w+)8W@A5(ueM@%racGlU=g_oW zBj0(jG|#=g`^I?6USo_*9&;|R*+DjH?5i)zKGGjHX}OStx|AmuPqwK#@mc*$J>pe=zqX-y46F-h$tb>RD<0M*vZTYpNqa`u>IY&7&!y)PKijn(!Xx%y z?MqJE!0o19p=DXx``S917V?2b*efT`^o#P;OJ?KE_)yz5Zd|{3uQt_ASav+}9om!j zsruj)AIeX=vf;XyT(+eSvG1UlF0g;`wr;LBMqay}^Z{M#zvG!UX?)#Q?Io{t74Gn> zF^{r|!EJ~=n4(MHs~@8u?J#-Nwc5{X&;@VkWi#?2M&-d0yIEi9Hf-6pb6bz4#<%n# zPhoJK?b$eN@7<=tB&?+CcELK!w)Ta;>B{EC!?aVj`yuj}hqv(8_;C5e*nFG5ak^}$ zw~V%BXqbn`!m;|Hu*!z+pU?)M`cw7h6T62lJVkdAJ2YMJ?6zr~F86)Y9Oa20aM_Q0 z444jf7&?-6;>-QU^9((C)8@nw%$;Y3aEPvTJj1i(M#gGOTqT2L zsh?%Mj-D##Z%mcBJoVObmI<%;R~xolzfo>ySvz*Mi{4{wH!j=7dX&u~XX)CujBXRp z!nrWDty34<#|A22_|4xoiw$f;$r=3=J_`r2PQ6N3%LU)`g|L;KVvm)#%^HK{N8V8n z+iTZz(@k;@IuIvuNFU_u8g$fS;Yr=Vu+_8tgv}Pe*3O*~7 z=hQE3;YFPnznz}tYYghze1nI)jA8bNdBirR30?T}zUU;|7`jc}mwcADuHligl6~k$ zVWe(T&(c-;4to0F)I*p{ruB!+x^JC>zwyZ4LtbKuut-nHsXjG^;G@3H$MkKZ)n09j zPMCuMwpco}4CyrFxj)l~)wb}uKVk>e9rb5lqb&A}jikpM8w2BR=hA26LA{N~w6=q> zSQh0eZDXx$P~&6AUObna!GGD#U2<(6sWC`kJTPw#}>q z>*joE!@J3+o~PxTXZRb-v}fY5WXX1AzjYn_)pnFe&*)$tC5!qI)6<31M=DYz&bTA;vRNbb}?P_Gp{3E zaF24yqs$#g>ju86>o)Eq>ofJ8*Hg#vw!UdsGRa#yw4Q2PW!7V2ARXJZ9U3b!?tEHy zwPV{8$vj=Ra2DOv%XLS(UG8k3wv913h8@b+j=09RsUAd4#Z;lZAX>|ld-yv zHnV-gTkVVg!nk-geand~*EK)l9P~?0l~tYSg^lG%l2v`S)JAkzxWv!aoSELYtzB)gH7W>^_JVV zqMo5OU(rg>ft_{^f70_BY4vm2qT7aN(Tbn1ZFko%yNXw}S$s>c>f7jQn>1GUQQIK> zn)24Y?aMOPJ8wO3zp#|8Kzr27?OiTDXtQ`8u37Iyn&?z=%a-~b9C|N0@=~I0_O^|Q zyiB*r5)QNpk4EGzow5=y;O`pExef8n6na9bb2@hiE>m_TnHNVz7 zxT0_FV+-Yrr*&(b#+&hBdBJSjE+a0Kg;(PS6Q!>ALhJ9g;D# z?Ywun`^wlNqjeneOJ;cOx$S$>Efp&9ngIFOB&?vZmAU(-dGrjdV@Ue@!3 z?&?RkF}-z+4bxYfE(`C_d&w#JW$SL&vbX+SyiyJK&@8c_S zTkj?7g#Cio`nk=-VmsP4XK5^3HYgmFwc1F}#$&$06}!ZyxR-8KUTwomI&S#8y>LUo0 zsIGOJ*rM;sSKFhkZhvOHg}?G{C!Q%QeV3eN=jcHElo30-eD@>rE;`vFn4HH(l%YOS zKI+((`c>P=Yr75p#35P46D+aus1uEON-p&kJ!#lBZ91QPjlp#)C%)ntT&vH?cknyv zYn@~Np^JDHX0@k0Vy3JrM;15A$W0_ypQY_l2qYvHHes z8hi7%eDkjQ#l!egm+^As<@&Zk*H3xTxyDuLz1p(hhTO$N{aAf}sGofvJ}Ltp^$T8K zr>uBMzHK|(hBopuW^=#BSJ^%Hq4WLVEBo`_W#KQJ!Z>Y&>!qXdHhpl@PJ=(~Mn2Wf zx(r>*7U7fn)+e-+r)60u)~62nvTf1GjwN4p#zT3l9kDSjeR=Kvj=sjlwxv(C-*}{K z*)-UPEYVn&+Lqj0hactUI0xw zjn#U$o!rLy5|fkolDGIbwqP>l%FKZ+VG}#z20?zVsEg zlB51GhG09>E;via_)hCSY;%z1@m{tr?6wtU7GCmQ*NxSL@*9 zH`_#dM($BZ=%F@+Eizk1cmXeYsD1HG+p=@)85(0@yTU~oN|fpHbstX6tslI8wyIy^} z>TS2^wP7t9+o9~XFvU)8A1ukk=V;r7r}#vdmftv2CZ3@87zkd|E&i(SemTpx@T*VD zzFU7oA7UaO<{C#+R?3bvT|V&!tN4w+HLjK~nF~kcl?-AiEQ??9vK?zoEWG9$+Rlw} z$9Kd{UXWkE3*j#wv;O6S!dk`cS@`IKpWSf4~&>^cOS%PB9(x&>F`4Uf}2o4(s`HeR~8uUoEhHCFJa z9h59<=~g;~HsuTBl)>v^&xy~nEw_14Kgu(Hm7{%l#9o=38G~^yJPWsZOD1^~-oj%H2=kqjteC z*{umsc^|=rkB1YV_`YiZ|On3MO!k2)nzG9zbqSpmu-5$evlcPFWhQJ z-o#lvN?v1jdH7pT%OReHK{n<+=!aZmj%}?|crXv&=2KXN$@Re^c@wAP?ARB-)X|>8 zM7mv`X@e;_Xe6U(iQ|lJETdhlzsFf!r{CEoSV>p1)n;4=uWS)nsYjl6whwJvcvNQ$ z8OO1q+AP^c16{4)HZF*6)(3* z-z_ZSoBk*n1C#q}wk>_xF26?uTVxw=+bVh}uXamfz1w-D7FoZ6B(Q-!{@uv(Y^Tai=l+ zvTd)}PCAQsaQ8TiPWlZjeOF(z&5`G@$q^rUrETh%OFr_*>ayz3O@?(0pV_wg+n%X& zUS*3{W1$_Cf*to8ufk?rw2|!-{wYtJ$P>nrH*L-OdB4#ec#F2^BQIsL*TOdRsWQum zEr`2pth}%Lx5~8P8+q2RF$*tv$Y#S<#x>-q4s<1VwWHs%eU-0v!&btc_Q^*ZVZ=BO zHru}YMdMZV)+zGZCcC|irEOvx44lEXVQ%`xU-i3g%bvxs3$G^rRbxZ?P0mIZvP!=T;fyx zDIVcV-b!Ty>C3WeoBKh(^k&(@Wj@h0<>sBb>J#c){A80Fuaj@C7eC=?eS_KcWyhjn zopcOe$%;OuXUX63u?}>rj%|u}*-pG!ADYUg2d<5K_VJ)4F3}WUw&i`5g{OE$7t$Q` z5oWbBAJdbruy6cGV_f1-eyV2~bgS#5jD-npC$88vGS~g!>9TH@F&P|^E7`$TIEbyV zil**cU)EQDP}jP)4$Uj&!8moyb!Z|_wj2Cn5786H!WQ{uKia2~YS_1z!&;p3>LMIy~-YGYdq04*be

wToDzN4)AO=>$wcVa#7I(h^@=<*voe=pcR<*Pr;H@s!D z)Dg=XQ^Z{OQnuB%tT4eP-Lj8a;@Yd?K5;bsaj^ zrL6c?n`xJA3>QZ0ShC5pWU$?8Te4!0HP06hW0-u>mb#`5^I(oH$`=iBM`ra~VIgiM z+_#*Pt1{Dfd)t7zNUm_2zid$bF8w5<+J`>5+M$m3q+9KR!*%J?$Z9)RJLzRi3xCNt zwiPDnYrL*cTieGp!O(Q!ExS*wLm%m}^s?S=chn_)VL9QQJQ!x4YtqBtvOWnf4F;ackb;e@M6BlhV z?#Fm=TjN^1m!80YcEL$n|0eEoHX8*OWo&Y+&lwq{rwrkeq(|CyF>Pbo zX;dD&ReRg4<+cr77Hrmil*tx_rF4!hSKloE7`B=13cq9sm+6Ajy0sl!U(&g}%WMm^ zt-ADK%GgHZZuC*~-TSlcN(|jT_OyJBv(jhbAn!_U*EVZ?iob9auhAc|qsKjEcE4H< z^(bA~P8iIGZ6v44gdwzxuG&bxuWg%ZOPxha*}i5T9c>fer#wDq9nCZPrd?=*Z}nke zPB}5lhC{c-mw1*v4j4w=l$E|+Wz@S;co`#Y*!+Fnx=ajBmo{wYwyaAs|LU7j?s|{m z@D0wiF~7oEZJTDuqW##%p(*?Dp1gW|7?*g+<}H6=ApIGf)lU3cH}U5 zIkCO%-g>0&(6%s;Zux`wuzlf~{a)9ruH>}r<|CTPKm1CT`-9_(avHDoBOlg5msM{a zLfd1)I<0o>ckwe{+o1FTFJlc~(j>86WVkGP#y$(PWPqmh3tjrX>4&_fzvW2Q(yw$_ zxYKUnA@;(*a7I4!To<-r3YLt+v|V;MqjTXaS z4RX@3Un+(7J|-jAnA5iHN`0F~yeLCDb-Cph57wm*gn{&?b$iqCddcH;@G{SJ@Z)}? zE&9oK_M`h9GTBbN$}Ym?^4P(ANf(T!RiCA-Vl>4tG?>Qr|OGmbUfc5LQ6wiNG@z51T_)mHsle7m05%hp93ev4-4vSfs(Y(8<- zHF>6v?RiZbp`G}%eeqrQqikZ<^=eyKxBJ1f_{)yuck;e$BKgKK_ep(%T@@3wKn``u3A zD>~V#X@Yr+5ktpaZpn;1@nV_z1;^BhWhr0%Quw+&W5Tq?AD%5!^wM{?!C8C8R_WXD zHP5z(dG|f(t4-+^{L?=&u2$XFretdjm{;%}Uij{)tA=ad}6=mAsr;A>T*q8WJ_b{z6_7p$u^ldNgEy8 zUg1x^L%wjMuI%7GYFhCH{VHEJru|!v`_O#N%X(+7c6uxnz4Rh~Zd*Bp?mKPb) zPxRGp;WLlMRsBG|BgJkllXOFW;whe%WxYk8@}d!)u8q6t}!2<5^d?XFvfQ2fAibh8c%F$U3=_ASI2?L2klB{ zwJrIpee^5Lv2%1&f2l6^jLgEk@wQxJDH}&$>RPgFucJQ2zvx=7$ z+*o9%Ro?oh4fEj9HSJYtYm4#i^5)SvhdjzE{UT%IQ}Rq>op!$F-&l-KHY`0>Tj?Kq zVBr0beX>pQnAnzl@hdx1U-CNmwRDk;U~l=d2YFOG;VD~EMz`&;U_E2o$fiBbH#~Ju z-r-l*s#jTXn0MMsE@hW|md`TZ?`4*8%b!P)j4a)SK~|LD;6nLOup z>l6DE7p{YcGHCl@|Ak5VnO-`VzTv6yC|iy8x-Z>nGwDFtxUTk%uk1=Z(lNFpzu9*1 zFMh<+eB92mtk3Eb$|UXL7n>C(;c8jZbI2g>V9OW?&5`G^uSL)Hu^aO6OKu*G#ce`I z*{x@2W7DPY!bn-xH8O)yeO`86ID>83rpG6Bw%(0FSU|hib)Q9+b*pxRclvbVcR#hh z#%UR(ONz{+eJFFf)~|G3d|GG8))=m`8po?0eOG0J|KPLeP`0kwx16<$3u8^AGN(KOXDQ{%C^E0Jk--=l$oS{30;*>y43IVjbY+% z`H@q$qrTE*$Jsoq4+^`-iZRCbmo18BwvBz*ZpU6cMn9-tJd82-;cp(c_0o-fA={Kb z)sL%f_zi5S3nt&Q{lF-j1V_oJGSWAXwvRB)@l$>0@@_MI%e*2tx|r7XtvcyAdF*oY z+hSYTw(~l2y3Bm1&b$`ZgRP=#+D=|)+Dm8fDcwUW-beeDt<1k=cYmK6Bg9>_7#|QY%#}@>Kc3O6PvNLEZlE)YPsUAI<^58 z@iPv$F+SNZm`bK}n0#fI@G*wyC?_x-Cq?Njmc~O}8_i^ciW0*Ln)ulI!*<3+^dXyd=-|i47JO@hw^8OWLw|WUBwf z!#GsF?t^>iUp$X`uwA$9zFPWGerQT((U;9tN1F+w<*mLLHk$ss`b&7L4dqDIu%FtO zovY4ROtbtm_{cAIQa|=sYP~HdbS|fivYFZulWh@Q(vRuajC0XZ?!>UJ%{Q1sD_OD4 zYFij2x7)>5=FzxXe%T^;rT?HyxiC!`q3QO*x9D1bkIQ4*wCnQd8Jn43*T>cwE0b=v z^)>s_7;UeIhEWS^sKcf4w|C^_8=F zP#(wc+zsv5Cr|p&qIZ98zw_DN+{eddy!a5FF!I@)!2Rg?(Ll}OssGO7XI2u%n7(Ent(!&OquI5i zeQVe!6Q0(lzZcUJU2vdjJ+$`ZfIjA)T5$_&>!+^0=tkRc`uBfz7(?Ejar5dr9L$1e zIPoa_d~(;aAAJ6%qYX{>J~Tut~3cIwaU>+$N_j8~`6mHpAEwKKdqj9=C6T}Lna z@k(akt_Sq)b87G?J$%fyS>b0gw65lI_1439(Yx`{|2()p`})01ZD!>4aIatPSbFJs zUJf@J!>5|`qxqgwGk426qQO~C4$3QhJg4{gBIiAOJ%jOZoma7%9lHECqdCl;u+{JU z&=t&?IoVl9YEmB_>n(g=^SafWZ>4X-qZuV0VJ8P3~htoGdV)F>bIiX(TP@6ckfPaQqsj5pN;Bf2*C%L5lW;`bl^ zlrh;^*Vf$LU8mkX&Z+Os3~xLyb~IhSw=TxLpQ^k4@*AA)Tl40yXGfcQT=$dV*+(1C zWc20EZ{X@wEt=``o@aY{@%8xZWwgBabC(w*`}2Kt21mQ_{8``MJma0Y;{`nP^m|RV zsUh3W-T6fGFMej!`s(p)v_AeXZv5?B@VoUrdlJrho5`F(CKix+MG zmHXxS#RnrZi1zHkz1qPMMl{{b&)WC%ylW1V?`jRs4?cMg2b${JIUjBJE_DX?K7Dl- zW3Yo`zpocP{(koRPG01*wGXR&&gFMqykz-2`u*^ciTc{RHR#l{c~Rdxk=kZguF>z> zV4$BTbNIfW88pwGb$lkr)R8HCe}6lu19#^IF1ersO!obp%#uU0$lW6=bH7ZS;mLto z?|a^8X}|ZLF5T7X6VI!A@BBN(zwyrG^;OTGe@o5&)?7dKT#n#3k8+1U+|I-N(wPq) z^RBwwiJKiBto?XfZhDpnjr*+kW{|%V(r|jEP3Leld-HS;y_2E#<#lj^3C>5eYlMF| zKe+9+j#uN^tfyeoD?hAXjb>2&_B5L6N$3CICFk+1zT)t_eOU`DAM>&jn^jBLr!~4x_vDm*n?B!W} zWB#k}ozHxtVf>69Z~jHzK^XVi)8um?K5|Kddquj zqosJGE8fThO!G}ma(2zt2D$75@C9*(b$@C6HQYcE#%9;V){VR7P1@9eSW zJ-Ol^Ts;5jFaDqJ-OQ=p-s~1DoM8FM{Uj&j;#_V;he}64dy#KZvR;`hh+UYq{HXwWgYF$-{}6Xxua^`u8V&1!b>!yZv9qMxibsl>Lcu@Rar(S^P`u?nUoktYD~)TGtq}y0z}-qv`N&FL=i@-pP2lqKTZ~ zMPo9hX7we{;D>i>zFD?sygY4hy|_8gUgi^SJg}$t$I*Ll&f|UYf=?EDvc?ndoaG(; z>q#DOEgXAi9=7ily}e)Jlt=qKd!E7dwfNHZtIs)tW3Q|2*5bqU<)HU^gX8w@cV7G# zA8l_>+~eg}e|iSfdUtR3kN507oYs#1en-P43^auQ*UR*wdo*6X|L&CrGP-Lx@SFFC z@#?>`PA>RMcFm?bet*wyP1eP=menI}yqUjuY&GPrSif}jV;|=J&ggReOT)$4S>G9I zR*SiO)O1$Wojk9u|JR#3wA(M9j0eZ7JGp#)lnd{ECZjt?_fsQ|;(3N=-|xM9=!vIl zQD1vEi)xrz#52DE)^U5p$Gg^l_>yU~gm2c5gSGpLbups*I~Qy99MPudT}MZB9k2d= zz4vnmv`!}Rk@sLF6Z+p?Yt3cP<#bl87jJ&&nH@vovS&50>;+^Udi`4>iRr&h=(3 zcyM-?H~phe{jO!7OqEIWML+M=e9m$=%TYaZSL{h=IY*nbDDNlxssFbQYx(Gv?)Ara z%>J}LdUkz%Mf>A2CLT}5^~t>d#sBeU{lSSZ@~TGZ@k}4baDF|^JXF^j=lR~p*U@$T zWZmorKN|k2U3$RsbKfVMw|{YWpPa&H{LX?*qL=>Er|#_ych1S{t7kpaH<<3{{Bktw zxfj%Y^9&z*=^=ak>N)fJxlhSKeespr^|iALwr6;<2Ah4qcLv;J?ae)DUZ33Fo_~uu zvtF`D?+2Ips1M!$TK9K{UcC6dTze0~XRXygebo~#=CJyx8!hfno$t`qR&20tuh**W zcT4Z8%Ts)v?+2fn-!t@~_nxcgogZDxbM8U4E`PYlj2UvCJAmGYV|;qFA8h)+8cv_I zRbR02u>0h7|u|{j2t7A2+Z*b@lMtGf(G%!15(p>DZ8DAg2 zT&YifoNe#w%USgqclG@CZGZfd#b@8ST|8>z?^pBo{%YEHX8+OSe86mOKe)N! z(RML+FZW6BpL)H2d`a8tOFg(bTa1T!@GkFQ_?&(`EzX~eZv9zP$8)&K&wif$?!2?R zKblUK+1qUJtNri4XC9XER9k%4-2JRQlh#_NHR_CPh21&yIrpu7em`^h>OAv#UgS~V ziReql!RuM?2z&9C`iqPknB^VK)k;p}fY#XuPde!-*S99;;Spc6xV-8sj?Ura8T{_MpY`e| zL-&Af%+$c|xOj96WrUVD1y_I|JTK6mft@h^?^@O1w5&))C-754q)d^y!OzlX(& zo-?bD#_udPn{WNq!5qVN`bWERYX4*r-SL&ou8-CD`hGJ%p8xs{SNi2WA8Y(s!@XRq zW9rMb-oF0e`C{P>-Xl46pYNjc6W+xPpD@vrwfH%^`L|c~{vNgF z)&9!|xWNv0>MjmE&2X@&f6wtIzuP-r<;i{0n$Pw`|9{Tm@TE6;ye{wRU%z<6Pw>Cm zyyvXsjU3;zoZwEKXY@Eeyx;fF{Ko5-H@MZ>Sq+D|x1L(`@AJ1N?|i4^XES)~YEFN+ zwWgk0*GYrt$ZV8}FoI)j<7I!dOOC?G9`(yN_{|0^_@=iQ)XzSdo>}~Ifk`j#c(w1t zKj*VoAMYLBnL{)1`&+x$TDnH}AFR&X=R14P<7W=5@72BfuZE`y?4!jsSC9Id5ghrg zUd=g=N3DY^?^%Zzob>sunig+sj58k9^YEn8n88+N$t~yTm^rvVKGCo}7X!^`Bb(Nk zSN_5u&S-@b&(R0Yt8td=3*TszF84@l*TJv1dXRUWCsXz8`Q>&_%_v%=p&ZKa^`UvO zvd>wv5`Ox$&w6+d2Yvel@9<7`?@VjF^hs~DsqfTSw=h@F)^+dKW8aNC>)?iKJqb4- zICBQyXuWk^J7aq|xq9Y#|MdaZM}zy1hM7^k8KW~$AG-Yf;kF)W^L<%O?33GI%v^-4 z`aC&LJ!j-6tyjnEQ^wMQ9=$&ue1u1jdCCjE`&FX<#6<-_8Vz$^=wc6i-pLD{&RzLE4sv&m zIZx)*vsrFVS1bI#_`I9PTFYq{AeK^`{Z|tFk z{(U#jWJP_Rt8HgPR->zT`SO#0_KvrD1UI?CiKhClpO4Ss`s%rwR>$F$zxD=iIexWX zzBy|?=3!3K=ZvE}>(0UZ%;e}8PwnB|`ceJk_DTe55Zq1dDZbtB&u- z@es_^{Lz0nqC1%I@9*2*i`5W4 zj^xVOVqJY*Pv3vM%iq*g3*XO=pUlR2(ewJyNj}b`a|4$>>vc~!ox8Axr`}a_G=&rW zdPb+X`Cf00dQ*GzG&j1epLvvDe=9T_KZ8GW6yM2Y@2GmxzuwS!FeayHpMM{meX{y! zhVy1oE%4q9yw-U&ec#cA?`m;<&*cBp|HZmzS=YaO*vr;i-}t*3r$*XbGyJbcKL3%w ze;148zH)h9r=L3$oSnj^A0`*}2x|Kg+ToBQTK z*4%k%J8Sd*+jl%3Z}z13>gSFzkNN{Y8mZTJmyD$y{yg=b&*E#Q z$-aBme%J9uzHodsr{?l4Pq0>#-it4Nm+$Bs9`C*SX2L#P+{5UL=gfokZwBT52X8XI zUdV?0(mUMJKbn)R{IK_ZqURr&ohkLg$0s@K%-F?GZ7_YNIUer8@O;tKdexb;`|mxT z7UPc=d#(SY0e!hcUyVC+@z5I$;Vb{#2l1a!Xldm-}4|=}Xa3qUYQ{A~Ac#h_+ z6~@I0c6xJ0cGRH_zxO@)yuQP2e&>mQG{7Bx;RNexJ(;6*c~bYOw}-1ee%*Vp=rLCB zg!QTBeWF!3XzAK#)clK|`jd0#jDO}5P0dz3!&_XvOB?#$yY-%XGQVRwYk%;IQC#lf z_6rxE<*80;et&bNAMN1T!{Z-7mkgDYvz$$M4AuOy^@A;fZ(F=|Q8tTF2ECz2xBU z$H{|SJs0Ucnvz*O{o$zJ>goCLpZd`s zZQ*Y3sW+zI-t~~o(JaRg{#dW=XeshaAG^T(3{%e1A zP#4|d+-#Fe=C89auj}krobSvYukJ6;)jxA%7Q!(Wobj$Z=2Z`>HAa1%_vC~IxbUp~eBu@CcP_qi`fo<{TC2~ZpN!%GTyW~qdZ>SV7rVYM zf3T|S)&6RP>*M**u&d*Z@k~$$#OK3)8U#k z;RJJaq;7LdO>zlV?$g1;|9g%v;TpSpoeRIS$)x^t)-2GW^KhP;-XG4$Gf(lPcC}}3 zax3Qa`Mo@kpB&+3Zajs5&%8gn>(@B{mCf7NIb529`!^$>fuZxO@vS}g?&q$AH@d(P zNA}4kS{Kjf#*6=8H9vF8gYediI3^?OZvE($zr_R7+N7trXuFzopD!2n@^SaGb}rgC z_g`P5m0q6XDLwHQuF>`D!SB`cvz{Di*E-VR6KCyV#aEC$0mlw-KjsdM_AwfzNqc+Z~us)KrS`TmG^d(|I|a^Op~*72_(SV(C5!PQUNm++?{A+hsGs+#?fhDs{8lr5>DSzM{ptqialQ4_ z&wjEZQ_sA5zg()Tm!Do?^1<8GR`+UxkDk2my=6aK&I8?gvX;-jU(WG|)@mcy=<0mu zOq}tD&bh~}nf>PLTzv<~gnYm$$J4m_mN$9F4_v?6&@ArdZ_nEUcQesm`6s8{qu#jH zmHO5Gdw)2$2KT7;eTt8+)mVP(`1SkV)pvTGqrK?txBka>>ylG^t*-YzxYbA&&ab*& zjs1I%Y<_uD>%59NYu@$jnhb8f(HLC#zI)<6{11y-y7$2jCwi>i`ruFRc(88av`@Id_-Jaa&%MU; za(;SS6JM##-y<@3PjN|KJtn7U#lt+UaeKF?*zcSfBp>vVSH0U$*XG^+VCr31(pN3K ze{@Rwt8G4T)93e-Q@jWJ_L=h!hV(VpUya6;u4w$`Q+)L*TlCI4xa04ghhKefed<)R zXTh7j_DLhZeLj=6gAt#>{_Ml0`nM*#=+(QtefM>i^PFw%)!nmd*qVAV=4Rt_&G;Al z_yEH-;QA@9_OkzfHGefs?{AN=iboF4v6&=uu-32f+MDcN%@0mE@BTOcFCS+&@9>fv zcmHO@Jx`5h)|{S};1>gJerE5^{BLb=a#!xU_d+=4*r#i?XAbyN&+q-}j9>Il@6oT? zeCo-wJDD)c@{#{Mb&f`A`^2xF_*k#u<;)rmjy*h_Jkh3S_gu?6=UadEffYT;OqrMy zJUxq{)b1@6&jH51y9bM*Dcu z{^zXv<+=UV=nTl_)bvhK^EY4dg(KSIA1!#)lPr1Km;W||@7rITYEP{&qA_?~H`n#N z^P`TwscDbDL)Nyq(OxdHo2=sFqZN%jn};<|Hu#UPWS|*_XETY8(dm4&*PI{TaQ}Lc z#+zq*?cF?`>u{!?Y{?4UXt?(<+<4i!srUBHTKoN2hxMyAo>XV=)XCG-e=#SccYZU7 zPT{c+e)@kkTZcKCzt-w?etLd+@sb%Rv*IgX^+>b1-?Mvo8my_)94|(C;E%`FSW|Sl zXLt_A(=z_OzIv*Ued|`e_TvFu?^T!g=nRoY9H)nJXc*&ob@&DvV5p}6pB z&pM)ob$HwVnMGzWTI9Jrch9`lZmff$dbH->8o}6oJVn#&SEqZ6m+!T*Opi2Jhu&S& z_sdBR&OqF!=Xk-x`Z{_3U{iCSTJ!$Ns9JaLYL9O2n>6G;gd4ofRruNuPu9X>e=~r~ zSsqN!=3wNUnI8Ss3_o~(-|>nzinCBL32M@&2E8`0f*};4eGr1dwGkeIsFNqLX_zepK^5(ecC88FHSUGY@OujN)_Wj@R@v z&;O&jp5Tk`nO*yrNT2KBF&l!YBXD zf=;~V9gb)N3-4FI&n`!E;4Z~m@a(TXy+-4EnzuO7xINTfpXBx7|8oz?hneOb@7tSu zvzni8YuTAZLvY;JnW6jZb7w~1=BzXHlvkNpdvj18WHA{fkD0-_58+s?t10-&Dw^W! zlyStWO;fBv1KH7HQT7GAzzPFdY_jGS(Ax?ahe`l|n_8BAZc-W`#qak~Xo%@k@ zaGAki*9+dk1(SW=r|)T6{4=+clQSD_uU_kU-%Rrx!tCNjb@$F(-t{02mR#%Me_ zs_tkomblu__a46N_gsCg-ShHa%&9xtK6^*Qxm!0cz6;v(=k#0u;rnX2*l%6GV1M&k zKeui;oh{iE%X&6vb3ZM?<$3S*?aLbc&m8I}`F38$MdRHIesE64%}QSE_h@BqoZZYu zM}7GHc-o%zl09+9Gw*)NOSPk8_R%)y>q|9v?!mnH_*&1Ke>jaHY-zajGhcEG?#>YZ z;fF*0<@Ozo*Qpu(she7H&RMpaDgk9xGnXL`^6IByTE)60zPMc;3oy*g(z^*=h$ zP@gwPXI%_?G8P)^@6Cg3(6s+Hf=gP`7j0UvuJ}x?IVT6aZ*BTI%jZaaJ>TAo+Z2?cvVEn5hBl>VOv=*C+ls zOCHNN=hDk8P6qcJ&eWkNv-y_~`@ge%>t8&yr$2exzh}NW@kCv6=shAI;q&QU>UKZ< z!oBl<^R%|hAMR+$cOcrCJw4Q&=fg6uF;&-lVog_b_f_wn)#U8(^`5-*1RsC<6b3%F zFZsRqa$TJ5mG0MTEzz`fU>^L|spgl{e7;A)OO5LI2rnP_zcuA+E`R1VoUI#NJa3k0 z1K)S#)d%xixBi2xp6P3@^l>(sj@IM>R(!^Huug`>XCE%-_xKj$+fN?UT`jDWbucF5 zr!n(A{$_7EtfTwUv^dr@xjdXxH?_L+;J)YZtB?J+jz&FK58nlQ1jn_hSzq++e)zjD zp3zC|&J&zJHP$OkdH&{w&g?HQbMSsSja%>RPk;B}_8qBSuyjWKtgDUv&vP()Z^rx6 z`N1ZaooVpN?dxkb(x1SM`^`~$bT+z*F}lM~k25h(^pFAPi*xlp3~}G@ zjycQQHM*6H>XUi=d2I~o)%jO9*t?G>c`qLQ_Wbs^OXzY2>9Y@Ws4gGROIWY|uKn)b zo}KlaRRekDSzPel_xQ!Db-BX#&2#%(FK^Ba4F^a5e10**ftTofb)zL~vRl4jgiAHi zb?ZL5i$|TFUfUb~o2P4qhmLAM6CU1s^iUU^a0Dy&ZSmtPT6dlIkGFDb&!1)rpS*eP z{o-{__)Z_(zxC^XFzY+nthalo{rhX~quF_^C)JNWFi&RoT@1b_(b~Rx!-wk8z+N=3 zx6LxTQ%B!>f+;*RHAk_yKon!s!fp zmP}SRIB0|e+~k*fvJ6Ht*BXrUMECOazKkZ$zx(iw_osKh^V3@$<3$*m8-0GC9PW8_ z`Yhb(>5M;^)%fKe=_LWXW)BYt!-A|<{2HMr&^5dbv*vX zJRVv%I*KtpN0(~y*&LmNv|TU1+&-(;uEY26njtvmRZVmLFQ4$`{72WFQFBxM=kqjv zb4w=pi=O&KzcAVduix3{DgUEC^N-)#pZ&!Xe|*GSYPmCfC#GIp(dEK1FR~>MnpPoAp z@409D>GQ0wrgBD;eC4xv(HOp?(HOj!=j#9AxH<>hT=D+N?({k*)lD9m-|n|xnNF_B zFq9syPw?W#m_tZyALnDGo!)#tKs&b z!LxgMbCzr_X6{zbU2ENXUwmeEG4TrSynB9M9(1YyVtL)X+2^hguKb=^ELQGaG~w^- zL$&@$6Bt_m=nyA-tE0WnBN}vOKCt?}c=gwF_V+2A<+{9gJzA|lm~fll`^Ufd4kzpJ zj&48GM>h6*dxibpqgh(tXWlt`&tl~nPqX&-<1f$W4iDzd;};jcYLHI%{dkvV>FF8T z?kspxr5WMkoGWldn2GZp__00j!%x*F906scz-R{NvFRyypqt<)cx32yEvj54y zbzr!scN2ZN1I%&pR%h=fxu4lY1HF3I$LQNU7Vmo6ER$`pu2=HK-+e}V^u^oq#owNd zR{T4^&2i4HeRIfu-p8LaxL(799`%5i^*i_d+5PcFu161?o}2poUE8dj&j&C3Q{Q_^ z=H!X~=|33e?01^qDdYW1m-omdo<{T3SJQjHvqfXR$IY-ApfgxM_ewafEAOv%^F+(c zVd~ZU`rLDL&H3)lT==e+2h3>PS>Sv6gvq?<<=xqYOE~$EL7pF+*2AIa{SErX`1G3l z=#(z^C&y%&ygct}4M+FeBplvr*6H#rSsprZ*K9qFB#SXFXYOaH0+T=5Wq$rpX{RU|NU1U()Qpect+LT0icuPxS?7{e19s_xc;) z+Q|nkuixI`j<=~DUExpu_1eDQ-yG{vj?OavzFB>-$cY^0IoyA>C7+u~{mY*+v9@^G zJChpnizZ=gPqcrrlIO*W|7iHFU2b^4e$f1$C->+L?mzXDwdUz;qZ#e1C3*9kW-RA; z`jnCI`q?|b{KGN4;uA*w_L;hJ7I!(L(V5;mNZy0-^a)#_fwk6 zlP7b0eg4~4?xX#isp`{XU(Lg7`RF^G?Ei8H6YkCM*8gJYjJTdD zuNba5@4l0xU3JNk4EG%GH?Qai6K%#2X1T5HWH;E-fxfGOJ#^~5GrFGa-8n=vK0S-( zGcz+j9yWJtd9}up#?}QpxlMM>Fz=a3yipgN`#YOVz*k+g4t~9I2gzeG_O6VMWAOSEJ$viS;qhKS;yq{1K<}!Nj$(yV7^-hJuVylPdt|XS+KWbeIb7Q3 z1Hb#k>9gQ{Fz2kgq;2+=e>3S`YqI~N$ywYC>QUH7`)VZT@A>%q(ey_jT3na>(i`4g zSBEtG$ysOOKc0)b*;VIwIKEuFy}qO9GUmY-_h%1YJ*g{p>o&KXGehpy^f`m-)Ozr4 zE}2cvGQ0JDdfM;0!3vJ{{>8mn{oIMsk-qT3^{c17!>PJtYv1U4&wt@$-iv9i(rXRf zZ!dcJ{w*GShvVLB&v4I}N9NvMbecP!qU~t0j+~L_YM}1S+#1R|8sh1<58uCe%=_dQ zz07pHyv%n`oLlGh&7-y7n$3so(dei8CnG$BBlwy7Z*ShoWS_TA_`)DlJA{BYle>rZhOe{pWj=r`8tp8dx~Cc+w@ zKlSi`vk6|Xn(xsE$LQ&qd9;=X@+hBba}IX&35!~3^t)=U|I|!BINzr*u3pw?43Dzh zEN%^J=O9hhg(vp$aB+?OQ;*-&;aOg_mX3I&H+!k$Y_-y#-tDQ*at~+oH>Pu0PI96) zSkbo3=gwd9N{kqeGgwfbC$lmAMWC&KXZ+4YVrhU&xBnKKfcGO^G8E* z4xceQBlV!y_fhZOi=XI23m(d;&4qvL*OqForT$C%0E z%UdionrnIEQ~mgXU(dJqqu=?+-`OXZ(Nizq{_2$0;;zqdFU~LTt}PndtdD1 zQ~b>ZENjr->{si1zxp`GXSmacS2CqfJy*->{bExCPP9pPvM^rtS;x^9Jx{aFhi}*3 zcfMflbMbeE;NihFm%CWGBmeN{^S^VOypEP|t=4chf7O{+@0lUFY{y6+dm zIMnO;GP^#$Jl{LG{;JU$Snm^VYlX|XZVj5L-DmRS{c1~(ysBpJ1ljuO{nkA0>G%5a zSC8B8RS@4brz2~#J*_XJ>4;OX3 z>kerRM%F(*{(+IZr#kw91(eP@i``ycav;4w4j`Re7^UU7G`sY2b z4ttV*=TdF`8*3lv)pP0|r+!D6t@fuU+4$`AWbWJZ!T#q=o0~abU!T9c!Hxdtk%wfk zcPpB{b3y09Cd+yOTex5^#?EPYCX>`h2i%+A;-Rw|)UV8%>vH|>rO!Ccc=TC=&L6Kb zGhXVyccdCs)4Xz*;wyXQRUZ1N&UkOXeQ2CB?fVSP)Plvn@U#x*-weZv$2)hl!0no1 z?OyP$t9e>`b^ljBqe*zSULk=ep!F>L3_C3=jvfi?s<|=vT@D9hs)UYwR7Eh zCr@UpXK>epJi)cP#h1@Jd;Y0m{W%~0VEuAT@2Sh(NM6x$bu6EKSIgk^UinVHOwzv| z(xZ3In9JM4i!lzr_g{VY;Q}i;E}qY)_TZN@nzaAZ^T|Ja=;vp9bT(8_cK|h7oOQWeX~~$H@DB)=V&hmao zx;Gi0`wj+rlI6_@T{o9{Kb*#?_wZB)dOo$mL}zk_hv(}bpVG6On|W%$f%n#a*Z4a8 zXQtoU<+qNT=hMYrv_~8Lvv=}{m-z6#5XL_}J#FCo_fB=|f3hm}#S*{Yg{$+Kb9}=` zP3nKO-#IrgYyIl7S97TTXgs>OQ|4_g8a9vM#+P*e)f)Wh`t?hFJSC^`W!>?|clYwM zCXf9sizhHuH<&Y%=rO-N|L8k@(VTh6WAK=(v8C1fx|{Vu7CLjrukX}Q55C@A=bF_Q z{lYyOXPq80HuvQ%ma(<=nQ+?o-qFTWdOUmSIa<8mzRWG>t{I%ppFZsir@8aD!cBF(F>RAl5MjncDzKh*WxZ`dgS-?jEA$|Jd@w?r9OKTetZ~99P05?HPsA{&yodM ze`}gkd5sO`#VEfxcXpQ#-e^l!!j3n5`|fl`=(is9zk0o!M{D?1v@{jL9Gr@m7&8mj{z(YIPQ)8hM1=)79MeEZA+4Bq2=y=Pzb@C0w_!JIr=+x-4$ zLd!oq3N!bLd{%dKN|(-}B^kD+e4}gL(fd~y-pTsnSqoZuelWpJR?h9@jSlB_@0vVL zR`KE*?Vn80ay5dPGx*!84%W z-l=WBxmx?9ll*uF3(aT*N6&*(ea@l#(VQA#8uw+1W}o+c{GHxUopq}hKfzl}c+l1N z&InKUZnKAG{AhpmRPW~wyglQ>_4S1AXszB~-}o#pv-b06FW;%>NBf-7?>xRbHj~ZV zeXloO(Yu+={@^DAyzIS85A`=gW^?&d2M_$s#P?60;tfpU+;!$b@9JV+Q)}(jSFfA} zBRurYp6}M-{FBpiGxy9ZKH~SxxjMY>`P8HD)nEO&YvM=4&U4N#rt_kfSzg??hyPDs zbVOHcdcHXY_ji_Lmb&QMz4LzgW9U8D^hPgr z$-=zGM_;hbVXo9OKeXljV4S(w$MjZ%G#%~H)PDB+WS*Z~-kGzk*67fl^oE0M^j!gC zG8q2nM|oB6%`V^HVB(Kj@U$NuIE5EJ`zsF`;rv@Kr4BnaHaAkI!rw>iRUw?}aZnDjOFwou% zs%P$wwch&Ufm+@7KbgLs*+btx@tf@AsoEziahB`tS?hO<0((=OHcE32CcP+YPcDfI~q5?@TvZIf4uC& z%X{kHIlj@*XFLfj7}U|X`g<;oCl~ruhevucvtZvlfj06C#(MPr$x@nt?gqZ_haLfnVXF@5@1-o|7r~!@0eiV>FseS@|Pu@iOyhP)~UBp1i($&UJLR@95Zj zC@s}vuD6y>_vUQ)dQOhnZ?7=!e1&l`7z}d-_m7A6;AeQMg@5lYRynHwW^Vt^ja-ix za+n-b6WrF-3mPw8@+$7*c2;^09=e!MW`iH=JUM`!{q{8L%_2Ntp!M@4bNdeG=pSt- zhkt1<@BicZaQw5fv)wx&AM^z4kLJPo@|s^~KRJ2-?7?;Q z-=?ZS}+GA|imp6OD8-8hdUhwzXuSVBDemK)FjMwM=u3JC3p}EiB9ItooxG}3A zP3{A0IMEYs`j}m`p-;8cZQtC-i>*Aw(I@NC@@kz?pOaTHto33vTXGuDN6+qY&&uEL z+JSVKJ~(x>x_YiowG2f?)Y>Mv#hRYqgPn0-}6Uz&d?W*{BG3$ z)CXrZpy%zoW;2<&)}J`ir=E+O9_yXjSA!4hmiANEJ@7m~@vFyY!CwvOpLg+}T649} z{lyBdw15}P32{s{%U-+?vWO+@u+?2yzg6^`QTJ%7^PSA5GPF z_CDVQt;cxHvir$Q7~rMHzU=?Lll|7?)0s6VW5H|A&HAH9 zGrm359&XOopZ7oY_l)^3SLRp0XTI!*JN5n^JipdnUF(Vc)>dcc1O7J~`;>R*kp9KH zJ%?Ys)}V9k=WH{g@9w{~IGnRG%USMW*Vb_JtRDBYzn=cyU+_|QX8`Bzx%@Xv_l?H* zpbxKLGZ&w?Cs@^h?#@Yd)BDagFZtxXK6S4C=Ioht?(ov@v&mCA=p($~2K(Sz2Q#|Q z)?A%hKVIXZ$@rNKx}&A@GPiJpcRf1;`-^Ka{nt#A?`WcTJdkBGmIrb5&e*?s z_I!1Aujl`|N25zM!XDnN=ey@~c~l*m*0cJ+_xd02>icqlQ_OOp;foV}?tgWjb2ZQ2 zk2dQJZiwE}7hu6I~-fvc9t-b8yquD%s{D7DD&1?A%|M6!p z;xpF6@6$M~yE@oUy)bk4(sy-rzrU;K)bp;V?`o>w`F@F`^S(JuE(eo6eCX-VV3HwT z<01L&o^hN-JsltDpB`txEUEv_CbjW?`$u27ob{|%8(iPszggP%>G!+5YwvyR%Xwxm z=l{^$TJbS|b9;aHKKpT&@{qOmDlEG>M3k`63-+h`3 zv&j?Pqx0mz++A~cua?h!o7}?l$z8LSzk`pK_40D@dHgMZ`#LW)-={Ie&rFpM_p4dK zGku5a&sqCZ`^{?h#*5~;HD9dUf%F_-hugVGyE%hpyulBKbFZ#hzZ&0(sSOTy0i4OY zywMxY+Vjc9D2h?Ce|um@OjoF{Ny@y(^I@^5$?qfe(oPy`2JB#{ha>?7WK&yAIGz`D~r`I z*up%xTN7=1|Kh4%*zoK*nekj*%;E5tm)hV3mpyb1mwbp@-x;mX?=XAp)3dmlGn()( z3~JfK-v{4*!;3EJ-GB7xosVmdS1>Q<)EqyHU470x9>ACy<@b4WD?$rdpaJ#N(pIMKzh$G&ayPoi{_Peps z5w7u7PBIVH$)NtdUq1g^rd*>}9Lmeja!gIQ(Tktd$8$1X{k&gHdwzPN@n2m3>JRt& zdNt%d`l4ZRldUrmANA&bXL|pyePMoPvpWA~)wAN0UFveiI@#r3QMY^KU2V(&o%Xpk z>JJ9K<5yh9Y~RjF^{Mrqybnfj(Q>tA58lPNHOgvu;Xlu*os6OpZ696u?lWh24L|p4 zaOuHQc|2d8u`f-Wy?wCpXgt?$mT=(DwFd(aex7~qoBIE&7i{!J$7VCZn+6W(GfugM0!tEo7yS#STXBfVF{a1Ylnw{Y_zSM?i188?GZO*M}f z)fIpLeOJ9b3uf!)`@VPCSW|oSR2%))HG1*;>Wg_jj3(+2zGw4#;_GDI8C#F~etJ}Y ze`A?3IZa=`lW2(U&y1YOw`TF>DY|y=&Ly7lrRT1fnS-Ljno`n=o`=FiLUfI^Xee~)IZ(LlKPvi_nakqll$Z#AIIlsUwzi8XT0ZJSnA8Z z=W7k*v-{Im?XK0@y^9ZSuj3%~`M?#^DKmc&X3c&WihI&gl)#?V0tf@!GXrpSzFtuO7Vf?A>YK zFP<>`89g_n%{g_PefNd=uk+0dE%7jVrRir*!WsWsAMcBIJe8YSM9azC-`nWBxbQ^F z!7CqrT=$dB+(&aCE_vk9{65tMm2yj+1ROx%a*v`3{<2Smkgo)p&nH;#atx zSNZS16VUN_K3seouh=(#=Tp4ldGB7kKN$SZPR^|{zUJEu$J5g~dy{YVtA4b|f97f} z#;R`Dr6)Ywe|4lUJ-bd`tKpp=&F1X;)lcn@5w6M=07^nG#ah%W+RU9P;0Nku!eBy%o=z;9P{e^eT?sn zRvVi4JY1=L>+vLvXaFa7B=a!$;kbT-Ev?bBS^nU6E^z9}hdX4R?!%G#i^V#6R~va{ z-`Q33)@)DwB|E<7(UB~Fb;e(~(KNb`=ABuyn~bZg@AiH&y?p3^hwq1UqLs7XSsHir zbSAnhnlJmzig!O(FW8ftd($tzoNrFXeO_kY_`i5$ed->s=j`6Czn<=$VC^3FJ}vbH z4*KwrcVUO?_nf-XJ{ec%^;PfE0nUAzqk6zgZR*htXWp;xaQYmM$rAmWqqy_Qnfiv8 zY$vDmq1Sgw-o#@LdaH-Ca%}c@{@Vj@a@JhXd+XWP-tt6~-h*c!hac`>2542X3sT$JjKJ@@8&UFD%co=hiPD^4gCxRnFIY@u|5_ zXED9&E%zn3aH}@Be7r1ourI%G z^tM?-7~4A@l!yC+gOAK5_@8y_(E`W%2v;(WC-qamyl6pNxkgLR=blvGIfvUB zjK`bB)zbCJ-M=^Oi{4kG_xG{R(fzb}pBY7~b8~Gt`CBu2s;7C)Z?W!`aE%?FFW1}a z*yHTB|i0KhLR)Ht^%4bE&48pL^hHUu)3ixmZW*)YCj}M(Dz~ zpVWSMf`hL3Wfq$?ysoPzbl<(J1x!8n`+{D4p8U~Ay>+I3{YzW(d(YiJINV3+9L&zn zy}{Vtf4!q`^~T$GKYPFZt$};)?9Ed>e>Hp0egCMHpX&E_syUFe?`FMwPdNULNl*AP z$Hk!+Eu823x;??pyK}=E9{RqSCIfJtll4eb`Sm?-Hrc~-^ZUoF$a^%5j-%~(MGt+} zQ+|Fw$bj0-?qL>x{YU%efBckpaOnNn$N$wt?s>i()Z5RV9gpwKULJOEZg%a3cW~mz zea`;Y7037GV46pDi@#ayoUVrT6&%jc8$Z9ja7tsi)OWGyXMfMOezCv3Xx<*qSI2%w z-dTN{Q~5bdWu6Zj!ngH1GwK#U7<$*)=9%B=}&JzxF1fA_+X8H1S_O9yy*inAE# zvDfX5w!D)=v`4Scg){x)IC;%)27bdAJ@rhla1K8;*PHjyEHA2eKKLm&Sm(P0_Gnd2 zd*KRRwLa{vZBE*Qe|q<9wN{5TqQ!mD94&{(zRUOOZ|>Au>-Bo-GIMD=AD!2KeDq9u z=4@vnZ?aG+2wCl zd93qzHvgSN`aL^xp+{P*Rh;!d`ifC6%*l1iRl0TN`f5JC+!ddjx7w%v_IF*b^j=L_ zGgr7r^XLf9( zQ|~*teR%)XkoT+OapS48UmoeY9@iK7{K31vyN>?lBvbbu?#rt+AF_m#8M7}OFu`E& z^M15ZZ||SG9lz->?#w}F(PiygAN|==Pd>c+X>QeT|3@1h-gA7ZXYe(P=t-WeHIrx- zUOw>a47}8UfhJ>F+qVY~oE!J>1gAaLSYHQ=Y?9e}q1PJZ*Sg~eFR4MZa_c$y`+a*i zr+8NVVx&Jc!Tja~jy$}zd=@`BY|m&eJ{-c!I=pas-&o0K@a_9~Dv#n!dv9*~Uif~+Gd2JD zeNlZgw}<1~T!&Lw*KhhxzjY*IbPjek{(Y|(Jbd_$_s#`Bx?95HBfhMY>+m#ZYxg>s zTMtHN0zS`~i}`|0PUQ39k`8$&*Kf^VT-AQ>d}^YL?=e1gmXCU>{l`0+=h^e+$=$%m zVpezc-+ON#=7u*qz#PBTlzdeaZ(pvbTO8H}r@R!?_c!~upA6ud_k$yhIsfdh=TG0^ zN}lCyZ)(@a&i3uK*EM%7&+cEJcvY==dhYzrdNJ2OJz(g`{kPZ0EnWGJCqsJtEWWW> zdrs>s-Zg{GN!TAf(<^Pv>-MbgudnjF{>)~d(V6#|Klh-sq{g!~Ml*DluXl7s3-!^T z^KXs#gc07qXE(#v{Lxb1)>IjC26 z#P#8wv#YZ?lFRDChn`=qkKWhsEX(DyelJpYG9TVxs&D78Yxd|D|7UHk?g^u~^o{3V zeC60Z^>F{W7i4&GQn&smi)!+D^`Mh}bB*@LcePq`^kgo3x2OZhxb9U=amHJ<4bQ>p z9{G?eabXJoAXtjRx_xK|$4UkrM4w)?@x z8#sCo)}H4q*{yE4&=H>Xn7=iT?`chrXe^#JW<8v5&$XZ5^Ys1Uc{RyFewTOo@Tzls zR?oaIzcqMPsWG24R)4sTm)4Fq*S&LVk)CJ*m;BlP_%YA*iC$?Bmifh5?b^@p4IWl& ze#5@|(f_MU_|hEQcwLXyjJEnk2fppknyOQJ-4mRxU!LDgre?B3PdswBqVMgQFMen5 zez)P*(|YL1Q~l}dbMVAh|JFhs`#RH}yt01p>K}}6Pqb1moM>Qv+ec0NjhFi2dYY`g zXJjwU!Aam8_7YiJEfA8K$ulk$A)<^RneLq^k&E05j5~U%-5JH(H=osw&+j?DllOAxZcwZJta*le_nqg<;pQ6c>lrR%Ojd`} zcSiMRue_`8=}_-v5e;9xU`TVYF6UweC+}pb-h7;MdGb4x`pE#lo_}{EO5Deq`~J<{b5`J`Ss?~L(ZU9W$!Z+$def7W^QC3F1llRR=>o!0POf9GHB zW=keK<88ClK4)Gx2lfVgyrWM%l!@qsP zTs>r$EY?@>-}}o%>(6hy`>VCthfDov7C#!Yk7hr0R=vAlt>#2``Gx!8+V|B4pL*yM z_YeQ@N-I69-C5w$pUqf@o6kCtS#wdi6$n9C6lzZ#XB_JTupSC?n- zTO%KnN!L>&AHijQ)}s2>pv`r~ntSwW;*%_|N3B=0HOxJ|ntp0J7yGh?Y8)TMeRXKs znFn|MMi-v)%-+2X- zyM*q|%z7Ud9L!*!d!H7o_3lePzZ%kC-s0aH)|<)BgZXn7-e2u_c+bw3!Z2~!}{IdobnB)FxK065+8oaZM1&t>4E#J zCAz_)_FEIZ+T-K8PtNsZ-OK;Qp8t_y52t7g+}PW{`}p555zwZJyg7o_i)w z`2Y91zJygh$ziqY4vZ&r9$kI^+^sWPw3%BtuJ0YOw#APBW@U_Mxcc$N-sgVKEFXq= z%vT@vkiK}{T%*An%NxIBP#x8d&egIWl0!YKwegDwB+()mmmK%;_xOmq1^nsx|YB}S+pr5R#u02~{or5J^ z^?bO{a(#k*IT!cg`2IvM`+ANxarXV6=jM+VxKgjZ?FDbJ2KRgKz8v-VYQI>or~R() zxofmOGj*nVZ~fwQ&%LXG8Q>+ld+r>x&ikAV_MXk1oSuJVVeV#M9l_ka$wTLw6>~4= z{nYp%DQ&-k;B-M@KdEp5U; zKX>8czBSx)SEM&ujN`THf`=zO+LJhpLmr2NT&tHnS7UqNDPz!%Hze^ zdiplktsBkaaZd8FdD&a_MEC2hroX!243;x27CF*0xW!_o)><#Fu_tg(#@1WkuO4mb z$-d|0`AZKN_+&4=lT9WBMap=-SK zyWyF^^`6u*Bd~a1y?;Ham-^sJ>&dzJ!8*0=TkgMS(VcwEalGLHT-W~Gx5-s~{Y<^F z@Y8t~pBi&sf8*slv#tB}@alZW!-F2z|KV!w+fSx?&j&s6xclVunSpv<4&=HuZ?Bxe ziRWPFerZ4Tc?TE0ej5L3Btv?0<~uMR(fn_on+u$)1zbD{121pwycTyfKAqRw?2o44 zeQVH}UNqeMY@ETU*J}9HK6g&_(}R!o#TnTLXSG;I`)>_vpP$*GG2WO5dBDpvdFGx{ zyV>|R-2QTAY~AVeT|>v}KK&nEkJDJqU~n@(_xJo_W=$So$B(@a4*2-U`__E#iTI-D zdb>QW7ykQP9m!$w$=Y1Ldq=~0H|Oxm3*LX9@M%AF^Y>o;2b1&cZ9ZF*J+k-j138bc zU;c7>*6Ssiy;}9P_kKUj$=^pfMsIc4k1)d-oUW-YMsj_9Kfh^T&*-bJ$>!pR5BcM^_>X;gueZexmi9Tzca|Aw|EoDZQcsWbbB5iwziPmvIGjH^ z;Lv;au8#kQY47#x%<3zdh$p#z-`7X--0aO&o%YvzGC0y+jy%zxXS4=?^?;uoa;KvW ztoTS>w5qlKHv7Y~S}r~uQ;!CF zh-TN2anC2SgC9-HBTRJ0+dG^3`W0S3u7BqrO?{`v_w?NRU%dRk2z~$dMam(Mu{axR8YyFD_{$PsFea)sA)R7m@ueM~Ny}M5)*;C$|Sv+mu zdK5oebpA7^(b&4yn&X`f*Qq0`>*cOFSFbJMmYa@VGPh{DZ3+`vC`U^(pUmh|}Ca1@9NN=#wzVrAu=j&mH(hAP_iof5P<@>1KoZpN;+K!LG zxO=nSyr>(m*0nk^XLNu4;oVPV{q&6|ae43U8#Cw3V|vw7UA_aJuW*irnPKqpufF+r zr`IPR`lUhmXgM0Aw>|U^SLW+nZZCDkw-#dxCm;4Uk8qGR-sUNN+=XhjH}?F*JF}2? zIF)Voms{_gFaBm6f5wm}dkBtaG@dfI#e%Cn)qs|nzqm&q_|Z(Q^O>4*i`RSMO@`6G z8nyHF*p710;IC-MymjkbNpEY*~eC|PJakZ9nGKtS$Jy$Pt z|HXofXU;GF%eNWav)}dYj}G)^KICd0gZFr}t`9Iix?5{Zedm7en=|WOFxF@N_3^J> z_KffOM|W`3e=}oWd)$$KWs!5fPt9U{_x)kG&ioa%!}{X_5SMon+aF)3ijKpw`9Kk*4W?L&4B!bxg5zr zKF_&kM@>1*i5BlIj=gn{bu3tD?QHO_S?T| zs4Hh{oGDyW^PLO7YrmZ2G4n7^@WJItEk3+|H1Iz4_IGlqU;F6&Ti(qv8k3>0_?UOz zRZku#w|9=m`OT6>@_bL`nCvqH_vq|kX5HMUFT7x+FFMR&o_g`RnCA|B|Gk&m%;&97 z&)>ReS#JElr}W)?yUr|HHyY~Uc})g6Pj1mm4t)1Mclak`^`-aVn5(=xmoGHdeq-_8Ud~SsTIY@( z-uOHE)L#sHd$pdAb;c{X`)N(dCivBPe139$`_2Uo#s6CqoXf>pwZ{127=B}RF4j$U z@$${_Wd0A_;W;?cG5cNfJ{qbGU(-`<)aN|;Z%s5YoA?bUeaYe{YirDX*n8D?ec!Bg zdyMt*MNOXYS|j&K^?!Qqy+G%`GEe>=57Ja@?UTXQbdJpv-|MAXtBHMZlJA$-zK)-8 zEe<;3fB9$saao5nbv=BeLw%de)O!Yt84lrX{p<-_c*Xqa(OLPYzI@d2`?}|yNj0SZ zYJBT*{eSF(^U+2gdwu3+{Lr{@u?>`UXQ2f8jRN6TJSR0 z;ydfru=w!kxmvb2a~0oyP8~Xpf3W)g)v_3ikN3r-XJ#*~$^VP->-&?-N0acMzR|T> z)6;YO)Gs+*o~+aFHQ&SOeV)v-dE<%Rd4F}<Z?)Oiv#Xz3>I2B=bgIMUhcswkF%5pbe3DbPwnEQ&;I6Iwe$&Vdlw_QN$39Ee^}g| z!8$ssjXV}BTzHI@=u!hl_GKdV!`(rgZClD@^rzRt5ZxfX-G$<!AI|m(2dwlk zOv3^o-KI^Wx(Z~6Fx_|M%+H;N;@KXa{bEtN*fg3zONB8SFyf=@VL9`wG z*OM9TEQGW9CNFXOiMnhdOZfy+STj3BVF+kY_vzO zH1cu%!@PMc)~Ppd-kl>l%CmR)Wcu_h554h}oOUlczxUBkMynlNU``MB<7jfNd`+Ih z(Yy3@-{;8*J;A)$?K*c9e;4QPI(T5?t99Z%TE7~>%5${-@u7UGZSdj|FPSO6^%Umo zF?aW?V>NKTwO1<{atA(rC);o>x9ZsAZt@+lrk+!`xS5sbBERFc`rH@n^mew*i@lrU zt}k9Og#YRN%d>jpZ{JgEtz`X^33~FR{^<~>X3_g^jh*l0=2=M}ebE}e^n-6rtslR~ zTQG&wKAoxl@Z*d5qdD)(ZS4<-`q9(9aIHS-^Q;ExaQ!z^*Vsqu#!G9{qx_N~zl&R! zGkp3xW4!ReX`brcbu^lc$9w$tjmGeXGxf}9e3b)T z`7M(!^xS;1w>rX|_h?uzVBneO_VDkPXi2^Hmbu!gBN{i4 z$?()Wd-pvJ#iMTLqBCivCYbTk^~@PQwCR2KxNqdJzEcMWnZ{GO@m2lN7wq6fA3lPi zUb12hS2X$_bOzvf&wRx+r}{6S`ioir!O9FhW6iHKj2>wzzw-ujaXuW@#o6+u2JF>j z@0ustd3rB(d>^bw`_&=sGjpx0JNwZI{_MFoy6hJWaC^Sq<-b18neoU)S+_>}@p8SN z^{e~$d1|6_@kWPr2K&$6(G~1hS3S3u?=3ZG-FIn-KF`auj274Y!9+7Xc_!EP$*(@e zRhDQoSI;y%>L!EcdiA9@cxe69H>aHS{upQH0>-VezNZDutL4A=gHzq^4M*w^7T)`W z%g?tb&hc$9>ekzGu~ze(A5SYKZ2|V0pMd$47Jjqd^?z3I4_K+I`j1H9D-nUW2=u ztxfZp9`3-Y@mcsUul4jy`<#UjPw^d|b3ee8t`Ek)>w~}E{r>vg+j-!P?2;F{f*Xy| z#@~YQf3dG#d^w}x>g-Rh^iWR@qo=jmk1jN$<?`LbI zf!}1WTerC!?&`o3dhzk;mtJ%qFV^thw-T{j) zd>`vWd(YjcJ?|gB&R{jKhwP~*_10}Xy{ir`dc^;7(dY5xbGVALTn}4##*8-g#$Vp8 zw^*~jn(OysTBo_b`q)PwJ;@Z0c$JN^#KmE$>=6`Z#`xke(bbHW=TjyC#FNVkT zZ1_N(L$|zyrlFsEF8z#o>9xP^cgOoIt3Ms5{uw)Q;2-%N<`;+L2RvdYe{;v!m-d~W zvvzW3e#Ndl#8o)m(~JSf$a9Rw+~2uIZRS;aQq#yMHsX9i6V3;7BR=O@ukP1)`X6g+ zOmYVnG!iedIj4KoU)MQjkCJ=q%{=oY*X~uvwLirxxfs8EOK#*@xXs@_Ykk%lTvER= z_InE$`ixUf_ouZ_p5oZCTmwebdD87J-d^)*XSSca_Ui4upL_f5zdr7c+h2T z^S0Zo|BuVJFTUy$a$5DG=i)Bi;4MGryzZx;jyarL{g7wQufEhC15R>?y5=xFtaZnc zZ^ULF-7DuXp7`RZJ)s(kne(Wry5czeD2_2VxOF}F=G?|qy`FDPz}b0iP4wJr?TI(i_#I=2@3(uF*KgTH+mRT%UFJzyN$iqV2mrbcrGu; zT6%1p@-VP;Kl_{erEq#}IY+=uF7{qL%q{X5gJ*DQ9QJ$U*Lu!Pe!>onSZjP?RvvYz zhB%E{qjvK#miF4Lr=GNTUhC1dBiEeBTRF~mJUKfKT=IT$-^XwbamKB4tvScLmVD_o z)s{xwWBpjH>v%MdVke%w)q3Ci^VaoT!}H>g`t~OGOzLu-CEl7xPl59-AA9Ncn~#d~ z{De5mxc(u}b)IRQ>%aOOUB7d`+i%ah%Sr8ZYv3sF%iHYb+#mEm`=nm2-g7;$xOXDo zzB$(U?6u?Oz9+Wwn#YkE@SOPBr^GNWNA^XU)VJIZ(3;%H3-@u=9%kOyJBibL5Rbi* zSKWhm)(bw=UTca~Uaq+g_<+Ah-!oU3^s{&Y*^w}Qo?T_mF`p9|4x&G@%==#ft-+6oK$6wZav;2`J=?SlLzuOD>25;n#=8>~L zR~+|d`-FJsxChwlxbOMELf+12#^4N&esZfPsz!=YU9s4Zo>APZj-Z|>)%=6yp*!IO< zc#UKFJNxE)th4_agMH+_^$2|NU_3MyX5V{FdAs=X$$m}Ftx-D7A?Bmbi$D4F_UAau zPt5o9JlDAQ+5VsL^YuqK*ErvQ71xVCHqN#^jgh+iOfQB%xxV(;zQPOLyC3>FJ?1bE zEZ`E!l)c`&}N6>4>A!l68rEAtY^M{~ z_*v&KxG+9BnKOB2KUgd8;lKH0ZPiykYXly&<9^|mz2h2qX07%?H7W-A_$VM~Sf6xnEwKqop%)xzDY;pmQyxa?}7e8ZXZ*woHwzwVJ4|7j27W-wv zln&}7ckplwaJ`pOf8?6Ca`5aN#m^jTu^;AZ4#Y5@96eY2Iz60zxTf)h-#&EioQr$% zADoGu&pub=;XI7+NY5TwKXa*f#K<4?OL?Vr#4u-Yk)Gg@9;nXXnXz3fu>+GHbKP96 zr(+&_)rH%;9(%j(zkcx@b@@zt^x6IgI@bqhI=+Md^|=1vfp^>OIk94{R7jqdPKaTQy7_-^)WZ+c~9-Fq4#drRE{(_NA)s~T;{rRja9MW zkzAz1e3Z*I^3gnMmFLj8>#4c;vk>e17}iNluL;+CV#fhBk|+72M}u=?j9P=UIXDjE z8}4zY&os_s7o%nzjnOLw=@>FsGawYu?}nqFpa@VPYVFZY2#{Nz#f z5zo&vVf4=@s3~6NNDjnr%?F&xRUDP4x~SKE7#hecYo|`|VSLuF+*tpTJ6y88`}4=U zdc3m_&UT#ZUwgzg+YjCI3jUck{#^TCf9mCNd5rr{iTh8%nAbl$v%R*i%i})gKRM=j z#dF=q+&{g1d(9`E;q|7wUA6tz{cq3Tf%`i*$MK%VdH$Qc{`f(6+&=%z#p>&>C+dwo zQ19!V1CQ7Ptn0pfrf;)v_PNKIYmckXwHLByHjT?6>Z7 z9@#DuRt{7Yczp4Qpx)$tOSH8LF^9P(e&t4HH^QH$}$9#S+InMqS_71b-nxA54 zee^&V<3~@Nul43z@xjSHG;VunjUJ7+#M-;54RgbXef`d#oOEBcGM~9SY|N31>%{uk zf6S%ZFFx$*KG$bC+c?u-_m3{ky_@^FYKgaXk_$YFI>8Efbzv{P_lY{uGSFY7_>t4XT)(*9+zBzU+Y5|A1n46=q%w_Rm z?!nZ;e-ytzd(xGc)Sg%G+`kyB zzIDhG*3C8Q9Q()~mX8rf?jCc0U|sXpWNgJRIjEI2I3GSuu9z!4tg#ln5w@ReKQcaY z#^=W#w|q@JJaaB{tkYxZC3e<^MsOkj%uhV=Mci@j6Av-E9Pek3o^KE353n86_v>8d z(BF(1zPK*=kZa(jzdZ*G*Abrd2@h*Cc5?Il#59)jgzGr=41OjT)sg=8$?H2`$UD}c zFEt?NP4~M(XBy}FEY~|8dChj;6L<$2GiwwVVFK6KQ@6SGnKB3WMIH)&zt;Xe^3;Xf zJMVeBv!CsU+zXUiv+KpE9Ml&^#bu0Y zsgBlKgV(0F{ykL34o_H5TD?9;-CM8gU`&kArF+G6uI9}L^Sl^uoaVtjnjRv@b*-at zhR^0O{UT0k^1VDiT|P%{*Gl|_t7~E( z=3cb6tSw%MlV0#EZW(LNS`()FZ~VwfyviMV&5>)I?|S;2vOkk!@+iNXpFc|*Z~CDA zXwAJsZq@`(U&T>%OMA@wTvy*JC-GA+eWT}7Q*w`(^0)3$zkOrP!ispVnQzugp5-Is zg~7hUBlnkn9n*uD+b8;aB-bZqO_prHdHC5mv_(cGhvO zxt`4j)g0QvhxKDFu@Lif55H>r^Dnu>Ij>iIy8fOfxS+1}yM{S>jd9_%IaXfIiAV02 z^vM{)MDF1_@NW9pGuz)j_%7$XUiI;p^BfRk_%?aZIy0^{WskB~)B^u@9!|~jwPU2e z{bFq9bB_%#!GUKt=lG0!@V9S%j6Q{%frdR`&>NLpXrHnHMi>lcr0}@pYw;k zGOl!Jz5Ia=V$J!+^ICbZk4J9$=va-wwJ!0eCUeZ3(t+pNr{*L-?U6sH{0vU0#d?v8 zd2_ITliOTdauZ|X7T3hImcn&iYm7LrsgAhd{_PJtk8^#u_??+~?8c>UOI>MpZ_K%M z?)#XW$W=X|XXzdCalHcP`r0_x=eS<^adEB@zj)sLm>jBaiO;p!>z#MAkHR^Nx0z?& z>Yrn!9cvR~_Py&MN8F`p-Al;#wRr9M@<2GO$vJbFee%6|xo*VL6Fr+7xetF--#J4o z>LriftEe?NfG@Bx7Z)`V8~2D!ofz}J>LoY%IWZeY9_hJ#&-Lm00KP+^oqp zORIEwZZMW?>NV_Z^Vlj=g#wC4(^54gf-)5j~weh z8ecxhOU3MY>Y4mg*Yuq8xo4^#yrK8d<4ZNxdDm4udzXDFz6;jCaL;z@hk1iPV&SPT z_9Xdb-R2jy#$5T&x%No$`J8LYJB;bxq(9sn>bY@{`*wT}e@c80Kgaco&+zY|#M8Vz zwhzOz;T7WO!}K8gXbi?wj&Qj5$`{Su{J+Qh`XT3dU&nWJeh&|<<|PkY3;SgCJ7S}b zIUuidb$__uZrLx+!vj1Ij&qM3RmfT4VQ8)Ke#uN~ty^E)nmEZ*rSI3;iAiIZxoK6r?;y+y3B z;rEE8#>qSGvj*e2-tm}gtjycRv`5K#m*f12?;L6(*Vao-$@%S%h;#j=ajwsCz2Y-Y z*FLl#(k{NL-`uQM{ghAS5xS;-%`tUTSNkFEuA%@IPx?|;|r?;dcMqu1X)@UGiU zcgFW{^hNc$zrql|TrV%h|DYenh&Z05Pk%qL%pPK1|%Tr^+p!j7dE5mtVx;^VA`Ha3f~r#{9gW9?Wwc_pVDn zz!&S8D@?~ECu>d|>62gLvBujkkL4@9QLUB3%va6M6~C^ToQ96o+r*+CYfX`yQE`Z4Ufq&J@Ve`z9Ogd*4~L%pO1{qy=3mnkG0e{ z=gWh+hI?vsud+`1XT0_()@#l8(^K5LAJXDJOsv8H|KuaR%2ofQv0{O1?x`DVLmBf_nOYNxrtYMJG@Iwu7MM9=*Oz%fOBxQZ{pXv-pl;n1iiUO z4xW$HZp`MasTKO+jAazV*C1gUA#T+6E58DeVPBRyZ1@^RGN04&z?iCy;1tiHiFtZ%uYlL*#`DhiT5DI|H6MAb z)0m7y{lu*2d45ZKa&hI{mlX7Rj z{JG#!YZ({3JI_4DMLKr1=e{dHI;ONwt*M26EIGYDrGI$fvyFIg%)I9AGa$~y9{yPC zQ9h}GF|3pPSqB`fUmoeKBE~fxk7vZJ@#M&P@D^V4$y{KiN6bg<L=lKJ;;ymMef1GJvah~6g zE6()$Um54y*Q>7F-uLP_+i_ms6X)_huZt_rG_N?T@5$@;zB}&Y9KR>eH1FfH0B73g zdVUTd9@jf=x_o=-<1gJFb(h;LKC=INjqo|=j^!@4wIPo?r2q{(X4sIHNBUg2hsdJpaQ&sy6(H6F&EQ_pM9u~zS$ znrF_~ABB-~&|GtqPu!2QpqRu*PH7Vk>gig2Tk@ig^IAv78xJw$leL!T##SG&pCfMY zG_LvBYd$A$aWLP&@I2~rPy4*c4`Vvc&-i?3uFb9I8pmfGJhTpM;qy5)6n1%_xC72y z?|fjq?UC`m-ksz9^G$KCn_qBF4Vd#K51QYz?|owX;CEiVeek7Mf8=-ed~f63J>S{! z-sU?yzKg%_)qTF}EaO~{^Nch7KXR^rFV6Sx#=HOdpA-L07<><{_E#RXf1JlUW7MPR zdHIwv&ew>&l-RK@AG6QiPdV?sTKhA7@8{h2j<3Fq{mNR&5%;Xobqfnz7ku@bUJwK8 z;y(58J~5@swOXffr8R4VbMoeXAG}zzGzA{HC9ZQY7dggs4r^t+_y&&=%bdhuPHH9& z1z%E}mDNZJh7?PX0ceX}q(2uJc{JoNK?c z@!cFho5z{u<#%u)CjJVFZQ3}$pKvB*>k)n zSM$-R&lKUzJzDbvL;DOhj4QrfBQe7}&EH=@@A7dU6^%5I18{?|jZR-`9yFO~&c^;-8!%_9_45;_W>zzG^+w zINvYHE6z9G>^+h;qzi}UAgp6h;Z^Jng3?`(e$|NETlIM2VA z*SlkUSDf{4xNrP_gVHzdLH4OVcHbQ1UiTyyvtJf(!OVUK5iTmzIUIDqa4?f z@9tlHPb0p3cf9JW*T7;DV_!PYW9`A?we&qi z`-u3Fr|&Smzs$$6;sLz44)D`E;3NlnoxJ-yNauFl)RCCRw*S>D;2JM$uom-n_49Mr zSl#!C<7WbRKuhzbE<7h5M)%U6gZ?~6-rF7(KVN@&d=Edzb<^GT-+kE2@`U`&MI5@` zt+xB0x%KvAPe0S|XrJZz-p2bo=RfeB@ve^Ry%_Ty{(hft|BgJ)_xo_+cWL80d7W*4 zC&%y3{QEO}2meUl!^c^E_xHuQ9@ouZa;ZK$hnMb&_f>haeBQPv>=E<3SCvB_F^~Rd z4)1sS0dw3Fy@xtJ_g`YE=llG?=DwI5T-UXgBi!8Q$jLl%9z6XHLHw+#`{Y!)!qGl- zo!-AZ2Z_`5#I>Kzv+Freqw$3Ayjlw`;+ouwx9V6!VNUMpt=Hs5=T(h~nfWuv-X5z} zXEZRpAI#TU>@6@|lbF;d{cn5pRokC^#U0Lh-E?>T-VC_@j$!Q9HvNuz&=Y&`;n&<| zd&^f}zJ2gJ;{47F=Xw8}XZ&69d-PX6_SE(*_r7GiA^xt)i{kI4+%T`tiNC8dt{dX- zp5VG3b=Uel8_dLsW|8K6?ektDB zUl@OfMg2%^+2gf0s|VGO^xu7QkGn7G51-i^JPXS;=`%g;SmEpJbzH8qS8<Q2_AKIT|KmV&ZWhzXRYO>fOAmJZo5%6qu63gB;3QA2LHj{-;L`Kt!M&s!*5lfKM=EaW$as1vd}`lu za9;b;Jk8&uyb`XpFn@P;{5?=QglBJOeDMJ%@1?juzjNLHUW(t>;$-jCJJ)D^>M!^_ z=!)BHKmHZ*og8Nvm(MiD{d1o2AA0I#+kH;w-}7W&yhp4P^~GUo!D|B zyx+gt$B{?9na}$fKh|elX=#6&XZMuauxE;c_+VbW2EH*ciig(Cqhp14wkc$Dv~ zsrvMUbyXXF`Z8zd(I03}4IP)d=u6^S*Fmf~uXUzQ^+7n+sXq6d;^otfVVrrSPOe)s zd>!l3zjE<9zlVQid=Edzb<;ii-&N>3-9LL&{L_~uHuyaF%3E)5iL?Eo7hkRGMLGJ} zW1RC3MSbpl>|U+jC`TL_8yE{uU~&%g;)xvRX>DSkZ7p;2T(KWj ztFbvx??ya0q@VS9H0N+nEa3FH?Hb6(yw{rV^uP2`XM5s#bHh0A+4sO^T=HsPq|LlK zSLeXI#Y6mfPd(yUG39|V#lx766W^gR`I@u&F7fFfWAgLJ@#F)H)TaH>wd*@S*M|>N zLwR;9y|2%4rg7eHx(B`!t0ui$YNt;FQ(Drmn3qOz#CP(a_=@;Weq)?noNHe`({sex zj_a1Ep4lF7X8kw8XMd*$y6?R;J)GR<{C&^F?+;&erq6YrWuIw}Z}^&1+SBxM?r-fM z>WqE?yZS}_UiX;&s-6sQGuQdn$upk5du?Uzwx$bKjZn{#=HARzWmJgz<6(~ z4!Gzly~&Nb!71umv)>`+ZXJ3S&TGHonr}>t-I@U4SLB_IPyPvcn#iJKgQxQ;=$L{?^ z9jr~gD_`hL->Povz;n(CS3NU#;-=r{cdqmIX5a)5ksJ6Pt>5_UpZRml*qp=q1M&0r zQ{!xZdz|g-W3EZ+Z7A*)}A_2kFeW6>N?=8+Q6Qk5EHS~?-=2m zn|-SKc{Y@zy4FknReR`7?n|Ajnc6Z>{8ygkb!l!tCZ@fZ@#faP-fDaMV?Ofx`la#b z*VbOWAjjrgby7>$az7MXal!u)x4m%t$uA#gdz|kN-4Nf=akhQs&*E=NUAXoH-zu z?{VU4e>HF5jM~M!wU{?@e(F(IZvW-0Zogh%9na(X?)#r1X7?X@rKRQu7wuE|pnKO& zAIM)C@SXgpzbwx7x5fJ!?{2@p#~H`_e4g_kd0Kuahv)Vln9yyn=@;hgJveu~`qAFq zc)suJsgroFA^hpB>g783eD-DBBfk1sS~NGYtdsHJkeB?2#?%H6mP_;HL&ar$^)da+ zb=MOz8e3t$Mxc0CViYfm=o87+bs{hVm;G1 z*(Q@@#T4hJRKtpRVT|^+0;lxAXtK0C&GZhTyP5C6CEJ^UQkE%CmVHr4Wd)hbP` z*P2b$sude(t@R*yQLM z<`U!1Z~GxWdwgrt=v>w$j<`D4zFS*!JK|4$_J}XA5l;-<=kuJKxbi;vn#&PSC!Rcm z7xKk5Ja0_ZN$kRuhs7tkGNv_%*J9B|){yA?x^d@tCHmRA~2N&l>?9>F@WuGuE^Abb;51%5&yj{!u zHNtOkCO&-Sb7KJW8UHx`Ke=CURiEpxiTCyX`WLs~e)mhS*`D@~F3x?0+Q9Z+nD@|z zb!jvQ@dtx|L=HTpX0jax%qpuford)X8LV!Qk#3_arP7U;+DP!AM;jx&)aj( zvj)6JeCDJF!iV4L1#9fNOfRe{aTE_ ztNKd(m6O*rCO^~*PaYC~F8sRto;f_rINNcad3|k+alPwl*K9BR^po4eF4w;WCJk9D zb(K!~z1Bi~axb0IH}~Z|FS+%0%M;FQzwdMHGd|8Wzpsz8{l+-k{!VT`ribRq`mT+! zdVQ<&KG&;<$v<>0+VZ)$xTfP+J9B&n=#BQ3Ypj^s&&K3h=JuTP*ZylyrT3FV@R&7R zmpvL9R{V*L`?wMZafLU$b&TW4Cp}ndl`qyYIjhcsF*tf($(Pn;?*2SnG!}2wDemC0 zteH=Y{tRL5BVO3w!Oa}x53tfV$IM#nsba)cwY7&=KZIxQ;!^qo^C@?^WP4YW{s# zUXR`L|DJPgf7u)ML%yq5+7G?Q_8ht|jPc$G9POiH9E0y%$NkKUd(|5}a_{xN(3fO*4Yttv==zBue zEwA8#aQ&GBKFS%G{A^N9^4@u7E#}_()*Nf(yWSkKUah%4dC9ZM-FoaTc_YX1yjPxM z?iT;X;P+p9xz4rU(POMLjkEpr3I zTjF}wHM+j_n(Ysto!4_>#PwWVe-Pt`^7@18u368v-`)Fjp6C6CAAe@M_euZVmG+i< z2zw+vm}ePY`MTkYPHeyX1DBuky5)KP9+RkN3X?=&-0#~d;M{DxOjW_Q{$a|>^#Ri`cHk~T*rC#wLjN?;|PAK-faxLy$V=Q`(k=5KmvoIUT6onH6SWAByt^J|>zAB=N- zj_Zx{T#x;+?5)}hyL~u%Gvl>idVhh_KN>S?ic@kM z9EH=o6nFeQ+6U`g{Sx-9V=Rok_!+xmJBC>1<+$|2^_EzAal!Vy_?>s1Yk%*?=rfJ+ zJkIr>_)PE5HQw7i(;nko<81r=?f16FdA4z`an3)&`F{7eUcEi>V=jubvF>YmhThCG z5%<~G^EuaeUkkVQ+`bO{^(BwJPr8;k^gdTS)-f0H$;tDx=A2WUa+sHUL-(tG&ea}p zj=b>qS9`=Ub@1#*-kh6!Jb%CiIinupsC&$7F0nJ#ajKF0l8fi8mCybRPkh#hd*gtE z>mYBg*SXIaX$@}{j`B)cfCWx-4)^^$HM176(c9)>Zp_PdVFZrjftlJGM;OFPyy7xE zYW$23tXIUJd*N&^-@X67=X$=g$Mb>|I-*fK$+>_0<&qvoXKkYg35)a0>CV23YCg*$PTI!mda@pR^PHYl?`-$x(jzzuSA6q6YXiqv8e8+hTHrWV`*Y-` z-|&!m_3RjXXnirC-tFs3Z{tAA-6Hno_dH#{BRhCkAA4WpXKeqR9cOy^F79)UasQos zcfODP%#EM7kDY72xAE?tSAI`_&OPw&7PTMpH+v2*=!e$r;SGS)sqxPZKXFlfK%e9M^H}kzrL=M1s1Ey*8X3P}g74~=F*K$h!wY|&f(K~i8@{@9M<0Vwrg@fqYf~$9_K30vC@LQ z-8I@X>+W2g9X~fu%^SmhVBFtxZfYf`#uex0fLhS4Jk7IyoU!Id&SF`j1_iZuor<@x`kgpR=i?~nfOz`?va~1MoiU+IeBuy_Jy~< zX#3u4&us61`tADn;Ny2@_}v-b(|%Xu_u~1!=I_YAA%9OkeqWBCz4`MuzN^RCub;Qi z_B*sV*MB&F&i2pP^F2N8U;dEO{C@~(Z^2LLmH*ujYvg?NlIQw|<9&UO>y{Uc--oA9 z{;b@4O8nt7KUdsG&u2f?XK{6&=kPqW)b9lMy-Q5#>3uZ$ac$?vQU`0`Xe^%#^N#1{ zllv$&Ox~)~-fK;q3L7jbp99@%sqh|B7FNm9;+nt{r%d7kxpaNtz|c{|Vd!MVm6j8 z7=0Kx_PO_2#-V2JCvmaYo}YU|8r)Nk?$N}FIbw2DKU-(}V4dQgalkVs_fGG}*3cM} z`>dh8Y>gN@r}kIw&8f%t&0&|DIE+i4?OX44o-^vloV+alId4v-pL@Z2n=j*7tJkP| z)OU@EHT`gH<_+D24Xmu8nCxM4qTb=5V^&;q)*kX2d0<};eZFUX%nvQnaI5Wsr*E@8 z?PD(4o*Tb6|Bm>3C^z5d^!AGT#)UEOUv}@)+e<(5^!8Kn-(SA>wefEOzxwL<_ki;n z_wRlE)!R?Rf5-LW_k;{#*F^`O39cI~MR*UPUxUE|9i9ChQ* zwO;a|Q`?vS<4bs^<%_iS*|Ju9n9r#ZIp=$><&%4(y=br86ZK-&R3ALg_)**QItNGc zvRA0>7}g35_6Ary?|NQhvd7+6>SgmUJ&cn-tdq60-utKOf>UZxe8db*;$YnxJx7eJ z*R>MQnou7xW1l+DoUD@=%p2$0+$LVmHICxzGk$o8nDT0H>0I!040VgR?N{G_a(mWe^#9Umuk?F6e%DNW#056$)LeRky2%^b;UjrN7x{D?*NKxc zaqk>>aKjVgef`FGU!UW8<8}HTPM_)9z$onO3B3^~dR+0@7jhgtQ-}9zd$M}o^Qyz% z4y{jHl72h?aCbw6^{7vvj1bKEcTYkYf9+MJhs zgfsCvS9*m{9MmWs+DFZOU=;S@9QkwIS_eM0=X3J(=NtE;=NQ+%agU>KnQu;h?mh#% z^Qe7rA-AfLabtbk3&*yut3m1Dk_?i!JxR!gq;>>Nt&KzJkw&z=i z=QO$oM%|NtVk5@!;vx?{?^^R*>g+TJ4|21|sxNNF^;mk#!`@r+OCFXdLx0WD19<_C<#T!?Ug=5JncADnVT`n{ z+{JnAhczl5Yv$pe*dxueX6#jSb3Vt?DD7HP>~&vp$@c5Ne0uw}UpapL`Y)Z{p7S*P zyS%0cYicsb^2Bv8#zkD-t0v~Cfw=?U-)hwJI>Sa)A~AEejtCArZH z*6O;*d%>4qa@I5Lv;N#K@qhQJ+CJCiLFr$6!G4e4D84z_Q}eI&MO*p@AL8P?5^xpNUhx z2zT%2vAi{=@!T_OIHFeCACb%UgN>_dVCp zE3M+@+OLye9L?umFxJ9< z96c!9;S;&y**r$Akxy^yIb!2}^4at6HN+0g^v3J-pm5ux^7$-l%FjNgt~IA?I`+CR zi}y9Yho9rZ``X;>ne&C;xLBXMeSa*kF+(G~JEF@mUgOW#=Xh7+J&yM^@|XkM^uieA zG-}PhDF%9uXfnqg=H#gFnDAir!MN>5Yo7UwPuwHo(RsqAzT7u@pNsR7Zw`Yma2z8{ z@zngVbK6##=RXng0 zA9JWZF#qLO#?ROBGxWKx=lFYgVeW9qL0TI>HDMlap$wW;^;c`cY2Kb>?e8X-aSy?pucm37djD-y#egRuUb_PUTMAR zWIUhaUbTeh7}XtfSzqg@nS26U?-9*ezt%C=_{OYySj+w9wF4dL*CCE^tt~xZJ@bpP zw2!!&OMcK$JjZ>;wWcFA3(NK4i|X2UpAm53TGVQt#+?0)^?|!`8@^ksc~*`%s4qiL z)@2XD5zojO`Q%(U$=Bc>OL-W) zcwc;_d++nBzV^~{o@t! za1H89POOt_RTIythu;MfpEa3htgJEOzy8q|ZSVN`bD!&5UUzc4_73`Y2ko^-)?hth z@8|KX3+uUW$Tjz|=g3p*jyJZS?-T!@?w@?w>Fu}wUA(XN*La7&{u%MUP7jnj`_lK_ z1I)R$yp*4cW#5Mmtqm{JPwpLR8~sRcoHIx7oyN~Re16VzZ7rzdT8T@Yh_B~k`gU9s z12vpidW|1rdFOa~(EgYU;;f5W$i1=MKk7xzs*5~57Z!7~#;iN$SpzkRgE6^slt*1d zYsAMoaKbC;9=VtU9@oKL7h;+b{j}sdIi0$NT)5kHhc8lY8=)4ts>!so6F7oa>kiKCEB!sXIM6 z@G|1)5$j#BJ@>JfY`<~Kku!}mp6B{kzUQ*-h9|}UcaOR4Kl_uu*jIC5FN+^)u63CU z4jHpzL$}vb|9D1h*OvF*Q~C~=9v}yGStD~$6P)K9;}kn_nVXumuaeX7w&PJpSI?;y z>!D_PK+Va6vEM)-< zI^UjpZPqlOJ=T66>zpf&s!uF>>7K9N!S||*`(3`)LaeFZ|f4?a7~Y;izSPvlqCJ`M@=A>1tl3vwGk?f;{s~FW^aPdg?tc+*tii>$4)=v%OIP-y>+y)2Zn4iamTb_}_*zTXP*Zi(;y#1)Y zo13^Vb#jkQT-5SA9I?9E7sT?yo}yOZ$9+CmJavhqYMza6=l8oI=O*s>Y$aaqb7`LZ z250hb{JCGLx%3};BhD9W*MH5$I@dnSbIf!7&KTc*%c<>~KmVfbUh#dneUVr8MqJfb zd1bO5BklX#~OW<&&3s*Dn>nVE&G95=5e#SrV}xS0=p)S=GllWO;Vn7A`H{T?2vhV^S~PZ~#@gk|p?M{l5$ zyu?qOmEUo<+8+B!x7mLAt?^xao@d|3dB&OM^$YRmVQ)XZ{pfd{++OyK6WjBjbn*6r zCtb2#_a(S4-mZJH?_-SL$MK4o>kz~1;_Zbo^7`T!dBvRX<9FRJd}72uA>!k@>1$4G zZ@KC8_M1O;X8WzU>Rg}YeE;GbF5AB7pW@$E&OMhs_p|zx`g2YDp*@~`m8V#fj~J)t z(@)OlzN-7&6UqrrUPtY*XN-x_xpnV-;r+;5VXb_;2ea4v+V;dX@SJ=~=lE7v}x zCyB+q&s_MP8ZyT^ID!-S4?N%dY%_m}5vKO*x_7C6#QF@n)_iup*XGY5sEKv1kw5Bq z&OH}z$2fi+#c#%_Zs%HG;lq=iAILYZ?jt{SPV6~H48$3z^Rg+1z;8{=y) zqDFI<$H39&jn=?>rvvuvXT|oSCO})Kg7A z8wbzxe(Lu;MxNc|X)MpHo@yw^_x9iW*rT*jBe1I{>c#%q996IRcAV#=Q5>`v_+5%M zRNENPsn2@HO&;RUcLzL;{Qhjoc=P6Z^EN)YgSY~S>G z7j6G3-p4%8^IeViw6Blm`}%y&b-uIb+5V0AUHCUXG=5h`PePl0%|2Lf^%U!IwLkE~ zzND|{OZtdd`Rh6K*K^8KA9FqNxj)RQJXcYY z?`1tRmfZ2&bHpVF)Y_G+#>U);q{I737E-49U!Ut-&+p=SuJO)(L7Zu7FW;aedS}f!E`1A)<-_7*dH`QI-#uO9 zdtToWhM$Q?4uvB;Vh*0ud-u(CW5pULg`z2iBeUVe^v-q`j;WBOK{eTLdA=Lt9F zr^nQVoIE$VrX2aD`o@*l%?UiAXU3*)u9MvS4w}4K$NAY`>qCv=K6%AG<`IWpFdkmj z^Uz3)_IvUsK0TIxp5dyKxI>p~vaai+CTrNA#ENy*5|`$hyem%{&W2(A-}sn|wx9js zQ~mvWzN`PC&hctG%Ed_qsM=f0P>IY0B$ zS7WJXvp3p1SmzqCN3Yy(*TZw*S3N%?FMF*0fM;EGzWmG_apf66ZQ!SNaY4rdy%o=y``Ays^>*{KPHaE- zraa&N+4`e(uFv-SaR2Ts&h^iKcl=vwUv=sB*iVSRBg6A0zpR74q$jf{@@M$IXim?w z|MY!$Wv^>2eESXWlE?JhdyG7@zr-E!z;dn3of?Q+Ib%=eTg)5u&jimi zS9RM%u0#?7PlFfK8R!YM*E|^ zw?@_pE;Ww&p8MjvU$}k8*Ilx``8AhrzZ`$>4Bx>&D&ND;a;|^%2QJ(G-Am%%O?>)^ z?TMeNKc5yi`kYXOhk52ZKv3VG$4ISM1|0w?m#m%2EI z8p*NphtI&DxwvPXvF18zK%4PeXP;%{kNFwT`^J|JVVQ%+s!_dBUH6Orq%P`a&BR)K zm|WxDT9G?-CTDsIo{UpX^HlA`OAoXLucV95#6_HZ;P&iO@Ob;&fj!Pd%|c{eBm154ofM+Y|K3e$d0zP4Ckiuh$+bZ_^w4gnbbn01rN7 z40{Bh&K8sL?Opq=J^)K|lap|}4s{gwn799_f$>AD^EDoFMosHc&-*O+xoTJ|H0IU# z$&or+oBR=`V~J;s^w3^TKYj1D%0~|LE;`LSd01C+76$sWa+9CPZB4989h$cf)5HB7 z*7lyTX88nOs68;ThPs^3HP59s;v&cM%+H=BUU>nmh*J&maXvL=Zml1EM!vWREB!)! zV*5GyMyxbs-SkcM5(m5zQ`qL#Ty>s|DGrMk)^T2Pz+CFJzR9KeqP}&Buir(^lTU#= z`i^|(@!roK59$xSjdNMoxIPz$I`+8c+jY%BwN&4Ap^X}YC-t$P^3Xi? z-2ALO;AJj7X?^nREc~j@yPpj^u+iy$hSK zbZK6D3!Id%_=rt@e&(3E$`@h-M?6(K{1~~7YcGZEx)^7^u`!35@=yGPm0a;TlyzK# zu?v^Pz}ym#o{6*c>3NxR@1ZfT<}%|~j^v#jT7$H^mV8qE#b30Lm29HV6&dQ zXuiX@>?=J+P2#m4^O|_=mpE!|-Y2Kj+WR5qQ`elE_f;=@J9$xGa^$^rPtK@WbzRRM zV{LGA95Ay-9#?$y0C}m=BlEDPnw6XL_3ZdDN(*=?pPqux;35vN zs8blI4-AaCZ@5O>jN@ASRQWky@pxul`+~K;gkhbPH~2W#T&;bMwxrcu1GhXX4SLZu+jL8eR_Rrq7 zAKrhFm)caLahKe}YHx_idDqEY;MM$!SM&aS$zG5{imtLQ-coKFz$MT0UtFcnylWqjgX_nL zT+b0lwW&Y#RZM!THN-^?=86Njb`RY%@n}5M^W3P7TE$;IC)U6>w)mj7xboS(;8|Gl zYu!4*GjYkQ?!m#erw@HUbu78$A|J#ry+~~Khuk!eT>3@LxZiQ6p2`!{*82e@npR2R&8|7vFQO}%9TdZ+U?6_6j^qH~k1#8%=7^xFu)-zW>+hb#U z{cyC0jn%a@C(QISYge7**&YcG9#0Mn#^B7n>;t()9&-n-JWDQEhkrS8k02KHGRL(h zr;e8nV9Y+t7xXakIZDgAPqgm39E;1q2L9-Cb8?KZt%cl)lk?o4($uvQbH?p7w-3@{ zPoxWYIiDC__XtlqR$6Oq)WV$gIUdjEI_u1QuBTs|kNbml)LG{3nMSTH>y65oaCt5T2p=TC&r>9{n_WmvoVazdd^MW=4MUiDE_(Ms89F3Zf(2y{QME; z5tH-g1TJ;u-YK3-uTlr|121`#2eqIE{8KH>=Unx&pR0bYrAFhXZ;Z=1dqKV| z6>7PkHD`=bUpVjswS-q#sR?VUi=Jg~frT1;o?I1E{9H4!nmhL^* zd5)E?m2dE(*4CrX5c5!O)reU35F_rfmqlXv-)@#K`ch`Zn%$*>t;u-8s?VRHYjQV#dkReAV=pfI=Ge1qIY-atYJI{}%<}nAwI+_^ z=A8OOyv(sr;-9`@EiUV04C51X-qR1w+XIZchIjS(*ZS>`^{F1}il=dm6QCsp0y@^@z=b)wm#;KdhV}vyH3TMW8iWP`;#@~MdzR)LvDen0&%_=4Gr#MkcL%;Hhx-^gzDj$}kq2t(GEeaX z2h5ABIa7~4#X319597tWm(F#+ZXOv?p~| zC$GW{%z005vhUQDxI2yDLmtRwe#9k?{iN>nNcZMAe2I182v6FqA@vq+YI4mO+vntw zv4xqj=7f3rY>ec``Q+jl=~4~nSflm#o>}>$-prr+jW6!Viz|71Pa?*j54<#k#&o%0Y269QBkKOI34GjsG+O9SE%zEVqax+J` zt|#uq9p|Jll0SJ=KD_gu51hJBeu=}{)b9E0Va~Y*vEc_eazAU-dfs!b^JlJol7`N8 zzCEgQyXQpy@B>;#4c>c?+Aod9ZobG?oHfZe*HfK(ob)AOrtuTkHQgEb%O1^?8WIX-XZ>CrwGM#TGG_~2zt;^rLHXx_whKI+o{ z*0a3t(&HK9x^u4YvySyt{`7WoPyN+P#R<=A&b5ZtiMai>=jZkALu+;&e;#pv<80Fx z*YOy6Ll^4kYCXpDXSkkoy}V8x@oY~0oh7+(E#iVpVJx)~hc$6kuM;{a@0&(*(KduWAb;2?*+cbsC_k65oh(2LZ0 zbdM<(@y^kCQI6+|XT-_Nkyl<|&icVi+$IJ&gWurqIn^)^*B{=4BWq!8;4gFLmwB#} z9L)tI?_*ta!nCjAB#)b)>&JZJagClRR=B>$82Ay(3vpGi=YfH^(gbW?(&!pK3#>t1 z!qC0}#;B9t3sd;w2Hoc8x|l<*=3S3ofG@QwX2t7xa{!L>b>FqRe)36Q+7s94^;J{O zr&p>2k5qH13v6iOnt85%T8s7JnR837YHH1xjpz8vt(p<*9wi6zv1ZR@trcH-yl1A4 z!eD;#2@LXZ?d+p;rKTCbt8I_wzR+CYCf~+tZmV|a6%X{Gd(r#H{qp*{XUwy`ZC=vJ zeAe)@dS7w5R<#|AF)qY0*8cd}eoWp2i~BS4rP*8u53Fa7`M4(@y^iOFPuAu9;6lDt zSKcldd5*2O>g+M%xwJ>lyq`Jdtub>({k8V&acbvhh5Dyh^}g`vPwoewjlvf%d9dFD zsom$Z^f=Dz;!KRf6;JA{IoE0pu4@k%pFYl9uVb_>@8^CV_|)v!5gaCMU3O-T=E$))H1XcKh$My^ayo~spsjBYxZ|4)Xci(kyv~eB(`e9LtPWI^w3Xf zL0rW!M>WW~)-pEhPVVwNd1ZWKf-o8Z(Ys zh-)5)y>PzsI2NDg<#}twIPZ_~Tz$yCmCn|x+~#IZn3q2t>zZ6kZ3pq0w{WuG2W!S| zKfPC2EAxDBPhDRc+b7TOd6o{I%|q{mV=h`ZhV-e%zP@@J(cz)!lUf5{Bb_!Ti3pJPCmMKE%RVbac+Lbk`LyPeJ9_>Td?f$I4g4x0k>+x zZ#*xaTaPpdTlIOLIrMql(_84km40f>8gq_$h!YpRhn<`JBHmcWp%>|U=@@bD@4?#S zX|2hJUH~I6_GftKbz}3pr{kyA)Wmq)V~um!Gw>#N`C)D3idgc=wc^tK?(>4V>=Ag5 zSaJ_fRoC_Rm{V)!6K};bu6c@IVwW~xH=bjtiSxpoych#tz@7edzCL@A<2-pbag`(e z+&>GJ@)R%K%`fY*rtYaNp7;1mjpW&w5l_ETJJyF*YwBJjUa{o^;!(G9d)?ZFnKdH5 zbeMn6)x2X|3nOZv*8VyRM)xi2%{X#R-;8&#u9{QlikV(9ulXqkJ;l0eSl52e&#ECG z_x0qVYRxt77yC3u&)X;W&d(mriO)OjUR$-m$K)~NV~+7L2dtS}wU)gh4DK0tUwPK( z^N@J8j#$KWT;b=O_Mv$S$DGBjxq_#;uDqL*@%N*B@x1k`midW?)`lkNNlmKTJk52* zOCL(N@@K4d^k@$9t3HdeHsTbUwaKS>SeN)ImhqKue6PiGtQl9MW3*;0VrOjMBNp|| zp*2$H*gxH4)N@_c(q~y?m`BGc$67c~FO)-$QA_i}WNgNeZ+fSB#X4qLPtL&d`=Qsf z27ON*dmiyjJ~{e+a+_zJb>PPmk2>m$Kj-)7XP;NkgPSolqDFc%yrKul$*Xgkleo{` zCO2|SPw1DlTXUV4;*_4%v;9n7z?eRaIQwHQ$)o+$5ah?179*%^UVP`#gn~N zeDg!BYHDxyGxD5Ud{{5_Cbnv-&g9{F`!n~I=9;IUtxx>KMf|$I#S8W6cWbP*)GFSo z2|DSO>P~*zW9k8G(@)2t4r;8III6bS#NT=DskmEP>6v>4`0-qrych4er_7o4GQQ`M z|E@=^2l}N!bx_YWtV0;&$XIDHFUMnCv7DbCFYAg`yy_WG{qp`ZKVwHMwB%JDqmT5? zTEubs;QM1dyAO#EywqBnSQ9*iO%Bu)bJO3rXRVssaUoCTIL_ScvAtM1WZmLsj!}=? z=Z4Fgx_)q<+P2Vz?_qk>r2V7jg;_OZ0aXopUHgsi<`nG!D znjP2i^u!p2)0oC3S7;b9(>rTG{JapOwf07s;P2Qq#x;v~#tF+foiA>w8T`OAa?Fo; z*iYzZt{rl4jyWk`*jn?R7H{xSO+9zKHP3Z(nVJ+QJ(zD!IcIf-dAZ zPrOv^ep`L#r3$xVeIp=u9Y9AJF=L`<+llTbV_vO#f>pf==x_;))*sO0H^KgzmsOQv=`CjvVbL-kY zC;d1RF*kAYzG|dL>KF2}=fjg3dyEUYp-%DCeQ`*iwT?Zay+I#}kNfO1mpt~lct%du zQ#`Rb*FDzv26-S2yV}so`lyw46W2Xrt%F0-fNw6)z9F78mMjl#7N$Q!#vBV>AlXp#Nd2+H@F#l=n;?P9_!p| z)TTVHqYl^15hFN*pY({k*2N2$iBY;Rml~ld?wOZ3e8xJ~n3L<6Fz9lY6*&ku{sEV=>M;y62p=XRdoT*EkpJnDe@Qo}=#-moKp5~N5=Mh zg}R6T(~HSz-rFn8d0zKXpIGJ-bG~Nv!M*>(sqlv?0wc=HKoP{$GXkK&l;hV^)tuM9<9|JYaa37R6L9yV`;~F;t#Ln&&-uZf6khZ zvG>oNk6Osxd4-zEO}XH(#z;^0B)IXrKxr`_%nM)f&DoqVPK}PMb>x$aYLR>9OdY`R z^Te!qa#Vd|BVJc}Os<}j26FQ}`=VIZ0%l&uQtde}-xQy9ic3Eq;>B9+1Kg)iF~?Xu z`<^;tKKW_g&qGhfJ5S$d!~r>_6?$DSeLcY5%~Ln)D_8qL+J!YR2T$u}ZE{E+X^;J;d78D?O>1*ubFIp=HGL9RnVd8`d+%8oR#hf)lRx zc#nzb&@ik~>MzI(5ALG9WQTvb>lN0h+4A(SQW3Hp}95Zv8zj=uJoS%7zx!|WJv!gd-Or#>0SyfD)L5%2ynw)zB52LG(bTIr)|$&ZO) z4${10sDAH-rQfKLHD{f=U-(D|@shK&+NZ8NYm(c(2A>0OKcC)`Q>+2U`Fl?FjGVLa z+0EM@?5y z+UYGh!57R^OZvRTweKC19yu;X>C1fMdp)1c6ZIUgIrV^=mmK-)Jb9v?%)EKO=tge` zUV8{lscUeXx$XblAFW&7%e%Tqy}X={=jGmfh6k!^tRom1TUdN<{;tXO+~?C@pDo9E zZQ%t!k{jn2oVg}`#PHr39+t1f5=Y><*1Ru0nJ->L7x!f54m|Zy`#^Z^i(-LOy-u$* zH+sQ&Jrg7K?mhH(3+9u9YRzZv=d7c9_$7Y!T)w0h?}@6}KFxU4uwU$jIn49DU#YrH7F#PS9lhj2*b*iuDzz>M?ir$5_Iz{K3hZ%v0VD|E&i( z$x}JQXYfg$!M9 zUekQ*DGch)xWP}g#htq8h2pG9?=a7`X6YH2&aHhQ9`z2K^N6kI^kMK4m-KEIS9vjS zed@)bSN4N(8E@~%KWa8c-&?!=VZX>jHJnSH1B3k-b&S1ws5*>=X2knS502_#9rrA`=JN9(d8Z!gwy#{@`*Icz<%idW+Z^qG z;7y*cHMGdTS$E#=G&^r%<{WB!KZr+q23^>vj&m-!C-nIYs&0GX z=h>gi&pk?Bqn3Hr2>jZgjV(;@g0}2^#x6Q4cjp8BN?pv$I(4sF=Hd4S_0HU^EziWm zd{>T9YiRNP>d{)$x)9^Ix@S*Ych^#E#f`puZt?>Ue=%#M~{a_;EcHy18b_u_h0sl z+{wpyz#rU+cgT&kYc1;nCu_(<<(Y>^)kKZfRh|s}wg~w?5$0Gv>D@ z&LJ-An~%I}&Z@7t8Ef3>Q{5+5Y0KO(XPwf4T8Ig5BSv+#=KC4n^{vlZVgyclXiRXQ zp48`|{mGGW^u)bM{@^!aO55m_^)t@(6RU88TkGjwYTdP@33F8^YwI~RlhdxJ=^=CJ z19S^Vc=D&b9Py4bKXF=frOlY;t5JElw|9!`eviE5Bp!*wxwubG#H%^i2tJvsePfS$ zEZ-bA@W|g@vPZ@p{YCu9u@B{6=;Ivl^KuOKl{Hnb7?U}!UA+@;V=Fi6XYZ#*)oh#v zpYgq>*!EkxgWJ&T{mR&^=lb-1aGgEXnsnMH@s0ZBn>C=O?yW_!=9X*lXlOQ%(vkX_ z*QkHwtQku<$!qZNIzAJ?+4UN~`6yQ2PXFe-Jv3Hn%)LT<)Eb^5UvbdV`@p#im*mcz zx=+01KYL`ayGHx$9(i;fj&pyY&Zs4h`bv9?in=q(@UiF#?S%{}MSH?KF} z?ve4xH#t;XX=qMZlb+;9|7U+Nulm58eP8-B^tztb8*6#*3=gcwc&ejX6GJ?KC(ew+ zzA9a+rTP(r`QmKN*6Ud5qt}I_Jk)mmuB|!O^g5qW3pwZ$?~^AzUGwBS*8A!lbJM6= zjzbOIYkoXWzSzIHUxa%+Zt|yZ$(z`zNBtR`QlE33*U#`kUT95z^g5n3*SX@0XVsHm zTrXYH(EDg`su+2renotGP&mO$Jer?5C`R0rZ%&G3Ug;7STd*&HR9AK)C zir0N}qff38Irealx)-0CPkgl;#xketNp7hC2-&?cvxdt@DdvnsgV~Zo|mRHc|eAmFU<~rA2DBg8>&WvaN>7Ovf zMV`eyakE}}$r{DySZ(DQ1M%WD`6jQO_UVQBxSlYY6Jv|tn6Ek2GY^mA(;V$xudi4= zzwZz5yN3C4zm9q4mv`wUJ&8Io&%FP?&vMJMTt|WEo&Ttth+qTq$?A`@Y)KF=h= zy7*6K`<~p$k#CpW;tlNXJ(<7*-tjx{^*OT{M6OEI9dxo2S`}ks3Eq@1YKRs7p zH0IkOlbKQHJ^Sa5p_??lY&U^*okiY7d6+;0`8uS9^H2E;YeR|8Fkak6-?86n)26 z&jEgTHp7E;?uP2Y@~q^`-fF*nr3SsK&-X(gTwr` z7Y)?hyA#}**Tt!KvbN{RAo%29U!~38qk89n&D)5J!F5*K6y>Q?uIZ%6PU%_Jv_}NP3$*+*23Ad`J45_i>~mvXMA-3 ztotr_t+VBLayS{Vuczu7VP!MQs8-X7B1 zSsdPIqBoe{^Ziu4&JNf3OV6Ew-pymZixnNG{&SwbIv<~%6?f(FkTWuR^ml(|%-mah zotfv!YJFuMweGsn)Skk}4D0#epdHSvU`&}rV*{NzurXlF*!7Oo%s zk0;Nlweap4Ju`(_Z^Me|7fe8@JAc7^YdiQ8sFCkd$0fEvfs`PEbmEAc=4NFv~d<6!ZE++ ze7smYJ)3Q^YhHK`#;bSx(F@=8h&Ob6F}&a2?8)yR?|0tv^3h~I&oCb0jGx*Y{O#pz zv>KC))|>S<-}H#fm}Iv(yWV-{ELs-N+Lgccc3*Sb|LKi3)vT^xZwGsNg9{$Iw^#br z$E?x%(RJ_q$zX6g1Ff?cEnfR0iz{ z%ad%mBbkqS;$?InuD7=9>fFAaX?%O#a|S1SRm*t*wlmgSgtbENm;%(tdo>}~N`Q)3->-eiGKVg zIC}cmvviF$tzY`~TVI{RCV%R{wD*q&?n(HIqwiQWCVSyrtoMCua)0?2?|%2=<7j|4 zy2J19f_W$p)%S0>jS-Gu=v#wxzv=j>{`NqP*4$aY`+PCj+deS1##kRO)UV#uPp#%U zJ;9{s{te3v%a7*K^`oiQp2@-Sdi>w}AdZh_d2heN?V2-ZaR|Tf!+WQGa|RD@uhw|T zEIxkhW%bcFwck0p9-ZowMy>n3y>0%~rUt+0{fp1DYMhIk`NaF;lf{ewU?|6)4YSCNrof_aU`^cvbDg zy?P(+;Y>YuT6~$y;*2kJRPW5$I6Fi1;%_*I@7At1`dt6ur~Zo<+`c2Nff_yS1HJuZ zFXyd8ewn-9;|;8vCEx6Lnmf2YTqFIXN%+cCd{x(Hxku`|{?Ofcs%=f*e`mEnKXYjv zJY#k*I#x4%RqH*2g$F#X|9U@rnI&4_>m01{#Yq08qcQHB$jLr*Uca2Dp8Uwd zSgymD>Z0Y=_}y5kQ+DKM|LuF`RcmzlT)p6$Pv_!kzkOzXu08!a^Eo|7t9j70J>Z~i zGH6}VBwX=>7ktk2q~BSOZg@@x(riCh%bpLv`CiVL3uF6?LBdW5++%{Tbc70m5Vp0Ad74@_#3O+2y}yl6GQa%t_U zUoFWcYoDps8c)rcyYc!wT6PxZ@BUz*3(WY9PWs|+HQ!9|pxR)Kw(-;ZhZk=2p!w-l z{rLK7c-LgN8u7Ewmm`|QzZ&9qH2BV>KltjYU;E&19QF6#o7HhKPw&O)`?fu+d9ZJ` z@ZUXaji=Gq9GT0`XFT^Efy3UaXY*+9orCi$ZgLFHYW?nqm-FO6&3gUhBTb(Boo{@p z7tY{-*_x$O`m9ks`Q_h?_R{*nvG@M_=Dp^&zOF?p&#y1`^~B%Q96U5r9}ReGtzVBv z!(ggLxbi!k;Isz!Wsm!i+FQ5pV7XSk&1=r)x_YU9{C?|t?U|gry1g|w*X$jL#*<^6 zivu3?4Tkzv0AAN@S1w=i|Lv|fzm%HJ$%RJS?1r|%ou zz_+K-aQojq$ z9q4t=$Mtiq*Fq<>ysiBVhu>F5v^eoT}4^Q@kRldi= z&0z1I`+Cmwe&?BevRrT0-F{uuTx6g<|Jc`>_kJ$M&X*c=NaOecWA5hFweOeP-}mu| zKIg3c(sJ{Qm*i!AaMm+*tCcn1kb6gO{DX0y@wE9^i+K9p3@&*+8l=xf=U@>oXUv-@4$~`{du* zIWzQ$|J7iug9n%K3yW`cIJMWA=tzmchwZ0*8VLf8Xx_AkDe^5 zPqotFU*kll@ETM5Xm>4M&XarEc+SS}@%;1~FWk|y-kr%{^uFs4S8E`@!S257NBjI+ z?OcO#`}SQKtk&dGYhfPTWShLgySa}JbG5(Lel_VGO!au(`u6?JB)q@+r{6r$6|Q~H zI^G`qvv)8x$T%Gq_cy)exAOG@PHE=z3dlo;z;*MwU<+6{)0+(Jq z@??MSK)n0gYK`$J{h3*~ldIlyj{ZE2JN4`Rtp%^J>{;vG6UOS>_t#VEr|x7I4cw=$ z(|KpZJVw)xmw22yee2*G<9O{}Yi@kudVS849bPW}TYKw%F{&01#tt4cwC~Tp_x!i2 zYd`ww32tZBHTF^Oa3-&4_-cD`;&1b1{bId6J1_p9vv=(Dccxd%@JMU>Pj7p!y=3&8 zpL+4m6TFuLeQ;FYWUY5;%GcLq)HXWdxv zSWC2D%@2n1mp^)r*X-@Ny`MX=GbEqjB)j-S=j7RZ)W|nAt5I{gnsV=)0sXCadxS%# ztp{(??Duo7Y&xI&JxYtUoqBO-Z@B1d?c~XG>g3`2Gj_BM&)tLP>_wxvPo9V0y3EsD zbrycScE0Yfy~d#r@0kI$AHR4X9-W6LIO1x3@OLddGvkAi3<^szo2 zyRmZKdV~Xpa_Aqv_2hRssEgK~qd4goX7_H*>Wzk9U$c+aeqOAf_fvDZj3qrj-@Eqi zOM}k(KA>Og&IL{WdsBN_vp0OjvH5JaI{*FsgYR%hbI+!WVG5+6Re#_au`1AOwVW%x3v4LwX~Pv^-Pv?>ggGMjdl9b zWlb~h)fHdMKb(#6&76AcpBnMDFSN8TYwf*YWmcK(FE4y}G_J8uZpKSiZ?^5fbNQZ; vf#==YsfqrZaq%5q=hAcVyZcp>EUmkFSNqO`9O%VAe&_C8?ai;+|Ni?QTbhly literal 338236 zcmeI5d3;vIxyN(=xc8siO?C(&1PCO-utN}muow0gBJ)mE$3y0o-jdaJft%ie$PQ_jt~!jVFn!T-nM|Cj%*py1yMT0p6w;6LE~M*mrG=KnJl@TGN=ueoGJ zO}}2;{2Xs$9%$3-8{k04te~J(qp!GR5T&*)8i_^N_bP1sRRG*2&4azT#kd!7nPUpx z1GNLpJ>yt)AA%D`*h_in3jRX)aqgIuJEY@qOW}{FZyE6bXm`js6kNjG6~-Y%k)ZjP z{2l;#AOVN6OV<@w822L1Pc+>!BH{t>&PcfvjC&EMvE{F!MLb~KndH+6#u@SS$tS}( z!CSy`y8xwb{&gA`$n=X9RXes_B@NTg$jl zH6zR>ddV;10q<5PpLQ}XmTGz_56?j&5x4zTZYSf3ZYg14eIf7w`*|UKQ3-1-y_H+Y zxR{80zyl8?y`_YJL&4GqEEY2EMO@~*f)Iyhj5oPy+|WE;YA@akZ&t2Blm8=eRC*Nx40Y zE4ih}=%CeoB~wirc8f5#gmIs0%A7FyRFh`hqU(w!jC&E6VO+V02gutZ(jfXkmm#ctsFPwKGl>te<`J!B;Y|AIp&BN_o=1~;Ce(nU>x~%1u^5AMclFE zGu2enE-A;#xKA~05%GX`%+C=~GHxo>bVkZGi#YOIIZDPg-%?~06JcKs&kO0hN>GFI zVvdh-QzDL#R)HnyOf_lJJ>z_TfWnKo0NP;}po~UOXAQ#Vh{$vtLKKF}7m|9wtcV9E z31RYyjBz9)ZfWvqB-NxpB(O<2CdU2q!lc4Ywl|n+Mli=F-Fvr2TPc>!E6NXfiW*lOQ#Bea~MO=n)gd!dwk3`B5Fb>3ag68VpiynsI+ip-&Zm4G@F}=eU&^zm*$h z9KVS3zzw=2~Q(GCmQVBCwi4CBB^$cv=~{m&8g>bMti8Nh89@!(ME zPd@cBE)!u?eT^iamZqBY=Z$75*T^{R3>Bw#Eyn6pGlIEhDc8j~Y|AdDP_5RDTM9|K zPAS*LxKA}@4vhn;CJA^@)`qzj#(kxR2>9fh-Qe-}d(u+$<;T9V1QMmry9|X=9;BMNbUZjkdDp_z0M^S8DgPGR18P4|dE$kk z4IFDFIu7C*(j63t`*=8m=-)yt`aGi^uN8`l}LF6aCn&l;~BiAC_J^GH1xa2xq*n7xAR$74$U_TKJks#DV=Jj0ckhtMei*a7@GG zGxCX;G~n56A4)lAsu?P~5aTeLK!kmlzysvRVK!+M+jSu2Ag&?2oQ33!d%$H5D`=1x zaWTfB7qG|4HQ7vvap5)kj&R@ej0Ju!egpR4 zLzENku$d_1K2QRI3Y`YA66l`sE`hkh*AQOLLK4P95f5g|xXY7nKLi{)lI5i1WV3w; zI24kmoktYmEaz zjeO<2s6aD5#%aLA3A5&Jf&IqeOoJ~lxbt1Wdm(IOfj{|_W;`x{&ikH!gAA6v_wu@t zC6-d`dG8v$4aNk!ZwiGp}>4M8@C%?gg2HmjHdy3IAP9hxm1}RjA^%HezJbq@LkvukDr+V6K;OH^j&kYo_Mq!Ir;GD6iTI+8K8MbAzlI zoSL6vc1vw&hZAXM-1ct?nLVSnGahoMv!I>vU~}xFj`m#)h?zZPGNA$Q=|nr6%Mjxj zU=aux629#9UdWx!f+5C{z>)}aJZr|Y5`G7o5^?aAImWFMBUB5v4s%$rDKp2oT~Wfe zT2u!vubP{pJDArf>U$|&QuJV&&MCdM%}^;U#9{V$C&9&Fc? z?>Xm1sNmts@gF|$o_?q!Ga_IdGekg>UtV~m1PW$vDFN&V7{`2LK@K?bBZxu70doY5 z!z$3Cgmz)1q@rD9*y z&6=qi&v?wgbwfx^9N;5{TzN0xz=aU-~yqJ!pC zvc{D6B5)yjiIs8vxapr!5@beq{d++x;@}FzjB6Gn&C@XmRq~eR?Ep$>4Kd?7#YpE| zN)XOFh)%}Q(j6=eH{t$v4tt=mGD^dTjlU$D zs=BAQM;MuuOSEMh7!RDMD(2)k>BXO%#dtZTZK8!bvak#@4xTVhpPmJz!FYrt%rEU> zTrHAmgk2A?Vza3A9;^W~)RDDW#kfK+mB(BRJ)OPOdN0Be=9gA6t_n+LV#<+ZxG7C7ID*3%5$2sTnEM!~~bNM(MkziGm zN_x+VZ~8&0-9?YESG zbSD^(dydG6o_nYY?$ zPcu$3Vtd&Y#)CM7QKf?xzU2!!JwQqVc+YmO$@RO%c!+jb$Qe&xnbQNI(?~!ZD7(ct z1O%cNe9aj~U&-)5;5Wxa95&?~;}D`yj~MavZ{_p=5aFH`6hOA-k`*^U$D6PR+BExy zOE3g`aZbleS~hOmqLFZreMi9DF)4S8aiAL*2lJ@Pc)&A`^~weU6aem}Tn6xfNm_-=9&;Jkt&?)A7zZ|mxsG&{39|2Nmz3MXxJ;@U0UTa>DVIE{7GZ7= z<20v|HIhNLtGn_uZP9Z?OBh!x;=$oi9(KE^mtU+3l9CJF6(+0+Y zqc}+A>$420{NAmQavK(m)1{s7Zc}p_RM!H|q zZO}hQF0Joa$(~)q3C1twxEW6))ufMw1`yaJCVh8G`%b4uE&1oDrSA`(-Jyl~f3S)i zbKH#6Jf>8Wlrn?uD_b=`JZ#=S$1DqMXXSZui_vy=kv~U7%s7NDC8FtJwc!xQBY*wR z&Kv*FvCBgHY}CRot(x<9PpKtlDM!pWO=7AwR$#uOqQ~j+%OiGk@8Kzt%RemUQ zIL#N58Y84s6gK;yZo#S9Yh-MGWmhAMCnrUcd?H{Ryn=-2oXa4Crl)+*u-a2|)=JuY zQ|jCMA1y!g7d8eUq5ks|a z^=;ew(^)G{&0nX)KAE+=vbFgiEjONg8f6^Y^TIgB^+-89EPP?&%v1I26xxkLMyWl8 zVxyT&K8-RS#}vYPvDhZ+wYUG^Qw!FswPP2q9nh{g(K6C#Y*xz6F%G^&$a370(w6e^ zWgU+%TzhKKdgb=;__{Q(O**DQDL2P>98GBJ)5E;Hu-SVv7oA+ZAtjq!QJxe@BGsf%oASs91Ku&F_T-Wc>De!*&u`oOoRk*NKddP!H^I0qsV2>M8dAQnXOELh zH>7PV`t;$4F};>XFgL-tr6L|6uQvWwZCih`Xw}K(8`HMK^~*Z7GXJCHMv_nMjN2>X z0pkj#yre~=pMP`K$rT&**#5yo)gDl>(ez8XcE;%ofOR!uv88+M9yIvm%1zqr^QG&0 z7MuUka{bAtUdAEL))Ad^!8EW*YsQq99$mTNwi9bMkLc7P4QvyQsaeW3GH!LMNgu>SQH?adt#-o6b(@XYp_#KI zi!t(2rwEV&xqgd5o7=OS5w0VXYQtO?<1`}<)I@^D^RjnI>p!nr zb7JEbgZ9qiCGA?8|E=ZPo+D~u9D?rv(exIQN;!P9@#)!fPHft0+_nrF$a5moZlgiU z;UpClXgE$Z9nRDw!hF}T!6&wC#b8I)tgS3+kv774$Jdj5rq4KR)ET04ZX|Ccg9f83 zO26E^`NY<(NbLHWv79SUwOOiC4*K1y(O1sgRnn7k7&#{wE>-eKd8fjrZ!ceZV*55c z_T|>i{Y%aNXgM69^e``M{M81=)0+h1?-I^&d&#SGKbSJ%#Exy)?A{qu^R8S&gOnsT z5OIR>H0E*dd0`ymY$=~x)#Jp@?bz(-j;$YVT0OaYXAKnRokF?FhJX`{!z9Wvk0Zj# z`nWLmDrtRW^ZFCJwqvu`RxCQYd)uok7Qv5(#VuZ2y>j+|XP`SU;@P3vdB8nRvK+Um zoKg-C3!hpr_rxVTu-RLymjU2`tsdM@8_SG|6iPWM;%Do)S}BiX!Qpxu^Bp4xpSW}f zHv449rX$pN|R76Ca(s3o2XD8L984snrV_}mw*DX7~_hM}JicOS+j$gG4oBeXblD9VJx5d@H&5F3Nl*fs9ypD@Wxe}J!w`yXq zZ5un}__uapvm<*py|RA6>l+v4wP#n(EpKh*b3{7T3k`;=Yv%Up(AusWODNen3;A@M^J1>#mJ$vEQr@kkRrhv<7TNdaOg?_? zZfy4M&ehMZnR6(iT{U$i2F5wrV~cn`9p{vC@JEQJszT@D7QH(Z*pW(nBnuB8|-&wEeK3pfYkJeaHEnj8lnQ1mG+>es0lzm4wJ>G1x|$F9E= zn;lp&^U)R4#q5?jnSYs*@j|5dke&f;z?JR;7`c-$nao)^X)4tT&avTG??n>(=Q->%g`*}Y5Z z?pi!a-j)tS@hu^cQkpjhoFK;`xM<;|fp?AfvcCS8m%N}fYVD0SN z7fw)Ox7OG8Y;XQo-O^Id4|o#B=^INNm1^rdwr*O}tE{$HIX1g>@zi7A--FHG+_UP| z1vNjYAFs+bkMED+R!;vA3&6{1964wBF+oZ>>{~6%jM4g)6;J8g8I^6W9dzsmd$HMH zZ@T3B^))xm8>`j^buPhxImsa>-9fV?jFVE%KvMZz@GYTxwP)OIir-@uF2J?o+EOmn&CMRVm$3slRhPVIu^|u*cFLAv1P%rAMM3vhqf=c zde(@Ptfp5dBvPK}Cf=aq&?%AWNi}KACskF<8`RCXT|IaF(cAW6v)^C0^;@%sUp`}K zO18VMy0ow{gx;*dIRg<-Y6*25t}8TsdTP40Z?#}p53{zRw*O~8yyWN|`%u}Jx9|PV z+|id#txnaZomU=y`kN{QVjNz~ISUCHHznf1F`Ce)?4psqOxg0$ecrfo&Cwt4$7Xl0 z`sT%x`=@O?CiSmq-NY2#FfYhAnGtdV4$TrWu1B)tW~nG{zHC(Q6=V7svAb4JKYG`G zZ1(EqE4NSTzhz=S4K}B`(jem?;sMuTffMs2jO%<}7>D_oo~3KXSLwHXvqt>muFH?! zjmtj0?b2OStJl}`)n>ihw={w|iF2Ba$8o2#zC&S?+dgH$M>lUfde44r zcFl!jR*mhg)9QzIZGGk!9&08&!gb^&c>?G%w~p(7UKn?DhIA>}I{AE^_QWOi|G0M_ zHhXy6+-0MC>bHRvMP>mHDCWnUk8!i9CJp=2F+F!o?XSmfSz7y#d-q_o_ios*Vr;KP zBf9Cg`9r$2ZT7Ff&!ESGRFhU`)r`}WnXRcRE80C{fCjsA-q26(*!kssm!Y!H@40mA zlmQnG?PAJ?b!n>yaF~U`GUF}sOmOHphE$V2QuQOc?U_}bs_mOy{o2(_zr1fZHoJLQ z?d(D48nju1I(KY!jt0z0ROzxh#%aDV)O2arV&9zVE9MPO&+gq+_xGRd!e-B3v1rD? zis=K&&Dy9Q?KCi+m*Tt);AG^}aXhIeeXyoicm8($kd*AEWurg8@8ZAzWG5>7`1Vck z0XC&yCk%FOak}3U;^CL44U=(fsb&Q8PDM@k&8_~<;-SjzhNUCk|H0}ne!2sjZJ$1H zLRBde8`YyE)#r%x)`toi)@t=R0Xo0is8br{jTEh-z< zt+-9o|NrY!s(6okUPy9HmPHwlTnx~@tnO0$!!;vS*=_4aAGv$|=MQW_WuN_Y)7;@* z26t(L(0a6Qt_tmN95%Bg-C>#0B}T__J}-nCm!dc&=wACFON8P=7jHErdYw}-zwgH?N#2&8MLygkL#FA$ML6{^if+<)9t?PV`c2#?PEWEVC`RjwjPyz^y3vHdY1Go zZI0a9wfIJO(~v2|amuEf{Vv706{#l8cv+ifw{INrvt8qa?VVc}e)iy6Y_@1z_pa@l z;kU}RO$r;!{sc?XRFg(K=MkY8r}43+X8Mrwhc6p1W=~!@^|J@pV6&Uoj;UzV*cOw& zmFFas2AN$$4{|o+cBPt;!w0{j;+Azo4(_i>Y7bsI{?mt6VY63mnp@enX~)8IY_ZZp z_y@(}QZ6sz0sHwFk5FUX%TayWJ#}SmB0G3_&Efl&eEQHzRCeV4W)1JL%!b>;I_~hZ`*lgpBzO9?UKhd?%#H5@7 ztiW6GjE9(5U8pKAJaldC^Vd)0u>+Uae)8}#Z1%vOi9ia(R^|bPU3~>WW_KYmZ_sfY zpBKiR#jUe@zjWioy!O<$YySMu;=eqy1eN{v&iR!k>Ha()HvCB*+2kgAHyrj1uo#=w4el5+2q6v2t`=0Knl%1rGad(vz4Z%g z&wb-ZlNt8ZwG;m5z#?q+?UjQfECT|O8pmFSB+`(|*93zX6u6OUMvi0q!X}SgIr^<1 z*X6XQuN(hAk1oJw&)-8j zJt{l=;Jp5ot#WEKgfVBm1C^d8V zSFY;&$493H_WGR@K7Mrek%Mzk*{|=KR#DuT%{aX4n40Sm@!$Zt&Uj_}=D)db(w`oi zcKE>5U)?q3zYoqvWgkB_yLKSgFL7{*aoCFn`3|RWopF%z)ib&RlASyJp+uxue!be%HbbBJ_DOQb>o;sKg8q)jykbj92$) zbNKPPKRr1WgWbA&fUpYNE9II~O>k6T*|@6&QhxP@zJGjjigA18u5q1;q<-nErD|?O z#IvO1fO*%_7VkV%`^Tpy8?-+>Icd!KvVVucM#fEvc*Yn9%s0&Ia`>r<2JPaDV!vmf z==NeA=B7kEdyJPBHF@F3BmVGotr2_VhCxM5)%>iiU5pz9JbR3Tl+PSj`iG}$^xNBy zjOkV0G?7@d8$~dabSE{ z-=dG68TfR2hWTg-Z#a+zl~YSLnSp^$f}M5=36i7{Nb}B(zcBY z%1GHwql^}F3?dHB$uQ$(#Z6!QY4wNC4NuSR|85ohuB$Y-Cdb5iT|okPwiyR0pFg?7 z2hR;n$=*0n-L!6LxM#d&W5E2TyOZKr%D(}{ul^Vq53azdzGnMA4k8}d&edhV zd*OT~w)(;nekzr>1h|xsnP@<4BJOp(f$@rxrY}9*=e-yDN!p#)cMc z2m$wuH!u!rzGz0td;ir}#$I`>cV!1Pf5MMJao)E~NjbJu6Z{aEzpFH04$l!Exufg5 zFIEZLwM6ZnXO4%%Ha%=iixwFc^%J50sMxDNAK(O_RHt8t!B8&e`lZ4 zX-HMd@ryXbH`|QE^TIf?YDYHx%_|iQ+qn_xH9WGToQF8lM(ia{)uvaKZN}Le_rvd& z{N|PNoc7>d9g3UddRWNdG$qWf5OLUhh8Yi^7sk}o~OnZz%$Hv&Qz0*?VcTlZ@t&08v+jk9*>_wh&K*l=D-d#m3&f7LM13`}2=hsJW#g4nfZh?LvJgfP!a0$K#^M6ZuTF$>}IJMBHf|_o*iTF{K%I25@&7_o*g7;6Y1Z zA?yOk2cPn8e0B6bPDU&YoiDIG5UROLA`T?D$hc26*(}Gs8!+yeh&#%7s7G{_U8J{ydC z%xTd01)9GlJqw1K%rI`d7^#INsW+^~JTr`YDJM@!QpS_EC+9L=&JyERi;+T7#DKNe zds$-KOL=fm#2ANOVjUHpPO+gf#<+iuD8s3xQl2r!y_Cx^4l*j)T}+52du5ODEMqP& z#GY|W{%NRus3n!fNOx6uTIan?>UcJ#TovQ0#?f}rv&wkZl25f@+9l;#Wjt$Au9k6- zVC4g*C+itzJUf`D!J|b|o^8g{2!48cgl%M7FwZvQS&;Jd&T)Dpt3Az`XWSMs5(|6U z7+T@I%row`l&8%&jGy)tVuzdTX9%55c?io+QIEGufy^O;VF|v5ictXa_O1Tk6 zB;}jzZbZsG;|UoDDYqRo5<2~5{g`{klQVAW#^T1=(!UGOxDey|#YjII=}Eliy?Dlj z7}qc5nz=|1a2Of(qH{Z*aZ$!~CZ9TqNR{qzaGf87o^esebxOH=j0b8SBA;r(GcLin z9?W%0UaG7`2G?_NVHVH09OHTd6`{kKml}@m8OC8A&?9fc9!Qlqx6e@8y9>rID1Z<_ z`Xn4--!VNgN*jSxPbcT#>3J_)z!}EDmmXCa4}|ViTgU?A;IWjp84i1fahS*7SY#Ma z<2hm&BDLoR;b>_{IqAIy#$oF+LH2zSvSZPbNFrCjEu$|s+6i16d7YK5vk2qWvE7zZEA1ljkEI5_FcEHe(ilH7jI?iAxNk54{j zPO6NQ=L}KOQ(|>{87YtRUM|MLNisq9edC;MHGSF^FN}Gd+hZ8_QZ9K?1&D|Xk-$(^ zcM$;3S`X%3z*Uj2I!Z8d$N#C-okCmUza&>oP(1ec6tWyz3b!9Rl7+--L2q{m9 z7~P$Ji|rrwyPBk#`INyt?nxMDh{9N9vk>P!esAzG4$+VavhSNSAfEA@ZlNo4 zIx1%+-4|;37>9=b#v*f4>F0?2A<7xIOjXiSp2&OLjDu5Ug6#Wdic001jbNS{<6g=o zPih$XL?H@$Ww}6lj4bo`B#Dm0cs}`*!8{P7q#@!7V;0(kehUknyH3b>+ZK%>$n6W8 k%7i+zecQEa0%3vea|XvZBRv2L0antoFbyzTuG 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 RT#enDgE_qP7%%Ui&lfu}{LKIW literal 618008 zcmeHwXSiNP)i&?<@4Y@m3F-Bup5&zWP8y{5N(u?7g!EoWC4msSQltqe2-3TuDoB$e zC?HC+&;&)1mWF@6<-6Z&f7@N=nLTyQ%<@`t%`jnRB3;!SaU&kD?;r|?S%zqtI|KHWeyub3lkE!*4kNKbf|4#+{r(vyQ$pC>Sb-vxK z-tlB^A<(q$ccQgxT>Ch(BmyLAK%mhH-*Su36m5h6EF3IflFw9*5FlLx0#F*-NI1w50Oh6_ODQ|K zo29@B?IT^6A=htic&TI^la{0%OFPCSzye*D<*EjMWprJZWo*o9DrCV4rzU#>$4_aB z5Rx*3qLi|uJ&gg8-;XF+JGw=%X-OAof$N$lS|XCs{`{qm*4yZpIq$)0I+oYOf37BP_ZxD$+oz5a3MKF@Xb@ zc8o(H1-dR%B{ai+VBtzp6q0+$RthT*8uOpP4fB?UV;}&@SAcsVr)&YZ-0cDzIDL%u+ZK?tEBldS0i&6eu>i%8VoWF3(?Kxuoa;?$(*-IjHQ ziUlB_tRp4_c!8FZfR`U-uFJ#-NxkF|fF!GsjiIG%h0x)2Mb~BW5;N~X)d@W2WN0Zn zp17|C9m$$5&~z(Z=G|5ilY>gxMIiW54@;?=f#hE~0?LzhOy+Q;9Z%W60$rCWSDYD# zs;Yn;~|{qd-d4%D_NUwpI)+I;Ujq=;olCGl5*7>4wpke>2IN zF3{BO9y+eevJ4C%WoKvYK@GjzQY$wz-t=9UWhw3b7wznnJ*Y90tRuEl%2uU%Gs!xl zxA!UA0~M#H086HdY2?Qk0w$An2+~f8!u5}IU1m&)Y6sHyZmWpWejmk)X6mHwW`4@R)uczw?Uc=t045vZf0(v0cUFby=3=zNYN# zOdYMEWGyBuyS*$X>zLj4jU42n@F-b3x;Ydr zD4u#hF3|AcSt=4&k~LkRCHEhk*JWAad62TRlfqv^@3xX+aixs;x-83i*S~0IXN14T zm1G?$j#74ngDc58;=k)D8^4NE5dz?@WGIR=M!>CP?SZsYszLsduFGPjxK+UXZmWp% zP9MdKCWW)!trNIg($g0$9IHYW&4+`BV9O@vaty{ z84tjSKQ~c?DFnPs)-f$zk#-y~wgO$3rS#&;N1v|CvZ8S1omVz;BsIRCz+I82Se%f~ zknl%e7*3>YebblmOvzeIR(5;&eFBg9?8F6H94)FsILX?l3p8J^%d*nIpR%(tTd$#a zTXxf~*yw>I>sTGgzi7vC;wglatRn^ak+S{1F3YL|dCJZPY_|sB63Z9V%OV1yCF@8d zn5Au3#i`Nhy3C?BWrhwdSx3CZd=yV9%kIa}PT(?9!qXShmR;Id935@Swunh;Xu2-L zBUS!L!aRY;)J6NwD~`~5fs(b07ih2>TW5~l0C1*kyO_kIQ?lkyk$74Lo~)DX#r9o0 zj?H3$uFLR9wNxZwUYBK+f=$#&4pDHDDMcr0i~)g=ugkL1#&#OdMrgQ(k~IdA3dCTT zC&-7xc7bLcF1jwuY6|}a8j_Qqb@nC$K$WtSftfD@J+iE(8srGjby==bP^IkL2#r-z zvM!P^=9Epzx=0{+Qg%^<2I>KXB34s9S_CLrv!#JV+Uy|pJ*De1w8~`%1`DT;6&`qo z!wd$qg(RK8+48~p6q1oG!ZE*q_6 zdzdU9y-*~a2vD*XqrF9Giv=v!i{5Q<>IhpiO4ee8w|Ep63s|fdU6)}KEKfX;bi|gM zdI18qQnoSh>AH+ZMA+KU30zVjw%&P3hRxrdlC`{M&0nCwZp^>L)G`+V^C=scmK%xk zN|>5FtI3051Snan5`sx-!&uE5oS-Ulh99~vGpt8d!_jpagVm)TnY=)QL!B92(+OOa z&`kP)ZNC(CsWBS?GbuYt)&_J<$=akC6^_MbDLpNVfSC)lC|QH9b)U5;Su509>`M0q z8eNx_l`T(u-6k5MNn2J`6MIGmBjud0*{ zWQcQgvH+`@G$d*!N+voiO(ES7ez6S0M+>oONrFL^?f=XT&8#8NhBEtYU@Ay?M| zYYl!}WOd*%(+S-At^zqUSA*=zTrDRni@KLh*_5m?Y!PKI&|vkg6Pp4ko3epvNeSnc zi*#LT#UU^Ztfm-BAwbC*&z8b3ZD^S* zvaicxbtk)I%RB(m2^@(|ON9a3KF{J(8)FFYq-@YytXy^>tL0P;dtQ{ROQ{J@WA?Jr zQ}YOblv2{me&kHnAo*AEaeHTWypwaigZC5SlxmM!q-7Z7#a7#j!_PelYGbysXg4UixZX-(`hyi~qU>Y6rx zB&%OBjHH~C<%%7tfkgx&QAhvPdLNz?H4FzIEzX*T=0qU1{iZ&0I-zFf5lHG9%4HAX z{P{YiYZ!}2Vt%>Hj+k8|Sh%uIsJ&qXQlf^B0vBjmC)t{;sG~N9=S#ztLLe(@=$>0y z$}Kh4iU2H7R)A1bHf85J;jh#>M{+KMK#_!Dab?_6U#$ofNv~|mF7gxC7bWYcfz~;a zb72Hvbr^k%B+uwVj|?CXi!QfNDrM(-h%4Emj+z;mFAY%&fr_%N*r3#K)L0b)Y`K+s z=M_shx4<4K>Ov$@wXCZAz`9g)MXOd3BEt=G1(>Sjf9I9znEc2i>ZrY81ZX%00-S>7 zY{nRf9w|fsmLn-WMw#<8&hWsmYjC1)j^s)P0e->Ks>$H2wlBZdI4{tObj?2yc!0)g zr%B$#5a1Ve)1nCQn4pXPs<`8CQ@3pvN)@4AIqmyq2dKv)|;59BR^`VOy2kr5SFKKNB-ET zB?AF5xm8TrYIRK{VXUCmN3f2<{%O^Op_A;GRUdy1J7uT*i0vLpCz3Oz?g9u%3RAMX zK)}=m5CGVd!VxGlF3<$LRqR5mW*7l^VZxdkj+us&&!LQWUP+P44+P%82`!mP1XM{< zeseH6YuCq0>Q1Iyl(J)=N<PXg++)^n!)}W?>qxjA`bf3VJDvmDDk~R9uFfTDcs8cMV*i=^i5K-#jh- zAVxF`K+?)|Xw_t3Px9Z_GM&bS8Dddf7Kavw3r|O1wVaw4kwlDg0Hat(CB10X#9&O3 z-|vrN5QW^!x~!}R;Nt27XnqShJ3AMb$zseHWCe?85djy6Hu-KVD>3sm^ewv-OYsN_ z!}d}NI*X0X%L>Ve7BP2dXjD?dv%}zD!O)U*ysMyMELxsmlM=Rga4uX-@&F5T8n4Jn zgj*lNkSfAxxB);Epqx`vHjKGj!{HPWte{&k8jd!_@Gw6b#@wOdaEjU?0Ih^d$vWQ0 ztl*D-P|UiflwHK1nAKFkOyHvB(_N2WIJ9b64v>~p2xK7&xyA{}I1!{_76tomD<$Xn zBatM@Kv&RkCC((a`pPkxsgOiKAK-3z225NDm~?Lt~f zMJMp2kdtNlF%=-5i!yO*BhsO6O4dms=azNA?vPIxHR9r4q?Qu7Os2{t0xl9;6-nF` zNZ_lMBruh85eW7)?zA+V4qkFw&ZRL`W)TRJB!Gh?!R&SqcGa>3rkWxI>{8VH=pKl| zgDI3yX}3FgC83d50@C!rAKT8 zc;I!tjb}peX^&!4i+&J=Endb`&p4mU6aRz|$f_}TOOMzHc>m7JO9^;6spOsv6J+z2 zp7J1&gw(ULuM&V|^=ze*)8Qd4wW)|e5>lVazKYtXHOkI~l66JF@;AuB>p_j*x`xH| zV2!d<;3qA$tcZYr67ZGwNg;T-Y8AOk4N4)9B#i52{|KMEBK}R2-fflAEKg&fNCNZC z6@q`2o~;@WQK?N?1pJ_VIHHF2@4`&bbzT>|=BS=ND<&mLB|= ziXSBBRCL_T^{tGOby=wjw2#^1V!?HWi(&5itOZy~UCJWhE=gdfV!H64m(#0OmZH>N z00Bo&veh1O=?MCeJ$D3Nw>B${?U)DRuaCi_AI@NcwbTi<4m{^_4^*Nf?}E zS7>YuJy&))`au*)ZOU}VuNqfqY)scMkQ1vWLrdx_i2(9uzy$_RvZ=NlqjYK%25j)NJ|S#D|i#q#o6FXH&?^Q=GiD0!8ki zK!DtOyOnBbN!}z8z$PM`q_r!V?C1zrO_GPyT{!|-+SsbWMps)nR2&x@Q1os~xhmxy z;voslr6Q%NL4*LRRg)(ndC-9X2C6#%*mT$koapo-F`%Fpzv|Lclyp%#X+~8CO~Y84QQ2iGJi( z9|9&BLZViJwqY1yIDPaZ&-xHhLN*>Jh6%|NlA~>a48|hYM?CVZ6#+90F-b);GqO91 znN3nF@yNMu1awo>3<-750+oL=`y~A!if-v?^{-oZW=M!icLstnuxeUaN6rl(ppS>T z>8CGpGirJtslU2zMn&=jfxzR?f{5YY5vDkN%6x{KZFK$w!X z0mYddB4&%Kf)3WHip>u{Ko8{;F-bWZ%K!puWvGL4$~o+TL*=VxfN3;@VFXn1TrJsD zMdhU*wNTT$EyId4Gn^`tK(te( zBaVg#R4Em?6>8C-b`jub2#J~>E3GX{Ed(^SOE4PU76P0kEJY_ypVUkY0iX(JM_Z(# zajhf3!w`NV@pz*)@(A#3v(~9a0oX-=O*0+}u|3dJx`lRkRXstMFx3t+f$`U1b{iY!_f&G ziKeWFk^5sRBj(gaQZZm76~$v6fuxYLgh}y- zJ|aONwQAN`L;)Z~AmVD0DoEH%VH6>dTrB!#jZjkJcIkuNnplQ4DAi zKuBE*iGcv<8VoI+ECvP#7_ta~X7!FIa}fa|a25fJTMDY7fzQnwoZup>DP~|bDlSnl zcn|Z^6WPz*q@Owio&ptKq^_s8V^SxKF!oRohSe#2*g586|(8TC;q!W z7sl!!;T45p5dolgO3ulTu9H8^_~`=8qM(orom6qQ%t#7hO2(C0BViYXVFdw@ZC1;v z8dsQ1aV8O<3p6W&!Z2vjby-T+l(Lh0aj1-eV-$mV1XB8Cd?ab(ku(2VP|7y1DeEHw zQ5izcKEgw6ii95l>olVP3?qtPSx35- zVON%`RAUSQV+5mttRRp*U6-vPX&Vz`Jdix*l(MbJ&CH;{ld>w#Z6EWE3JxJa7ieZw zW`2}hkqT7Grw@nZ;8UpPDMusfMgYVpAmO4eKCwNVxlqd1tun*@C9lh}>ZX+K=x$(` zcr=_=1hT@EuW^*v4u!2_^P~$jtwJ;D9GEPx;#?07X@o+J5irR;8cQDnxsp^;sUxAp z^b5S86pA+`t*%bq(@ctTGNW7s&OK0eUu~5x)A`; zsnRdQj}tVeAX5m?1)6U4Y4Wf9-ByvbDP^Z5g-b=vC z3DP?+wd&L4&DeEWR^ybi9TmPNDw11;2-r&2k<1+>w)L>>Zi#e(rci)7Tv<%kk*@9X z)4Dv}4L}DQ$)_p=vS(xSH6-n_>PCW?DiV?(wFm&6%&Rze#InSu_81VL3pBNQROKyK zVpb}VBp?i8aVrK@R3tyD5CADzk#NzVZtY@RSytV_rtFRl>+Npe z$K8!W2_eae5(Kic!cgNbw`C%~x7XOiC!aB>Y14{MaBh8+vXw|u-U;vrgNk!wsK{;4 z8mw*C{a+)NgFVz|g1b|b=OlTMM<7>FNGfrrBu$Dtv1!vUhM)egk;@~S-m)!YAmqr% z(~>;MAOOrkBH^Mi7~J;hQOo)#ygX>;zecTy?X|(Po7b!5=p5;tmy9YU`vUkyYQj^G zJTa-#-DTZ|{A=`zlx=9=LzRE?ek93B-GvbV$>7m1BNFqRG(6&r)@_fBUH;A36)F2@ z#KLwB>KG12)o^ryCM-;ujtJjv6-nFgl-;ROy$?n#{AS$BT>EbKA*zIC-j57FsjnCU zMIypk@3FKW=szJ>(NX1BW0v)8(m0l+TR2e47SpD1FW?DI{W7A8x$RS7bH`raOjyab zyZeuGcgn&PrH%p!Fyw>wkz1*XM~Q>0ntd^T**6ncRkR7MTJt!>*+zh$)I|UR5C~fF zvIE1(Td}BG)j#1k!{>Z6X%&yXaMH}GddG_mWW5)?^AZqg*)9%Umt{5XQ_8-w_etMO zUd3EF%IZ?~r?HEw8`MTM3;tl}04GFmM5w+yY=~I#u{P!@rh%2M#0&J zF3>ooTB=!67p|8bP1(a*H2-SaN?}n$$D=dWjA+>sr)+tmD#ci8ToD1+ugkI<5f6({ zwQ19!_Uq$L`)1}^NqcJKOjPqK6%1XVRg@}!13#|IQqp!VWnVjRT2%8o0mg?CE`1fwBNbd+q3n5iEto^&G z3y+?*UY|WYdOFUz^F&1#Xl(uFKK1h5R*|l8rtBMrjX64hy>6S;z5|blaJI>%E>$uR zD3XA$_1L7nuv3?#3pNr&J*qnXUFJoSKQc}-mjs7}!<!>h=It7i`wbacr^!*=PA;TYyF76ZNWO6oVa9MpALR@c^3_UP)?2N$nDdiqAg z_SNEb{hL?*6P#kYGteuS%v2d8kfnNPH3mpnG(_8mb>5n@{^ngW00-k4H^7a>gc$HmQd7Cpc}v!Z~i1&r}m35EDD_f|b+wl5X9OEZbz) zUYa@I-Csux@+To@5dc^MmveIZoH;^Ov))yW|F(Gbk>#7L+lrn&RZYsgAG$!x>N?44 zDw|B%@M9rQ&zO5;#byll-oh2t4PE^coUoH*i5+rH$nUm_HZf!A=G-AekF45^$Ic%* z%-GzO4@4Je@jgb^Wx3Keld{LxR3BWq5s#=Ll7B8+-?OTba>W^Q7!y12LN2SR5)jZW z?V3h)-&wfq$m%WF?3VG9jLA^>K$Nm!LjzPv!VCnIvo7iI)`^pktlfgp4zJxZx*h!q z&g0z8_zX(ZBS1i3%3j{P=aF?=(c05C+Ey+2)pgz4^>x)h*=bZp%YB7ia>MrY?YhWXfJWpwHp0+g!9qPd(L^DwT}u z;`LYAnCZGqW{(o4*}&FS|JbzY@V0G^+MMp4h39U)qxUJBuFGUZEu6AjHaOw=C5sPl z-|nuxzI<5=dgoOe=>?i3tEoF6Adr2&H*VD79ot>EEkgzh%pFM=^A~8w4hfX3Rqacq zxH`2<`-3~S9p1U!b^FVv4V{}cC^dIfjmv&xm2BVWgl|cD;Owr7)#Qi&0`E6yq}6500GjYLIgM&b>G}shj;Jrz&_Z# z{(MnX_>uYBRc4NNI8f2=Jur4MX}LK5K^uc5wIhcQ&p5dhd3y z1B>XLSLNF?5VZka)4MGldl5Sy4X$qS*^aG;_w4k*URrf}B{t@v`!wxqqfb{V|6!z^BQY!$z>$~1uM zvM58HClW$lk=uIW-m@4V7qwBt-26{5VaBV}u)G&u(Z zQWt1lsv5qxZG#`8eqr_LAMe^&WLHe1cV7RY>r}Fi@-;|XxvuHD%*@V{OxX`EKK0Q4 zT^`uG+t$9nW8DWk*B2Q)9ahsaId`|pYUP#{b*yW((uRS|u$o4RQ}&kOgASdy+XMSz z-;UQdF8lM2wQRe0;Y_!*oQhWFek|2Dd0iHt##3#Se57P;a>D__46SMT&wV=%oxj@y zduG+bcek(Nu{pgR{&!xgF3@t5buwRrqLmyE=-rk{QNvhm8rAvrrWJ=S*zJM6vhj>J zx2@o`U#yznx?$~99(3rVk{p_wtW&yHB5lL0rs1xhHR;fWXL(>B?cMU5%}al~bs4{H z8a}`wA*Vu>PuWGuTDh(vauS+J1m^bZcIcwB{II9iEO>Rx8Kvxn4U4-rcldW3%AAlH zqR#4CGG&{jbTk(Hi~E)hYlmsQS~dOf>@9~b-s6Eixn|)jn@<<8i>HoEzY&ANpaaNL(1;ow#s80*S+S$3%4Eo{@EVbfz_uzxN@di+dFZH zLqg7mn#{{ZWyaY%`KYKnXT7a=Te%wuyvy!a-E2@zb5E_slbcUJc;(q1*eg3%+`rh932{?HCBy|jyFjy-tQIUd+2m+ZKE z*^IlEP1j+U%^uafeywaObft!oPb30TBfGTm$tL!w`ThP4 z2d_ED1ABDi{F_goq}8rHZESVpI$6oMQj?pqlew6uoa-c8nN8VQLE)+C-?rJ9Zq?q{ zZ7XIUymp@l_WS+oLD4rZo~YZ-7}moT73ap86(En6lWnpda%)n$Ms;15-A`IKsxz*8 z+X+3|d0;yy4?B3>J`e1lSMRxL$;2BLjyGV}Eg09mr6>Q+tLzCpQg^P3b1P$MBfKul zO1n>W(7x_@%TP7g#4Q@DM_jB6K+HD+7J_Rmd)u2jYvEOlKLr|h^pe*t;2 zjx-KGrZT=q+v$BfxN9@|c6es@@`E>=>w!J9bLo|*jW%pUIyn6IyrK&O5~y7q@Kjut(kSf%we|d>}i8q)jvM| z*6C0IGIex9E~QYh#!hHC739dStrraG>XzNN>9ntJ+V6qAvVZmdQ-+*7W3WY=-M_s< za*jiVOl?NjZk@nmbyqj3JAX)*#V2)h#V(vX>gylx_rN~AZqJ3YPue@}M7y?U+KHW- z)s6MyL?M}WBY`Jt6qX&jE{hc%7lw3famMiOPT0or{Xe;G&(}Af=Yd_dXxy$T12Nc= zVO^b&a~xz+%gM4rMMth;jks8VcV3XPSB&h5+*Xe2_5A)-U*B?`2lmjm1=}a}$7B7f z8^x+YUdReLTcbqa&Rv&fH=jOjo2(ww3%UJh<+QJVa-IkF*5#XbP8qOuVqZMAa%7Jt zwU0xtXb3O5E=$Q)ldiKeaj|C3ppNUu^+s#^W}o!sE&IQ|^?cXui(Ag!H*46&alNtG z@XpQg>$*t7MfIs2fd8Wh$vWDFI=5)BaYCO>6Z;~x?NbN5bJfew5w$fE{SG)0EgRVlv$bzp z8>h5+^r%GIPQTkKlJ@8xt#{2BfXB|8JM?ci?f&ZaeID4pIm4Em+y%K!@81@uu6Z=e z)28D7P$Uhf>*1Z(`iZ^w%o>Qn&YpeZOBb*B@{V&nuv<5tvT#@@C#+|y2H2#{FIfJT zxdQj*x-7d3o!Ft-Ide|LV7G0W{^cEeJ+NP2x_Z$`olhIm(FvP5um;sTFZOj=t~`Y+ zvvTmMX8D-z`{xd_Yu`U(zcVN9x+D;rXWxzF__kTXxB!VeekI;fp)Z^1v=!IAZ3&c5c~>0c~60c;^+1u2exl z+Fo9lWu-lDSjS5i4Yg&Doip!?J9m3vKRs*i)PB`2+R% z)4J!+A9U%GVdm|o4dXw*YnKQ1_I2xK4{krHPisf5ZR1*Yr7a-OIIWlJ8%VN_q&=x$ z^%ZBFWZ14*Ir5XAZ2kPMovzzwckWm-vG@31t=zSfyHqtg;aH2(jzua^D5dM{dDY_@ zc;|KY+yPguIN6w8v-0GZFF*bByLWhCSFRj8rbi3cty5JUTe>bK%v|9^7S63tReJnx zt7vx`+NJrmt4=m%_wJhZ&%3vIV9#8%XjJzW!@E|wZo@h^v+~YM)w?a8&7p|=e%EiA z-uwEsBMjJ$8^?Th*O~vk+j;xo<_)I|ssTm#VO?9)GoP|~^q5M!xgRU~3FNvgyGQkC z)9Ctj!+*44gjTz0)OA@_ z+DpfF`SGTaTJ4Do=6rU~77y&^Et3YcZ|s?MZ)G%PGn6QxZicrk)q|>O*R1Xj)}H*6 zt)n#AojWFccJF2n?A2?Q4(;5uZ@Y&6S<427{%JNth619Fy5#x{@VYF!Yn?Kr-R;{) z>#*ClkNL|TYyWZYM%V4Hcdef>s9n!C^}V#NE$bS1=cVepEVTpV>N-pE0Bhi#*X1jR z+_h_rTDyDq*k510@E`YX@WA$-HmF<6dLCQrhT8vvK&}8&A{G0X@=VD(-o#Gs-u%8j zW7XP&`=W^Ey6TRh`Bsd`sU2nxCTAaM4Aeh-{8 zPL*VpncJ60S+mGY0`QAo=v1mK<5-Frtpe$fOOdtl%APw!vlfjx7@ zoHmVWRn_};P^LU($8wWbZbc8#SSeQ1M?m1z5j9VIccQR8e8Kn+?^*V@2Ufan@85OC zz>bZZ)c#h0rttPm_jOsW(n-i!1mK<5P1{d;>avLqz4;VwTlZ0|<=j)8g6hPZF?4E}Hna2bX(bH}4u7$q>>_n0iz)>)d2&ppsNv zguo@M20VZDuA#`Vl~zGu4(k_RTE$Q z!DLQ*?&?Wz-!}iR51!$=ef+@EF@0Jj$xJCbi3We!mZ(A6q+bRC+vfFn<%d%^?dhwg z{q?6yJ+M8C`lYB#DLVxS=f@~@TKO-7)%2)cRoy4A829RpQ!Cn|-y8SIPnURL4_`7V z_bm^lY)&o5%`7Hs%GVY5u0`Y9zkbV9wmp08gb(gt@X3M2uG>3z&g_R5W?e0*S$2R3(9I|gZiGmNM7D2agG zWKH>+XV)6jzr|a3PAjssxnTR77IMYrZ(4Z8}#ly z)3f%}HRC^iXn_az{Poj#eqjjdIwj%UkJghlnbQ9K$EG=F zFWfS=O=HD>U@qjW9p^`E1(RCvX{>+0^SXNY`;Sj`$o~B3lo5S+e_oU`Uq=T~{>GlQ z#F?xqU(0MYV+J;V@A1hF*_n$vX_8f#DI2Y*N!K#EhQ8PB>Hps2lbo=7FCCowi%^s( zJxpC=J9{VNktBBD-Fvlf`1V8N-}~7_33`sYPUNj@}bV@}qTujTipJuABY z`DYW5+bJWPmlCN!p%GauPq_Kml{`ACXY_@WC8-7WuYU#JMoe(wYT9vNB&vpBH z{qc!Wcfg_#dAfiNRjFXK1^sm+q-(Xh25(DGY5j*^oMh1+{6U|lbx`~zdY`^%gHqfw<-`u$ zA-wZ?=!U-U{$iLFd;O8YJ!@)PPu5WYq-%>?LEtjfG5v8vn!fwgP%AcXQZpo?j=}(9 zql!l?7`IN~(PpB9*yQuN^SZwK)DXk={k@&Uc&o3M2PxaF>$3QSKpND6K%Y(x-uT&| zcb*z-%zkl4-&T#jtxuuCo?TDbE+%WhTnDSkCm^tAUCldB4>DtKK6&EM-bR03w3N)< z&DwGLbFM%d)Pz7yi#jhn*!S(H2O6*qi(8fwn&HOo>e|s{P5D}rs3W&aXSaO&nE|@( z)+;(UKH*ryQY|~2i_&&i)TC>zx`uaN2X5;A_A~wU*{>ez-KmwepBLpY;rz+SmU>z>8H=8jA+O&F1sy(b#?oou^s7ahJ= zE&MagQtC)Hb|_`Lo~%jNx^)c<>E5o^>reK2^V!~N?ZC~QtLmfpdC{bJ*=1K!w(H57 zbgfm_P3!*eU)I>Qw&k18^;T%FJ=v>o7rnnkh%Y&agAz@sT>B6i?z0oyFu zMh&X_`(JjGvEMz{ZRCk{vYwr*$;HR|EWAn90B_&|)G>W{w^c;ZYc6j6yXU(K+qTuX z{uyS`G+nHZ(wsN{)j{mQeKNj3cCR`9wP!m0?)lCFcIOS%O>q1o6eY67ma=_H)|9W6 zZ$YW+vaAGlZfN?(^PS4tOF!?>wVmR>N1u(&a1CPvG#rU%q9$Fd)pho|EX&WF*FN@v+uQv1#r8b*$UW698#(s#qN({ZK3YGD`}A%rdE`(? z?7&@>qUh+MwSW6!yNdRk=h~jw>;M1U8bcuNKC+v#y-e1WuT||u@sEP@>+70Jo4x*0 zTehuS+<@PqyDdv?i3+EgOmLo+RS`RIKLX6`(cwG4{$-ojU#c#$A6`{uo0+{bL^SNTeWYQ^FwPzu3WD-FKv%SO}duX zHP5@PBGI*JdfYQVYx&x*T4!zi2q*s;X3b&+VkoJGr?lXhyH6R(dDkZ1S|8A>@W=-lGd;guylDfXkP@b^2kie_C8sYu4QyB z@NTO}U)NmG@YR>Az~;}!^4BnnLe*WbWUFKZ37ikXWQZMdUzzK&EbDr9`u4A0Y7Kq0gu_YJfL`DV6_~!l>oVQyaN57xtQ}>saQ-Vm?7+LI z<(t#e(;B?mlpR#ErhHv?k5YbJrd1lQJ6E-Zh9+v#btzrbb(!IfLRHEJKcQJEVh7$> z70nE*!|CDVF%K|V189Lum4Ev3KML-&zH9|Ac@&54p=Ii5zWl@vyrm?;Z0W*-ab;3= zn8}**HRonU@3!pj4`Q2Yn3o2)5c^GF-$;gd!jol9A1 zhb(H+bwyp%yDcR9Kq*~A6)!;dbfpYGvQ3xl&neD zY+Z8_2^IPM4u&UXQ?e#qGjvVwws39{?9CP^O-8yd(luR|;S@DQ#nv??>u6gmB6i^Q zKo7LUrof7%O`?v^DuL@aclvZ)hJ5SD)iqSctfoRPu>-FJRv;%fg_m6;O4iBUEN}^D zr%%^qu522~tevDZl>vb)u>-G3vc(mKhZ!fZH=Z~p>(p)*xQbKLr|YsI)#?%>wV)CtLbCR7Vnqd z=?SK^Oske!O%iezffPja5g`JpZ%iqfBMjJZNLf}>4SioTOv19mr9>Tdr<9$9kyMD2vkhg zafJLW<1U^R0+(&Y>C@(qgz%inI*OKbU6I(-012@J52KW>0jt2JO|s=dAaG4tpT2-d zp-b7V!1^>52EfB=Y9qiag=-YJE0Cv{LPK6P(kv$efs4!H^uy=G8?Y+01C(|tR#Rg@z%JFp8C{_m1H3>Zsg^}RA>YCgY1x^o%GB|= zp{-h3R#SUGAZ7~{G$Ei|*P*3s(y0If^v+9@oFli&MIE^fDrF0>nz{f2T8SOFb4|Ji zRKt-riBuSYz}2T|`uapox)!E-P#weB{5459*R57vha>Hvl&E~Nxy+>dhX5pxxk40TLa40h_3MZl!oOpcYEseu|DR1eB0 zrEDcEHI}TSXi3*WiBxQ|hB>K3N7$U0$Ix$Si!qscr4Ya-6Sv~AA~D!W%x@;f)+NPH z`Y48g6=oVEiPnKEip%3w6QeQp$|9hZ{2n{EsB4c?c9x%1BZdH7pb2a~7DXLJ>ubsu z<1qEgML>Yqfp@X4Yrx-2Y3K5jDuodUT&Ja`k3>*N!YBn@#m2$}rjEG?U`Rj}7ad6h zJXrZB?ObwFr3?Z{RD(PWR8oQ+2G1s=WZ`58Onq|^K*d-kBvI}Jp@=+NwOnRWr6dB% zh2WV(gt`WVaYiM!^pXUo?zsriJ1+(s(aOiCl$}dUs+327LF~XEq1834i#O7iCop*^ zLLhMc=1w28s3BnF;pIY_TUo?Os+UK=o6SF$IbxOgh2zJn$+MR{Fc1(UzpuUUiyDxJ zAf1SlO%ett>5)7Fbb%Jr&mpmRkh0~eOCHz=#KaD~;Feh2iZ)9vHYe$+JOY%mfprcg z>qsM{YkB5-@4-1%XZL@}8W$08TH4-g3dsZ|b>Vb(M+E|Vk&D|=L=_~o_dX0qxh=n+ zxGN2qySuCAy`b<+P+lW0dT?6T?xt)!wD&%e058y-7In0XI-0V*R}-oUx|ncfV)j7S zFnt%Ljf(am1S2C8xBB`aYSMKGs+6CjH*BxC$@nx==Tf#jQOQGD1S%@Rg9gqCfsHxr zQEqKhWjRUhC4p@GxixOBnmkL%g8%}#%<-nuOI^e2I5aN-PEr?n1U!D{ zjo%>^2Msw>z0@LroFo;G0j|eiae}!G zFsE#_{3LI}2;?%yiAv1$)(Rl(W@tAwVIERPwFo$>4z$G9s_57`+Er6aPVy#$K+G10 z3W4hyRthHqGCZWdY7ubm0u3jn4eyFLQ?^=Wk~c{N40BR)IMQ_lAVO(N@{qbKM?g|* z#=4WJW2g~Ap`4cFP!a)zB#8-3g(3v7rEEzOQg zY@2_U!>Jnj2ne8BHAx0icTEUjRtl=bgbFA;$Y`P^xs^l!?K>|#)J3J}C>T88NYanG z>q7t`Zy_W+)WwMplGC_8T9RjZ1h7$;L|t)3kU%8QJ@TLr0VJD$L}nz6t*9*P4KS>l zK2DNnB?wq&X&eCQI)nU3wA<45U1TZj`bX|58 z>`Ge|_sEY{1j>?1ZF>^60G@V1)5=D2t_lIWB(XcXfToaiv6!+|5s&=nM!*(VNz}r~ z19Y~~)6GWmuL=Pxo4+ku3QGxFM=PraAgE#;`7wZidD4=uZy4!RgsPS z7(>83NlI~;8s{QlCS}LmFvdn2NFf5b%u!ew(?g-pz+C~Px=~H~hpQNcd?Qz85YW2$ zC+QKm7}ZW+>#9Mh$%TWm87k5!Y7tONxumxwJ(BfQmPbIJw6m*5DO;`L%y`rE&MQj~ zc`}oS7y*;;!dPagNTaAlAZClgf=S{j4BH`BIuI`M3C9Dqlp}9u5KznKB6_GDC2()b zL1sHN!$TTHEdu(!uLZ6?tAsdrv7^hi>L%903_U5K*nli@*M-6SOcIuJ-MBD*X@54LA|S{4ER z)42Sq(P>%x9ob}YI;GowVcqcASe)Y79Yov1wn0d~q(yKvx5E#=6Y83c;> zqm~{;qfosV0!7l+v1*jE%_vdrD7h(H2R+0HOucduFu6c5mRib@H!}!inPZYR+0m(n z2Z8JwWovl+@U)=|G&4$6K1yzY!w0U7ETBBHBqdf*X-ACW}sCA@0K9P|GG9a03L diff --git a/src/gui/qt-daemon/app.icns b/src/gui/qt-daemon/app.icns index 881e134cb47bdf5559edc198bea68e673a9af67b..8004a8341ffc9c0eba87e98295a24a81e624c59b 100644 GIT binary patch literal 71298 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 F|36R){F#Fe?o?{%PHCivZUI370Toa{LZrL9 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 diff --git a/src/gui/qt-daemon/app.ico b/src/gui/qt-daemon/app.ico index 5d1702101f26267f2ce6db5e5ab3d39792cad14e..0a902e8370433b8762a6b3eddb2c77ac9607c337 100644 GIT binary patch literal 110252 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 literal 142967 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 k2ndmWG>cGK Date: Wed, 24 Nov 2021 19:47:00 +0100 Subject: [PATCH 0020/1271] added missing graphics --- resources/dmg_installer_bg.png | Bin 0 -> 668173 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 resources/dmg_installer_bg.png diff --git a/resources/dmg_installer_bg.png b/resources/dmg_installer_bg.png new file mode 100644 index 0000000000000000000000000000000000000000..ee45f1e05609334f6dd525edb8d1f9f232384599 GIT binary patch literal 668173 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%0AgoI^YZ2jCX8E?Ur$p@>G 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

3`1yr?;dJ8R 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

$X(yM2&@TSyWco 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 z zUqpL9FG3X%j)JtP+?I6s4fE7;k(wPjzsiyp5Y}F;7TFa-5AUlhh%u2BJcZH=HSQ;` znr&H7k^Hy6zZ1QZA<+EcPCi3z2vxRgZ8j-1`m>GNL`i1t%y$Q^=!#tx7m}hz%m&fN zSij$>j5ate%f=RD3%JTii+<{;=;v9?zO&CICvrOrldFDKnw#G0l+;!zZ0%f(a&ILK zGa19a8<0IeX4rRQESRVkL}RHmcGE)Uxu$1r4vaqs+!_nah$2R1IT8t%#a6G~N`xjD z>jc+ffdxbqc8REnT+iGd_;7Mwr0b;bFrS~3Q9E^9m9QPZ98<(%j`~T!TOgOW zRQIHr0dR}vN&QvAfy6|^Md^t&WL3@gN&N57Ny`<#4V8!2@%GFfU{<(`GBk9NM`OqHi1PM>iPlkKlxi7>l-B| z3Tmv);Mvzkh1%nkNEC`E0Ru!BcG;*35y3fb4q>AW0DnFE>)D^*q`Q&|u`_g-XhPg; zz-&im1Wz0)NON-5Y)Nu+lM?f*S!iR^5y{{DA*puR;YK+*PDCznG&1_q^-Av8vTF|C zzqv!(pD+IXtbH%N2Qz_TC+Bjo0TJSITgu6mS4x!G1Rp%Ipg>-+GiG5Z7~xsbrIZgs zig5AymfEdJyM=%>rRYLVCa_|#=L@dxf>E=fU4=t-Q!{ikJGM2vkPC?!oy{!kc(c|n z-EyDfPiOzN+KX6A5!i}IVEQxm@g2O9Tfk5zL^XyH(M017qoX?b)%-B?&w`$g?W?H6 zclMU~X$Ef&HqM|ley^QPeL82}Ooz2P>K2GGu7>lBQzSWtO!)J1i**)A4Uvq&z?lum zMayZe#+k7QA(V-98W~ypw8ZoYWn!C5OnKlS&Y&vS3lZGunU7C=c@y6awN5r|M)$%^ z+XVtEkgEry<`KU!I5pACV~XKLu~yA6(s;Htk*W%V!B6ClM5_KQ-K+XbI6QRhg8GR< zsVM9aFOz?wbUTzPinx$ZoU8aT8`n#Dt2uIkT=4T~wr)4jg|WlcH~?0aZh_p8_#~J) zwq?i7=S+*A&4YWuJmCN>;{-HHUgSmOMM`MtJ(|<0=Q|pJ-PM>CEvYwZ$JIz zto;@2;9TKEEKqE+*k4Gi*aILkk(j!}_7GKCL%P}Ds^mflLH6SNvv$2Z9h>J9w+FN> znEm;pmn#8TxMbr1*$DfU?LF&Xsr{3t#%-~K``2}^vpeTd&6)?hkdooMostDaRb`p_ zwD%!kL-lutyBII`9pVp#if>$b`9W9?;$$Q(%!=sOLE#OD4+QdbRaoGX7QW6gY2(b4ZWhe2I+UHmXMY+_fK+|=LayP%e>Y9*rT6^>#AeQ* z6qUVlajH>s5+_N>-UR+|F|+v-su(WFwAm~#r!vU!$`h8`SZnJuKwJq z_bNmTVxX|0Yy_)5VfrPhd3i)4|1|~N%-Y;W*jiVXTG&`r?44UlDdMTNHv#>glPgV2epoI)6 znkvLYKkkZpzC|pHiJM{HE|RJ;xsixO+$^WFpT%KbC+y}xr!{J*q!MirRJrX)_$gLUC z2}zhrnH-Y|WX!A?QFvPzwR14qPThsRQDC})yaXECv}#|BG}~UCe*;4>&=$-r78U6( zaIHH&tMB{kRmFq3C|OwwqIMQ?B1;SeFNFWJeBc2UWi=zrC{0Y;=8cq&sOF9XK^@&Z_G%J=D&~4C`*uy>M?cCBvgX}j<0h#@ox$sw1gAEk@o6eMSdy? z!EKO{$BEH02oVU5`NZx!a8x~$KNI=KlN7!oVj>~+R5=1>6Wn7!RAA9ZT?bF(2@ZTv zxsX2aB0#_qk?-I1injUY+GlN^RQ+(s^op0WCHc$3UU~~btP)B_xQE&`+&0bO2-iZ% z_m}?twSRw6@uUQ1;$q<7Q3n9$Hp3mz{krl6U=Tr7wd;J}OV_xkCwFRH({R(EX^78y z?Zx}qTnWm9t0*4@zuQo`1%CfF;%;WshhY;lKao z;>%svk7KL-zF`>NTyP294Y|gc1*yNDT}ym?a>pnC_NJeE0roEiB_iUM+=0N5+S=qFZEaOB>0OH%6y*I@gA9P`!$O zQF$NvR$51^Ez<63G?KsByK&(F=?kLa(r|*Nv zXhF{8e1$5_W?n@*ATVu!ibVibZ4m9XnLe3cP(kUE`>+@@k*=ybBrrR$4tE ziTLxITrd85(Y_;55R|8_E7>q9AK|GJt&wslE!s~_LHtj=;ECIg(74rSxIx4R1MVMygq9GY&fnOHB_GK@D4C#A%*7 z7cA5jribsyaKr6ErBe}71volG+A7P$sR~JmnV+lfsogr(K3h3P&m!JslIeeq z@z)MuGQ?ODYs#ji0yuU@@Ij@M{;a+u&DNwd_7CV&cE)`=)kx;H-&?ii!2H5sSt+=f zD;;ie`t-D&=tMe}DX4e~8}P4XSSGkM@1{s@AK|OS{Mm7HGk~}uJ`y?AtIDhT7owiv z*6r5M2HWclIPjBm{{?;`5|uOQl}Lz+I~(r{OxzN&Qb*L?L#)3DOfDmcyh=xWV0ZFe zvp&G@P%o+U{_<}SJN`fHp0w?WK+>y9JoS}q{NJn-ZsF$asVk|@F{yCd^Z?wzHLf{? zsEzvGU(Wvg>Ms}ZJwO=l{C@l>nQ8vrI_HBhhF?@wuAT3CZXKNteB=;KL(_0`^;f5T zuf11M4f~LTU6tFH_w1>!q}792_&AClA$DJhfB!kK5{<0S49loilR=Zl&9Ydh=JR*V z{eiCnzn}5(2TFhtzfj+6-vQv$vx=9?8;OwnzzF|sq=Vs}z;3t#T>T%t_6HscOK04~ zp2Ll17ovN87Bk)mn1WlnAr7_y-DL4cs|nLDrLf5B2rZ50+K{{x#4Ka2MMtwW2BI+* znjEkaJv&&S8gp`yW_{i=X+&7>l?M2R{7b03ZNGw6C$kOs9LX$F+tY9!2B8YUV zJppmFNK2@4daO%U&1{z6LoIAX#gu~mfed*r1GO558;MjsY0o$jAHZ%RWUW~PaulL| zY0A94U|0ia2qbdtyB`W_j{oJ_@2+|B-K%u3qMeF_#RxEs0v-vE>3}U$Wxn83lIJOx zmE`(TOEkikU|O=VgC${-S&7qer6PO-t|SB~zzk+~EnLG*ON|4Vltg;Dyi0%4?kat! z-YY4XX=dHj7Ar+j^|li=5*stjE#2{TPocBaUZP5AiE`EPa#4C!OHl%=ISRQ8*XZOg zNX)|2p zDdaV$qrtww{1;i?1%a*gVfhR24Rcpg@xnD2X_ZAh*{djh4ezknW`=fczM@n(%lL|} z+$xh3l5{-vJ|}fTSQvt7wycZHaLTzCRi}AU+*1~5;cs+YMNLws^v#Qokv@oNEN=zH zBJ;DhLVZ)9_7THudR$s{gRa1Uwn8h5+rj87tWZ!bcL=48ryh$rl z3SnonigX>C*0m>XuX1@yawRZgDQU|nUuZyqWpZHX@V&aw^bV=|2G>>bMte|%PDj7Dz${4Q%BS&FVvu`~>$rMQv0rL_N@C#_YrB33n~YRiqm!{Ko(j5vhdld%;I=OT4{-w@S{q zdsF&%5>KS<@qe*9vO9qQxv2PXWb(EeI2H!pBN(Bv)`y#j%(JD<*j&p367wn?PO9Nc zHH=!D-LSvL5rg5#IXET)iHPrgcb4l_y0iAZiU3T^D4%HaPlcQ1@SY@Kbab`%A{|_> zt#hQ*HfR`V(Ku-vZ5!zu^`hRZhzODDAngH^s&{a1r=}2ksr=r7b7SK6;sIit`@1s@cz=Ykpq{zCMKsd@VhE_rMLxX72p;Nvjf8?6 zXSVg^{7>;CvA?HUqP#;_f{AI^SwqYOD44hjDIh3`imLeja@M|=zEka0dQwsmFozvV zv4%lJhT)5OJ3JKPWce0`F@ErcFzR(SAEZ2kiyLFg@I2 z4+Q0!@Bm7btmn^}3n@vxc}dM4FwJW;p{SOd)5mFWm9PYA{b|bIa<%lHh}E(bF1;+} z9@4^K7bU{z#Vc{R12CIY)@#}en6cEYhI)$9N2_K#_{0$e0&MjcS|%^yUDNk&0NzwA`Q?d*YK!67{M?(cf_0Y-o?N$tid07Xi{~s+O=B&Qd}dpjb^{3ELe*Zy z0|eKgX~_4aGBys;YD)6&l%7gOovKm9rlByIIf{wdm|su$t^6v*$9?5Ar41d6sHzgc ze1L1AUi*tmKW|$}NKKk)X{rcksYqNf!Y<*K-iaAg6NzZJ985KtTx#w|{n?bnxK(Y= zms)rridT%MCJU+o)&)7tXtv!tTHl)FS*YX&+h99vvaD}}KRa%208sekLOl_H*12y) zI=FfB{ciKeEaNz}eii2DNuTGtsUr`0ERy>_(?}X8ws1F&f&m9OP@`x;j!0CmDrb_O zphWJ$!fOByi(2k0tCVEL)r zl`p0bWWp#wxUnb%hJs?3UPM$yyjR~#ch$aE-zf>8!-sW4G!OgsYuqSk50lT_Lflp9 zXO5c%0x;}J7B7Qm>l|yNjYH!AW@ch$W;9M4Wsz_)BB`o~%6NBX=7w&Hv&23?{aZ$( zo1^q%W(yjqUrFQFvW>dIDKA~WN>cH#|}jE!SxH&|7uv)-or5baIEP|z$oaoIcx0G zSvfd>m>9z(9~mA-jfVU(|4fDx&%vMI-HtUx0jZFA`1qP(A}z!HhNIMx(XL7)qrA-l zgfXy?dFC8&>2%7vi6fZCXp*kVq$FXs(_x`Pru{!v`ChUPZUYe!&mzps?MZsCeK*{a z@ec$gB5saw2kEcUy^{26+T^}0SJJVU*?|t>#!~{%wz84FAU15j<&45is^&wm=z}I$ zc^jltq8QhxU8i=A-ycRF>;;=ZRkc&!k^1|w;y(+%PT*!)#boYc8N(cTiwc&IZK0+} zNr=a&T!@Kr1V1ZI)AAn8-%8GDLkf=*j+(#?OuH5XH(?hK&qeZPj@fiHNkTSNRZ+dD z^h!RQcQdR@E`78zkzR6E4C!URb}FGn!~N(j;FMIgSM^YPKtnt=ri8$Q*s;Ec^)~N5*d-c7l*WoRJh#Z*4;F_i!u>3DR3<(&UwBS(L zVj4A1qf@RT#+2)g1v$e`=_02gwnmw0?c8^{%T#=*VX6xEdTBcf0G@kUI!}Ecv@F1I z4h~=@Fxatk#B3I6$AQ0@`V}Jceu;Hyyn!s!Y1PUqx^1Cg@zrD|)k<1oS87ojM6#-) zW+eHflss0QOr%eRkzFD$PEl22LffEaFvvG@Y)ORjz)dO(m0C0=W)ZYRWIT3eolrno z=2(g)bN2#;S-wO;=CxEhtFmz~u5LTz+*>I=DZ}(dE;>o;#vqrHgjEbtRp~{0(W}=^ zwfE{(hn0B^(fy?I8g&)HU39~WGB(vzP8bmm_AIgjMYWRc?}Xv}5{()48BoATma?4V|dYfg1*x;g}jm_|GoPgniFbfFpdaE^3%GdJ1-W42Rdu zalI*U!^7J^TezB)OyI^kw;Hu358Q-bL(kX)o`y!R1#S$-tk*s8P6Xn$>*U(eu*gvs z!P+EK5fw~zYn&1lR3c&$)xIYpZjaJkwd=>yiPq{`$kP z2l)`#yOOkU2X4Nre^)v`inOeHA|g^!FVc~}s=uhaiXP#Q;AV)tfXM`IG?vrZu}BYA zPEO~Yrqw}~FdbEh>2$s67*^x4n0;gOTWjwLhz5I!b*5x696+=5&f8%y(Kl?*7&eE7 zZ5PA;DqbagN8^=LHPFJuli6pig{a%HIB0K!&l-TcDlNW$MC)$=HXWqn2T()`5AWd^ z?7$#qWe_+-2r)_H4xLw-r#1E`Bc4P{x>cQ&C8p<*YdKpHB9|aBVN>#A$gV3bS6DQs zsmh)badDa@HFNPIWdCScRi0I0Y@$Xs#%?wFzz2NhfX;8)RLX(-~^4VMF~9 z)m%do%K2^x+^p0UrQ#HuULm3~ zy4DD%p)J$)RY6A!^HEy*b0&bv&=?ycqbM-Ijv6q7IP~whE0)9c8>%9tG<+~J?zbW` zzTX^m7$2 zNqdVzlmrBKBzIswf)B*a_;r62_(_W!Fi}x zy^{E^cR1`DTEz_@*LHY5jhvsysRJdpB$c-|tTW(0@YoGDJxeun~gV|PBP*BaLe zs_d022;c*O)xWERd#o&f@>(nvB@?@9QI5o@&6JAAIIqtC7SWE~lY1C23DetviHV@T zQ(Rp07dKDRHKsdq^EUSqCj4oQbAQh~jzx5m67U!i7x8D33yoVSiH1!vW3eDM#>5#= zA$gufwA}pZ<+w?65X%H-;$onHEvm$@V#YLyxDDKl6=U??ra5LjBVgV>%8c>U;*kdi zrlwD(o@IX~3FF4NF}-}(RgxBWJt&PU*d}L-;4u%&70gvk$AM*K?1Rp|Rzi(ZVgXH* zR(MfMs*e9mz(=0I^d}Li7nSLb5YvffX%9y}J1W=@z7i%vtyk4$s##v$}BYo};qY@X$SO9DLEM>{NY8?6D zJUuWRr~x-j$7TrMifS)%@pA6<-0Njj8c4PUq{JuWXgRq?XyKs(ah0>`DwH0w>KKR+ zK<5)awn1hAE({3DGt9*!GdL-yBc0)4CU9Pi4|{3MF?Q1{B@i*tI6gFJK+c7eW7Mm5 zUN2sIubpTo6s7=DRUw%+&!j_R+!Gslw@9|sOhYOyJ{KI{$K5m89hd_|GzHbh=^I2O zajc&VD+(^~Osn}+YUDH=z-S%XmQE)=9B~9)uX;X{T-AGGm(Vyj+4g+YbCx@6Ufpc` zi29D4Lvvtm0F2+(ShX{6Lgu%PojSh+eILv#6XCz`3T->72D9nDA# zU_AUmuO!`IU`Jb8C}w7jgC@@^lZW+qPkiLVceH=8I}wrktIBYer8)j76&r-&$thGC z-WwvL?*z@U+avd)!DU4>aIm;xM?C@IdK~Zpd?e|VkxY&c6S zHiL4_wwjcXt_N=NnY!4f+9?{zILk}dv}0<9qWo$jmus!W<_t;7YQ2?dZFq z`OqkDni94{{UD-DKBBkAW^JmJi0nrvp!p^MC^;g}#2|-o7z%;VFav0ro*L~`J1?Ec zwI{FI4^G#xd7(D1-U~M3Pe*+X!h~yb-)O_skZ&TQTrP22rXieT3OYP2t{fOGqhWFk zVggh|yG}*47wtrQQR&0}DeYzamP%E{IFISjs0D5&ipD)y){mWvD`D6y6VYCCdNq1I z1p+{0;AV~u3@Tm6T{;D?IK`#oG~hu%&vfn3^?GReaAe1PIw?E3T*Ix4i|ZD50jLW3 z9^rNl*N}7bIV?jvKRWq85Z0*Exl;jd$TdW&zN2tr3;2-@L>L#U9&kAP#m#rLPsFW~ zC*NzhfkZkYnK$1>A2dzEsZk(IDUR}JTF?e~Cc)1|luWQS%@gYIpax*TY4~Q9K5pXv zCqh#)KsQ3k?7HWiwjE%0Mt^F(v27LCAANR_+tlN4Oy)+sBpz9aMUNumf3+4%Z4ovK zLEgm+MW%@;T;5nag?> z88OM`I2bKtkC08?CI}$saCCfX&@d@!FLL&B>EylBuGdb;tC9|jNg6GJhKmRz-_;KJ zQsBlKn{3YJu=|H+Ga&ai>@FC4*p-@6V=cq$VS`>0xG@EDuR9`D=T?Njz1Qwa=QA<$ z(^K>PyE`4(HAK+&_wj6}tid#Q4yaNi1qSN85o)5?I`C6L6;dTQxQ4t>x*&v_rn3N} zj+oX8O<1Z7utdh%EeL->lT>-)@-8c1?GyIzCgDG;6f&)!YiJ+%0%(B_w;e zG5V*oueD$3G{yLZiVRTGINi=M(fn#zY z5FZ;ftlz|+yf5rE+i%02b{O#`9UK}(E6(dX5Ww`bHv7o9w*tzB}KKzTDCnA;pO#PcmN9u=g=NE!`Y=?Vw**y^-iB$co`Zv<6 z$+paTt^B?(B$V790(vxja^Pkc7j6#UU=PQ$S~5HBV*?WP-#OY^=K5QhKQ@F~_PeU( zq2qCaD? zrT&Sd0Fk_)RECKla)xJao2$9s3?r`mwBs1ibK)tOk%ZL$0YQP}KLWu=xJ2-KD$?OvNPaA` zO(|t*y(uA4SBKWksG`yZn`tr83?MUf3M7r$is>~qQYCDgArVVUwa8tJl9J}zRn@M7 zl{gFXw+U>Wc^0F41!96w*74Tftct^GL^5>Zu)(|cHO)o{`QbUgAPr>2&m z$=@_v)>tjlv%v-_x3`d*%vB~?mw9>{>>+uQOhNh8Ih9qqWyTcrUMSn zg`FLa`B~(x^L&+e(yOd=MDilqx3Vgvb<9z*MI5;_rY~dbZ6S9;@4DhfhN`Ty#(bI4 zmDXhuUC1y%6q3?NQU$20icOL;~p>0vC|udOGB2GS1mL!?jkP4 zRWpZItrBLDiPl3on5C`3C+ zome8O;tAlU)y@fZq*OY)J(dp5$4xF~W8PwPMrV9wom@ma{mifNT9^>w0H<zJmhY8*Hn@iWzz28yZ*IS-h;*;gy^!<-CNy#8Vl`7r9tT;Lfce_2Gnlp=goF+E?|E`Ea02 zOiB82+`6iYw~*!*|M)MG^c2g?(vmjKWk31yYAzFr5~X3Fyxzj~sMAB__SMX>sA?nx z7=tKzl^s@sl=-!~Y{KerM#`#{E-hji%G+*6DZ^q)0e@|do}+?E#T#i(8Chk`471Ie zHz`cYh&nx~W>V?j~!N@1hTsl}mzWA)-+E_&(xrI$;u*Iqk6 z7J{iGlA9q+Gql~%pY_3@_O{Ng%aNUO{3f@S^LO!#X3{p!1qG7E4C7Djy8h=+C2hZd z!{LBy2OT%X*lDZWjl~IH=UwU1!6>$0!bvQiz;G?GBQj^BMAA0Q4e>WM8;8bXHciO4 zHO>KoM26y)sL7&+_>d)hbqUd4<)SL)#D>)-%#_t^syK7_@)PWi+Z zC(it~&5T^%O2LGNvWk2he9cd#ZZz>~lKTgCfR0lv#(|d%+9jVLvG_n}fddh%_9|zU z3(!P+KDJ=CzTE5xZ5NUoEWxEocN>~80rxtb>CThkCb_3rNiadW5hyyE}%oxf7eTzhe|;ZZHPDT-4K?GVw2 z`t_#vY>ys&2tJ56RZvPI(g|IN7<_8@)bLY7&kY?L{EhyV^y-(F?|)0MPDkFj)ZHyY zeJ@E7VbQx$A_5xbW6Mu18mFC?*Q@+I%llQlgow<11LPD-N}Q%&pu|cJM>?aF7+AHD z?kQu#okybn?TtXLZE!f?cmSzhyWtwOTdSS?djqi%ph_a-7_I^Hnx?A3VyVjHU3T2( z%y(fjrW;yG!+F5E(<|8v3N(E9i`(x+Ed5#fSM?on6Ngdu#w@nhzP|_!T>%Gna8KY? z{Ht95p7+`e7n^(?sKs8t95h5_dAo-ICPBN~uvVv(I1Xyj?=o>}Z}+>_NPYz+Q7#_4 zR&;(E#ytft7R}}e^jf@XC94C7ha?_%MToqX}5irk)sv`XxyKmflR~41B_={b;Gl=qu zD2Y7UrQQApS^E>mdtN@4# zLBj=)P6GohGl>%JX4U7ciIa)uffr5(Jvs-(OF zHo(gKQh2??Hj7k2@s&;)@}WF6)QWj>#as>jR@(9*nh&5_6+7Ra`T2>{)b37kxPEj?d-om6tYSm<|x#?$KCbXiy;>4 zT!T`;AkFe|h7>T&)49!ZKUe+Me4h?$t=>uauWILZ(WSbT_UoqOjUl!bBwGU;h}^Lc zH@+xB6)f}7pIfv?YMciISM6j4*>4KJ zL{yl;;8TO=101CD{^zCt_q&|0Bm`zS9v##1Lz~=}pHeP6=7i=% z1nu9oe+L}k?q1pb@F+188n`3SX#eKBzqLOr^}}A9Kkxx~OyI^k&Bfc=_Ci{871f$a zbr&i!m?G&L1HT6K6g4lDTSQb`6UmTQJ;O?s+rqDvWdS9C*Z~KkR!P;rsee;dqrqp? zqyDQ`HUu|81E1{5OVvdmCi!q*SEtSUw&8InOIPOi%`j@l?UW+*U)dH zB+2#Y5Mx1gfrtn5O}+THPCC!W#vL91?~|OG{_l%_y^>dQz?S9xwKhaFOhjN?;kjf! z?`yn@zR+hzKV#eylH!kPoselIgN|4dZ5YUuFiHt0=e`}OW%bhkxcJwryms1qB9I#f z_{a%^dU7a0(a|i^>bJe6c=kRDYQPvN8l_J3YLc)WGKNq4LDu&l8va^V+ua7R`GyyU zFGiVx6i`w1y>wUUu5b=*OC;2L4WBt&kLkrWT0 z~ot)3^ zbVA#bced1ew-O~I{&1Q}dYjd64opPh*7Ju>NQhhTV*Q}yD`V> zhUI(<-BBF>dH!Nzo-l@T0q`^P$@>@I|E<~)@#^N1AxvHfdY0A?b3A3twYDeJM5kBI zY7q;@Ssc3?M8Nh6np1&SPjU z$si(r!?3}wZjX4JYYBs;}2?2QpU(X4UQuovkn{=Rsi!b?QgNk4c$booEXdjE9npEi6fD zk~K#E04R)raZ=T*??t(DI5qCC10T5mpL75BRYiyZ-a5c%C`8v{iMFfb1w5sN`m~#^ z@t~1hXkIv~24+D%&0*sycJ+Sv38HbHp9q>}UL}&@Fd9BK?q5eR`?stAaqi!)dhQ5B z4G&~gdm3|%GtX=aMW@cql01EMRa`>1b2(9nIoQe3qRZyCaZ|%YA*CnTm~OJ0ITYD8 zynH2oZm}IZNj|Ius>8k-QN3QZX}N9HIZs5Tqi!N5lSXxgS-`NNQl$=;v#SQ92^de> zt4+=cPMGusVHh@5rUOPP-m+68v>M-^t;PXOG64Vtvp=nTR-d7>ls=A)A~z$M00FKA zoJcqbnFu@%@NG&l%DB1I4giso3Xza1+=-iSL?q!{8#$B8NVBD($f#A?HfCFe8wk+Y z7yf18r$0Mx{=gkUiHNvmcLG{fFaA~hPbks-zsCH1Ak5DGUD%;{hC8~${~|!THxf^7 z!7toe!ClDwN5d~+(W5K<)9)qtXK-a>0OAv9K>Ts9;Sdu1i}z2&EtL{&)MI_-tTnDKw0)>$&RJaE9N;im&J{JHD@{qFxhYcHgV zmLK9#{wlavL+*qJ%p@Y$jt@PKM`HIF2fkQ0h?(S{wBv^79XAi>Arm*mEwQ8iOd)Xd zYtF}9h7f^r%nkTNaMHg?_j9;Nm*|h8ydyM=oUBcBcjNxPC8ni3tBaCU8DJ><+DpvF zg~EMBOxBI-(uFtEXV@l8xKee(|}S?JCrfS>n%+LhxOfJK~`2`kq*^sj2XT4B`)628rd z;=IW^-n8TdqRQt01H*V~#VEq#+6X1O(`TvPAM!T!dW=_n6OmS8#MV2{FuTz)*9jTmd zPn(Wch%weyaZyUQ^I-xDlg9MCHZ+R*I9YEs58IrCnz)v;WbZbOraI(I(9)|(x;8sL z1&y-&x(Ey5o1AY1Vj_Zy%3DvOd~EosZJg`>pLhTFg;a?#baZ9uelfAC4WkQqR@$jc zXr>xtHB_)rGW;nG6YDg|oNjqYryvPSdW2)s{MU(q{>P>NpEr5$RW$lGDyAvwD?1XV z*;mUQIvyH{IdkD8pb}{5cV0B4WS3GcA=OC{>9mJAw)|{;qu$vxK+L=x(XTYw8-S9M zcvT_Q;WlgKqR~-O4)GWsH@S?A$r)rQ4|;ey$8)fMMmsI3xVBX$I(KIJJ#MB}YwuVI zx1}bOFv3veZ#wf$^bRx+rw59vSIsfh#3iRNgZWO30d2U}J#5U^;mfSz7^G4|YHv*g z$I-Q9i>`~{5Sj%ax&5vL>0hOL5kKvj^_`282)s{AGi%ZW)SU$f0CfCMWFs^@Q-Y4@EAp@SAFZnlWPv{(9pk* z&~e{QHFEk9hvLJ|$b&%kbhOaW3J3Sp;?ydUyj}bMdG~)`h=@SM+kBxMz9f6TDu75y zghWQZv6BBs4%wSv*KnIO`)BRAq3OVZUv(XA4aSHJ9NdBVNW|*T(*2bFZ3r{{`ibG6 zrYY{_F8;EPNfzz4^-qOb%&Th#PZ?zte;y)nA&8z_5S6agO(A-UvMs<~nM36UHps48xawlnUx-DJDOxJ%opf@_k3>GY#=wjz?!Q9-MPOj0Hf z$-2=>FKn#FBrlZSHOobc#L8->^NUho+lu-$v9TBSf~9m7aWW9q^qR?6-Yk;Oj(cgY zaK);{m3CP5c{7wp7tLcm>fB_8T|$;$ZKH);3&_fl6fUTNT8kFY)p_`!Qc=lZ|j`~P{L9jyZpC7az7 zMpi^GEnqV%R0qXZQO{Zrd01gC`(cS_X0<3m2RBYfnXR)gjv8vjue|!uu6c{wbd?mA zdpz!#3J(()ww1ZzbePZ(4NY5}_~(|#1lTlWoXLqXgQ74b<^zV98*aWUAl-{}FXG=x zJurMvkn0yJ>{Lp~sHLx2Cuxx)@f52L9Z6cCL^IaavQfsjnp~}q?NSE)QtSMG;0wS` zgC-0=0K^BlXYf(|+53Nse1G_ff{Vog&W|W9TEGaE1p|i?qjunv=KWAxPu$KSojeR_FXXlfe8}d&?ZvpRbKqA zeUXQQJ~SpiSxz#(ZPy)T_#@}HLC~dM<-KQ7ZW)KB`R^kU$y=wLP*aJH?}p;|`3+(8 zs@+qIZ!Ll3_3HmV%g-w*(U$wvchyylfYatG{u9iu8=U1w&~TSL|3UC&RY8byGP@_Z zBZ1VvOZOuEtJ#?MWr_cd;ZNXZ8x&R;S9339(+zp@S6ItXETL0eJuYZthh|B*v<9&V zA#ihK_nq94)Qf-d-49YBGz7D$uSkAY;|5S`iknr21Uup%B>)i(ivweVo&0T7z2E^S zw;yeg5r7?$O8-V^0LSjBIsV^0L9YMb5K5m9WmsAxuzyNxKxv3zUS}Fe##C_Z2fOlO zr6cwm(j}1SHm>pf6&+Dcq{Y%ckQ93%I>}+&oOF*2Rr?eaou*LSgp&SHipbD}9}%{t z1=Z5{vWp$2Wf~ps6I)UkTc=aOz|ry`(bSWRe7b4e1BFEzXiQ(ZL6~UDUR6@C!+WXp zVkN*;FSGutJW5MB76;RMHA}f_OfF^2T=X(za`C7Op^Ip%b!qaqoVTdp*7?7juE4x; zaLSblv8k*!uI&tC6L)cVRIw39B9xoZrL@={-%>c&PeuGYD2ib%)|S8A=bEjNw2Z$T zGrYk$49d-`Ts^&9`P4SwS^~OG^y*2p@Dal%&X(4Yt3oo*+T(;`vtdm*R!4G?TXW)g zolahhRiN|c6g14=TAW({(dlP&Wdo|w~muAjLGRf2FbOV<8RF#Fcm=+DTz(QPxsvWDQ=CZ&<_t1@6b-v5V znkaEej7N^8$I8&$`iyKSk*;oSG7q!(AxcD*^E+TRm}lary-Ko5V#(wz7>EWn>_duf z0c=xqv8&CsNqSym(vnoIf%D?`r*92pgs; z_3uK=hrhV})<8YfcdsfPn19b3xIWw|>-)^7igKfA-77C)SoRS$N*hd;-I_cTAe3t< z5U}4_N#1W|X^gNKYg;b0hw3s46o0Q_TkVjA;?;0KE?wgfO`aFgM$9xbR52V-Ec*$t zAc>T9>t|Xq51!I@>5Bk^A-%oG<2n(=pYwCtQM<^#4K~Wg@_1ZDvfPSgAED&I?B>d( z6ovG?R{|Xl?#b!D4qnxtd+KP|ck^+EtOmm<3nxU*p2_gh-oMF<5hW!)HF$1`*}rxD zkBfh~sBE6ClwHQwPgb%9J|5cm0}q5PEM5uk5z)sKfsxiBW+z09<_UaI zkoq^@|J43%lj8ae$OENQwC%SS*(Q#sYm)cfwIg81++ZvO(9 z=>PGT{~>z`*G$ILxv~C@5SxcBT6+FCH*CMLP0sKy44t$tjaUF0x6&Xfml8iqv91%A?T?KB2U1cma_#Aj`N+**2PkPL(tDU@OmoyK$_lYM|D3aBY?}0l z>Tp1#PJ0}z*|3p{tVpyIV0sSDTbw#$8K!1_YWds2zjfWe-{s{>qF^2l44O-Z4%4Lc zTKedBiPP}=vC z_|>+71SuW2k`+r2(puqI5z0HY668vx;}*6=(x!it9#bQ4S}8)a{Tm^v`wtqLKl+8- za`Q}ZzI&1GZ}n#)8PCVC70<%G*jCNnZ&cXDA_kihF#tz^s*Xn{Pq0SUa-APB+ir!2 z?zs7&DY_A4{F;t`4GaH+V4h)A1>7>8*gXR$)vND+ivOwFCD%OvYPgM(ZGNQ2T6C$Z zB0^FBE*-f23+|~o{a--i`=8?9i9qB~KOrbH6P96>2{&*F)d9ZrjnYQU)67vaD+Y)S z#r1kJf^&6UWp=Tp3$$&)Q>%D&_#pC9w!^ylUUm#Vp0`?Z7m~U;aT5XA@0&3%6ucSd zuEs8V_#0nsR1!8@F2cpP+Y9croowB(6h-7 z!JV?OPE+-W-y0xAMZEg=j?lPo2mb58=bl7$JdXEf+;v!VsEEog8@BXdYft&qadb~{q$=jR#q77t>f#W%Q-7ucfR#*(JbURA)1SKN9c0Wj!+rPN^ z4sP`VxpbsLq#J@kxl3d@w!(qiU2i+qB8MTraiSLTSSJcw+Oq1z%nLY%l8CX(Y*>w< z6td+cYr1X6MxEyLwVSHOlG*K7rSkFm$>Ls{c#qFQ<)(Pnl*Jf`eYcb^LRhTa{J#!! z?`J)}+i$rc?;5J2k+T78vpx-0;<1IdsJ^o4`Cz4zbmaxLlt^Zy!A96}fy;D8RrX33 z=-dd|tg*8Gx{j~>Ta#Za7Q-is*CQ;%k}l2D)@m-iACeral3qOlI0{;J|2mMUf9=!@ zxS*t}xK?K|d(vD1iMJFP7QqVdS*lF?Qg2vfRFbWP~yyn$oIPXd$rhh&>m4Z$#+&FV5( zN0V|o!P?eU(!zr!r}O#Sg^*2Fyxd2|#?0rtdWq5YTE$;`k2Gni_%<`KhTcxt#g=YA z`1j*_dugQ9r(7z@L(zw+yxC39!mHIHxgc%iTS7NX;&>G-h8VJ990GZf9V<#jAH!6> zO5-kA)&(2ttBuG)$KTUJixuqtvek`*8*J%B@8&Pplk0Bj5~vsd*11!|-w!J4FP{4B zE0_#dwJAlK5F;XrA{t3y#o%6Pq6TI=B(1+zOGJ~7HiN0!o*^854cp(Dj}4AaUatMW z&+^`9GhhaqFl&rN#I0NjVNraNZoxsDm{5x=7=JaZ*9d5-gL7iSTbmb?p_sWx^Q%Q9 z$aqDCrz+?uA=`_TA)%q<;$t8fGs`1ehQ{#AqN7-4a?%+ldAAOV=Mam0xr=EElg1{_ z2F|Vwx`dn}$wP(;ikZpLS=V#%1>{9?G<@B{%@g>U2-3eu_fz~ksZ@BrjTM#pGCFtB zY^nwm9KaxuN}bw!0nGI;x-hTpKuBa8{>U9SbA1njNuznvp3neH)Ts8${_G#v3^^N_ zz~Co#-vFq8^WEQGeyH|9dz;4m7_3%zPrGz?<*S9kL{ROu|4Gy{zBkQx<^!YYNxi?S z^tJMZDZWZs;?>=yj~>L>nAF_l${#HuiPq6LXHqn8L8`PiS=t+5p*xCwQK{>Ly_dSh zI}t4m?j?t*7HQZIcT+8O3uk*RkJEEEV6MsLg3Nxx{Ja3ad~&{FG(U1CSpZ~DOHqu$ zEzS7*i-_L^8-r7o@acLiDq1?)=$0)52#!ffUV9MuzZ*Os@Z75>6``90kuimHhV83d zi^jo+VA-+`PD;Z#G-x<{B;{W&>J^TM40PY&ABCoh$)zHNFz055)qK&-Xc7dGm2zv( zJ)*lGt~7SZ!AC*>%diFQs?zz&a2GOzWIAvI_Y6Kr_fszaBmPY4gJW197r&5CN(4d+ zv=~CUYP8tn0tErB=wrS&$^$nwZ^lC2RE-~$NQZlcbm@b#bql%}iH&Y8R#g$XG8&*E zBGsNqHeYF!&Lj)-zm^Ef1I{7VRt%O-0l;mvCDz48sKq9impcme>hO=MDu{sw{7n1= zG^)Myui{_Tzv;m3{Z85tgikI2o|Z%s!2C9)+tynWn1ataE(DB442qhYZNW&F#-(Q7 zMY}30IHyLb_={eN#5?!o4*!Mb#NBIu{h|I{$iJhLS;CUUEUBv`OBiD*2OcyM%I$sm9NFmdgA5TnKMz zAzSi#qc$jJoBUR0vO8f4k@}{Lw>I@@I~MrQI*eyuSio2TPXbct;isRs6yZd3_id;fg9;x{)2I4V|cIS2<6qb|SOaCZS=a zN^`!@hEvbOJUiS@Z@y+9nmbi#x}*?!!$k1>zC1dp#6yiAYgHj3B+a3DQjL;*H$JB& zw}fmq`hU|3%zC}s67vzT&luP>W$eLh^3tJPH==9GN6S=|mI2C2wd zsbzOM@TuXWlW@aS(72hNvxhF_-BPAy2ZVi{x^OJlI7ZD_jn2)<#_%wYnDmJW&~Ujc zm~n92FnRIkPTo811v(;?lOWWu*U|Ui!cEIgIn+cd;~dBFaFi{Kg7Q> z5fP95`=0~b9Do~fGl)n_n#nB)~R1_?_)T!8ov2Y=!l!)moO*059Zgymmg zdg=uS=l*I*RnGmedUy|G{nx`X`Kr{g`___!f9?G1ReMiLgNgWO7yMn|x8dRk3H{7= zK2mj7!?V8+uwysgA=SV7>%Ymrk@iF&a2)^oA~43!;FzD-Jrh83CGF>{uO3VDXj7_R zxH<15us3j1Yb}xtI-Ecc0<^xLC6nb{!aZjOSk;TX5)r#^z)2-s`V~S*%X<@w@z@Q- z?xnX-;{ujxKy39Vq6_`zxxyOOsw?;cJ zZ_`zqDgJopiZ$H|M%6lXIwGC4tKI3aY8MRGC)#$I9z(Kk!dgn>jssOyVh0?ZbmD(r z^}PpS)~*B0(n<>T=YmaB<>?qdJ>`Frq7*<$FtciJxgtZ4p?3SWkV0v`;H4_ga zjRYod#aMsj3Fm_(|F@VSBPq@>-E1AzT&Of2vzy@&Y9+x>6RWq-mbGH?GAMnMJ(--7 zByDV6q6(7%nU1Kk?gkCJ=Tbl_4VttlZEn?SE>b>L3U}i6F9P}QMY|vB-$~55Hug}L$&k$VUy!qh0keh_`n53qP`R9;2Ungar@1=BclO&iRkpLHLE>&SF^Hz#rHG%uFr06TyjplkRWrUWj@k8V_ptI^t+!Lm;&54(y)62U1nJs9x6&-?$fq zh=;^skUPVD12?gP2`6{J0ZxZ4LyzOyxN@MK_dkfffuEUM5hlH;cJmS0dpqy872TUk zR-XgU2pgCG=P%Hp7IJiNnqf(eBOri@F221s|Kkzxenskxu+o z`#0?`zQ2(6s$Mm;N1)TXb8%BlmXa^?RXH*BQw{9`8@KdgqUqM;0UK(J-^1>vDKsKr zEM?fh$ZQ8$mMfL>B({%&yhKITI;NOpuLv&m^?b?IB9d61>u&J;Mi;Us_)B?m^-y`c z8M13bwDL&VXkGZwopP>LUggZ6()Tu}wq&{~clN4u&sYI@z6QQkGgsc;J(!XmSOiM< z&y^OHZ=@m5wbhu-)GX|gQX|2I#q~W5UmqhAh{josdmjcn zPSTwpqIwnXl}VaHIaJ{ku|crmNMuZYGI{f{%>hbenLZ#i)wz zI1N2$#|;q?sSpXs0nHe=SvyYkLlD^QMcx5oFwp_j0uXXULJ*y$>LVlUGvQvuOp`W) zMl+Vf#u*o``X+V+R#K8HY3KdB`gfrVX-`BT9#5kBh)w+mcEO-QRD10^l`HdsooaI; zKENHFYsfXybm%XnUZocjQcp^%5?vrUUqiU!{AaZ%nPDc7RQcY@jG9> z`*_&mM(zS;erl*;|JKQCM-uNd+K6pih+_@OXy)ti$wCyjn%rQb@%-imN-|N7%Es*y zO^YPHM($0gpG*INe=vM0zj7IZ7Inlexg+&wx&AHv&+#Jd(O50V!jew{rvmw`5DlIZ z4du)lS@u&lM_@1$H;f~iV+cE_TvRUOO2{Vw2n`XF`rtS}2=)eUiYqMTY)YO800Rwh zQBkX>9y6Z?6IDenL>_ozekPn8pOku~D``*UHFaW>mefLHdE1kD<|D1hE8T`Om+ivz zAHKd}0y8;~BRIg3YcL2|7!1Al?;=;yD@mv7Rh2+MGvoO+VC7`zvI~ZTmTsz0<3}1- zfp!`q){Ba~`I=uCr-O%(I+6Nb{EPH&$_=@e-4T3f+>;)ZNL6~(Ui_7OFLG7&B%Z>T zmV-XU5FNmnLyos2wU*#H?Vd~0xg)mtIPeh^!C_d)K*{zWB%_MG&F2vUjrHn$xLwJFKv^VdO)8Tr%jHoO^L5B?+eoUc zXp2Rd#Bw-YWx;4GAl+}rrFt0zKzot1CkH+?dhv2DQxYRGACP6!7_#0jql3=>+4F8v zd`TPQ$il$BM4lFGbyX!e;)7FBdGGby69Fi0A&StYRA~05*cV!s-M~8aG{$apIGx90 zippw1d`rM#>3;D(53IJafYoT}-urI7$9%}>YJVv*@F|2_R+;ufv}KQSMe9Fd5e)t^;; zc3S0wK#3ILs}w-Qt9tXH&z<3+J8nD?h>yevlJ^HzQ~)pHhpCV~Q%e9*1Gw?4vv~I7 zxo^jJb~XrqFs|v(Q>;1BHZi;hm26;e1QWdT%QO)*;MZtzDwPA!tnLE-vHpn+a6rW2 zA7UL1S*2c;NWE&W+KF6Ld(v(=Gko4jOHNpOrEoZw3`$VP^XOpyCyGzfozry|p_z>Fx^AUGoG1z0j8;Lo z0@c{g`H_dA%8*6J({rQf0@9xak|z31=6P@%l|8>2h%3x!c3(o80n-RBoDpmTH-;q> z)v*bU>__qwM5MzhQie@y5L2Y?`Q)006nHvGD>Ely5od?CYLsnGA*B4tD^Bg0(v)eH znU#c5WfnWR3}tJ?D!>vdYuv02yA{}{fy&*zge2d2mK4eHOeWAc;#I>rZyF8|omZr% zDeW<~#fP9~XWom_FFSi|fysNM(F6&xaz_LpC{m2w3Ns3efzH7e*i!9Eu6<5PZt^T( z+t^uy3#aN;!#2Z7%at${yvvURhF)^-3fu%&N{DncD4ec*Lx0DrM^90E?_WsY6$kJEH~@~&7TmuyaFh9a1e!m_ zm}WDKGlLPw*>A8%frHX}vtuJZax%k%bw7;DHKfcW5ZS=Z==$=ET4}ssgNRf#IRE6y z_o{u^ra{yP3m@S?IkZHi^BK1mvU$t4sCw;E?rJQnpTugZl!{&C3h3CYnvzFEkys|p z$puBL4Npti?lT-KIsGv?wiZMw!_`sK#Rnq(to|YdA|`g=hS>2Cdp-gbtS9TrSuZ(S zQeS zQVlm7sCrQ^+K)fRPp=^`HwRQYCg~Ha9B!~67ciHakrpf#v13`sxW@EfNUCk3iOtT! z0N+OGg5OnpvwDwT6(Nlq3W8Q*kYjeB2IQC=5Y(WpIRCQ4prbf&voB(@mSKf;8<~(! zHFXeD5$(KQy>_aqwpfm!c(G3@tKEZAmc?Oe0g;e)LP~Iq25_XFR0xp?TxN_;i-GBt zH{&SH21Q~L4R#J!;tPh>G8rbJbc{5j97+VRBQTSwcA}kE5i~B!6G1>{q9MicHuZvL zuPAI|9bGsD1*?__%zS8YYH@7Px?!c#7K})m4VIKnoqwnyb$To@RtL>9zZ@La0)EGO z?d9#FuW!=zbKqu|MjaWMEAIGnl8-@!Ek$Yu)ud33w*|(W-c;Aev#l1#L|9@F8WH_y zO-W@ltW4l@S)J3UhQm~ZG+`&np}eSg!j;g%9UT83B1wG+GqLKNGX87sRND}^$>wMq zxEY64NV0(&4VzpPL{RlAozSqPtttd}%jVfz_-3qRfRt3dlH_Z)_ksroZiqz56Vdp! z)N~B>Lt0V-CG{#^Ago@w8@>!8rpR3X0}ZYp~#9{DFso zO1S$jd{hY)D65D%CTRc=Gl$;X=qQ?8qXCkL6IO;W=6ofr#yYXvEv8V4^_<2M=A}uW z<^=_f;Y#bOXuc-|V@<;+61Jgf=6Qu}tQ8TS*hHIOD$yLUn&;}umO4{k#w(;*#)f6| zCRrxc6%+zM4A@?q+A~%W*`kp#fFXDbs{$$$DgqGL(l%%vd68cA>eY{%8j1>|NM0`ROSzM>N}k9o z5utH7I6Vsq5zo^}W$=%$k~uCVt3qLn<1;{s7&tH*XfJx{)eFfUwfLdhl|+>Y4RAQP zgG1YL>!@LJOc+0E9zUvCXvD1cv~1v*xXK2OBTtI!BSPccwaWjJYorBnw&pM0yoyG*2$fM-E=UcAJo69823bi4QMac z0UVK=ysP50l_tavS!IMw>6P(=!mm<7)sd)}VewOOCN|FKA}mkRbwgIugqfH^PV*5; zex&UJZQ=NvS&4b|-cuJobQ^cQ@PHrVJyWyT%w~f1RAu^j3dX1pC9EW6_M}Gdzum=ZkBc7I(}O`iLDqJu%7{fK+Iskafsgy*+kTfbiKcNx_Ie5 zhLhhye;2=rS=Gp!V5bRFlbhutwf{22)o{fE9Sga(gJw8Kl{;b8ti&+of*Z!sp<%e8 z85-7NI#m(zqF&XD%CPH-rtLXJT8z#t6Iv}9UTqo=E4_kAromtyv!^H5BuJ;_9G#KRL)wI9xNY3i zk?4f;MJ}CP=G_g(64`-csxexoD6x9E;w8X%){+(ARV1s~%AH9=M`hZ6OQnH_N{J(t z7tHOZk(GHD=2jXFVvv(CmoDBTfjnP~rN-V;-X%OVngLAVB5k=00f;a_%b!YRs0ur7 zsIcS4W-cFOGe4^NJ?_H^`@3YSV$gWlz>OJp51p9pfg}J8g;Rmllloz;bQd9;;h}*W zB2v;`(GptlL4B{PSRGRjfoa8Qm@nhOVZDbmCCdPsyE>n8umM|=g6uKRFyTv1yO=Yl z9~%-V?1K;Dow|Azi}qK-4dcg@i*PK=6A5U{Ub-(s_F??!2p=a8B~lEh+d>9KHgC?F z9J>&TWE$u05Wku4042;;;UM*x4XtDF)u($V1rFyZPU56BV7aE=eHpuhAAzz zf`PGI^bLKWJlGw>q^7+L?szZcWp=TQ2xvh-ZJ1c2@nM)ePPpR<4xsEqCnm#7114Kh zSw_`lkQg7!nl*1-;up+kEX9(W)LAv8mTkPa_GK+ld>cnFw;?fBJcyt=Zpt7BVE)bA zP}}fPNJKi3UZm^8=Tu=h!=6&H?<=;Ct56hweqG4FU?I9ep<8=mIFkr~1Gmg=_zY_3 z74227K79K|qSA-m*&;G-p$aFIhjD6^+(N|}l=Q>=l4#-U)F@l2>iu18vPF)hG`D96lH-Md$7$N0yka=M2_juP~)g~HfMr(BQy4A{AWSA&0lKTh?hgQ zcG8w9zo-N@>$g=HE;jL_;XV2nQ{9Cy*I*i&glXg%-M2q*6aSw6)p<|FFY{-^Jl!gs zYOi{I^gw)Q;AS}fmXv{(=m4Bc!OY5>e^35n*yi)4#&QIf3AqWVSqMieh6^g@Y?B=blS1Q|n!bWJR-Kkk3z#dIZS^@@!9bh{#Ar-TepPHKn%_FixRdu} z=HCp=glhY_#@-1@d5?w}0THK7dWO7=WHDhMXd=J1|8o3e++?xR;!9)?wS~NN>qy)P+XQf)bJaZeiGg7q-AM8s^sF*nmiSk*n{|kc*dV>~JLNvj*F={)*3Am&#k} ztr0vYF7w*LB3*Gs`y@2gJV9v>`UN0j=9c;3_{ij#l(ZAM^#1JS+RL!5K-FA+m(28J zw>0d8e4}K!0kFACc+vv(*SiR_*i_Y%_MX)H*A5_lZkxXj?q3IfYPDhYs?+X^$~?S8 zWV!vhg)nqu2;A_2`QXrS5&~CEJ3exU=3i!(DUxaI!D)Jqw{K{Hyfivwtew1>B@xwm zb_2rV{MBpciPB?bmLO>tswAYPLPp}yDqHHQ=+)E=jq`+Xsso%OX6?ORJMuEqqY0jj=3=!bnm2qOmQ8D zoY4n_d8Xv?b6vl=()=*iWK@69YTQ5s7~Bv+q?2$ca&oHeU7^`1<}qxvq=Nh5p&ooB zYKa=6aJTth5&KNIfw23w<%W?^u^tKoeHX&*Wi_#0_i$uVPsT{Jka^N%A`(mV&OerY zj$(b=VI@D7RrO4oZBgEpyX!Sj8*Zx@iWwd;c#9m`C9$6iH#?Jehb7V8uGo?OTpsI0 z!%1`dT55c}y8yBu`KwBeYpcSsdRZl)_F|Qp%c!;&Uqd`t(G*T^RaJs0pcuB{fLqr# zM|xJdcz^Z&+Ur$Rm6*WO=|v@p`e28m__QP{8fT5)O@4{F8aX+Z{x*VZrLs|g?EPk3 z6s`aRDK*TkDSX-UyIDST@zs=d+?XdN0(Z&_0FXF(Z7 z7LF2?zI#foo%tK7Ius||l9Q8IR@2i^^Y96_ibyBg3r*(}RG}7=m}{?mt{IxIB&uHB z^P&0gr{?h)c!kNg!f=gFNXb7?BJTZQkS_ z4bc#7ord}Cbt>gc%2A8P`6892oNqS-2Z{v?T>gY$N(zbMDLWjQGMyi5LPbR37H}7$ z^{O2f23i%#_H6p-Po*f>i69M!m;s*1e~rLR&9q;2ahEf1bh8(b5#~L*uzVbH;mxuxT_C31+jH@39IQi> zP3MAI%CjtKxj2tlwHNJ)#1n{_xn&%jYaJZxQTgKe?ds9{-VZL?%Cz$J;QosIhKgBo+KY<9)vrI->O+=NW7J^Rp>gi}vH9<3X7aBW zdA-Wn6KOcEd*X_j1!9jK<9|ATV!Xxt%rtSXnzP*DZ`ii$fVSn+kq@n2I{)(KU*4g* za&`Krd$?O+oiHq@j|ami`!CDsOM8_{Me={^P*50mml!MOzmE;@84&bfLkOE-I--uk zg;tMb)D3}~n3}>NnF*+>i1Yw6ABnukTeuv4f7OqG-$)8?F#IQT-D+q+**KYt8dZ%M zry6-1tfJg-kG)ErkVFkf@$)N;A}}2i6PajvD`qCfx3T7U7her%Mr;uqkrSUj4yX7S zl1gHj--H$SDvB@dv*{KLSG(setB7^sxXfgT=-ifwmiYej7&OVeKuTQA%Giuk63$ek zBsovh3bW)Q0mzdmt9O^8Ei$}RQP)SzWml1ne0_vc=( z626KWK8YIJq_KisxqEa~wttZ|lB2QbI6C*Om3PQ{r{UoK z8fJqJ#W%~|8p8d#s^Ric2RJ4#!+5l)<_x%aZ`L~Pz@hWc@@pMs1}I87n>$ z%JqKPxZno(<+O_@W^fxWUlO_Ka4hj-TY#VJ`FyTfMwBIUVzy~XC6h_UTT~4@8s7MEZYko@0SmHl|{G6yt*^o zK<6iGs_5P`A`RCPc_I~tJ2*Zxd}_FHdT`{J-XNErexphIm+PX@X zI&6Lq`?e0BPyg@Eu|M#&Ajp#gS^}%Qt3R9Xa32Vt6SxsoQFY8|jZe0eA?ZP&8ghJSncg~jyAf<$~`+fC#l`w^iVZ}%AM9V^kPzSU}{uT7TM4U=?T z{SvmeD!xQbK#e(@Mi+4xN=&RC-_I8z%?o{dq7+MnPPQu~t$RZw z1B^|p>u5EsD|0c5M<97KcET>o?ADiTF(|FBLYQ4#onGr0xS717h%QZ$Y?Zo7>xzu3 zCoQ#Ne4*u6CQDY!h<2^|mD)GM2Wh zC!*oPsf9q88CV~ov zNR;|OM)nlHk9R9bI9FIm82}t>ud378V0kr4<2;w|jiC8<$6}Cdl!-dO);lNDnR61d zJgOjoL5$%vpJ-D=Wt+xo?Q^q0I$Y?eGz6E4!6(O0Ee;Kd%6ms$Prz^yqL%PfX(-)M zP=$%T@vOSqA_+J25g%Ij?TK2J_e=ldRo+JjZ`sG&R7<^M?bKg?A3fi90$PR}bWUDm zG(8G?T{SCG`@Ox!P@OI?^+&DV&_23uNH6BU1oQH@A1jlF{KZVbu z#J~CO=cRkSs#gLrGr4q^%@>6)Ngn`epuOtF``0U<7{`X68Y1t{;S-z3Rj$LP8wNQh z@}~ah_j>6`6m2+FXG>(F z@~RjL`yl9eK^21!4Id9^oLsy9$4md?b&lGge)7>D_yd37>w*#y@d11wQjv@5mDCfO zPq02dA#g(>1f?n>7or1jVu;Y0R0y9OJTM2!V&Dqqi&P?^thHjI zTjx$Mv@bs-A}YQIut-&$TKZK=S|%eNZT0U=wD1QR5euVm`OT+=Y|7OavjId9l5^kn zo?C~;HQx_>bluCff4OR}L3Yj>$Ue#8n!hB=Wah4OSRtytHxSU@!#T?8gVeFR+c{trN;y% zfRsU3k)NG&%txoa(tA}P%j7Itib+T&Y?f426$K$&-=?an`6WWLSgR_Jr|C|$Svzdl zRGC^(;oo5DV2HDqnv*u=wtWY)B?SW1t9m5`P_RgDKyDz+3ObAz2lsqH>*U(Yr61lG z4Zx5nQPO;G&jJAp+cd`A7G;u?A|fP1)W&^#LgVE9(*6D7-_9iA0IqRhPdz#RwgH{k z&aCpvm@x~4QB^B03IB4oV#)>yC`=R|(N;r?bhw_W(CmpvTbi9@z;rP7YF0~4mL+er z+5%Y6HRTFt=|pZ2Zz)%PP2){#7H%2b30|yYDGK_oF1sTX>k29$oL+VSA4D!H@2Z}_ zrS{d4+)B&m1a623L?Uu!X2OB_sB%=j&f@w9z9{_Zof`b%%pZ6N+*zs$ae)WR?ak=F z=6Kp{3r+Af+p(1_(DXHq_Fj6@Uik?3)HJ6yc!&PIm#dJd2?&09SnkJ9Y=o+UKnxt1 zj}4BFRQ-G3{eAJTopb_LG+aJe{3}BoOjyab5$?qS5FDfzdhPhB<)?-nGqXJU=q4Gy znS$e$m}U&OI!Up*f3%Sj{1h+hAIZkBFN&GlhEFXqM|>LTOG9es@hOxa#uP{ zUt?o#&bGc7?iQ(2SCrPhqi2%fN(EJX3!$i5nS20FYnd89F90anv9wUtQmzFLp_VO+ z3`GnoV%RnWtFJ)B0FjEyr6Vuey5`up|K{FZu0OBR_3DWo2;*Lu8*15;XX$-dy|%)N z%G&ZC%*DaBifS&QQD6Y!h8eHafLqsmYx&sdweSAE^sk*0{&Ju30LTJvycq1Yb4QCmZ6=2xc6Yd|(J9nmSI6EmrZ;xI}+PBkR0tgIX z*j1!7Pe5UdYrY}2O0E5!=(Nz5@I_(6L`0(D>TzHtWuDNZ(VUZ`<>WdP zB}arriGjA^Zzn#s@_O$7zZd_0AtE@l2RuTR)e9ZAP~?-HYDDKuhK_AC+a=~dsmQL) z>%|6d=|!@gHzw3NNwa@H29v^H)!W-^yWvtRYXlV8wt#YAMm(oZ9NKGTt-z$VzIeFg z%Ro?d+=7n;Qh!#tlGyDwA3?q;*Qf2cnV?s>00-t1k%r~GMG^i3f8e))`zh7?tLOhX z_?r9!MJFkKLUo_RhZ{mRUjcUY!Xh(kF_wowPDark`8bnaK$pnsu=lJp{12^UCn%cLrO{oI+4L%4^-5iuJdX$5{0gLsre(Z}vv5~2otX!y_& z5%r=XDvIJwQoj&J?-vXl4`Hv3oUi?lH~;fZL@@2OdE5+f1LnIB^T+Go1Y6kjhm~Iz zriE4}J`%Si7m-($>nHcx4S|~l`c_G2RZ%^I!Tij85IvBpk|)y2E~5{s=GBGB)l$n5 z=ue(G7MoL`C0Y92ZVya3gQ<}eEI9|OAzQm3wYD%jY)62t5qY+i-*Z_tUYDk=BeRe` z*R^(EMOI~WEvrpI&KeNS$4%LVw*A+M9Cu3o(vw##e6vt_Gp__^vbh~el7&rQ zO{`k6OBSVMVdLFyOHGNqu!B1_{M6#${JHP`KKHMkdL`!huC8cjKt^1wfeH`H^YAL+ zt37E(R~1*i5^TyfAS+>syN>mkKG|Ua4 zB3@>twY;xkWgu*bd*+ES7TYio#G+8sHrqYa_}C`E*wS-W@l%sS`% z|DQK&-riMtu^p0#=X{_@@v2KW>~vNc%a;g>1V9i3L69QN)H^Ie~=AYu{r zYc{(!o%!liPSshd`+lpmQe+(!YT1xxK=fhV6o9?6bL-T1PsI<3CH}moQL~?hoiE;{C0ec? zX|7g06#`*W9n~0N^X4+=>b&JO@=wD<`-{BcOzGgf@;2a3NQ9Kg>Myd#D^qe8wILGb zOK&b`GVbQ@C;Q_CqviMP-ERxqk?>FW4dI41{imq`K*Y?XXP9kqeOm129%sN&k+pRlx%SI68intFgz3wbROnH?-qwk!^fC1k2=a zZc0UmB3lgc(i#)ZZw0KajBl-3awRQ<*O!N=GRgkmT&`g*+AFr<>!uDA??13-as}4# zRg-S&^5vqmPw_9luPn+&v#Q$89Ga!e6fdkYA+}L#D$kX#lr(grCCD>II7@Zn^kb|c zfi6<&Z6WjWFUf%#=Ve(&rJ5}$mXBAl7X>rG!eg^3ZXHZ!4cOGpon-D3Ou5&$i5zhQV%d`)c zw5m|EM}e_&7Xx_-Ly3j^P9EG|o9p<$-}L!P02_(q-4JXQb1OqNeesC4E*J9chFMG2 z(5D6ccD-4ns#1zStXDS6!-W)9ila^ZJ}2Gn%>b28wM?;>SxOAW zhyAo$O}wJ1Et#%sLXwZu0xLZkaqR-}WXEInnnaW6{70C`usJdJYJceT13D3#_(aG9 z@Cpri{~b3(#6(IMfdLTalbEk+pMWco`pwRt@CpA+*y%MV1@`4`O};nEy!8NdXYhBD z-kI&qIefj$+Y9eDBI|X*4mDg22i-#SL%}W0{p(=23D^s3?al6Xt`8|E@-> zs1&y!y*Z?76ZeD$!WHNDZFq^?^&}2|giw7E$zvlTjx?Iqyecl!LhLtLfJifyE?y-n z;T;+8m*ll;&Rx=YvrC%shbt*+&dxs9o}U0iBeT=0ihkIO==#nmXqGZaKnU?LnjjaTn^a|H}Yl8%fFPmIuCP-yyt} zGACm$y_}4WcKq{Y_%;v|cc!|ZC~ky9e3u-p72l}dK9W(kHR>D6S;@FTzdZ!4b(a|X z{KIn#)tr!>J)!wy!1Ddaz0)VJYF4Q*3;>m$9!{WTGx(U>{ zYN~~>8Qb!=6Jb8WyRu_s_rTzX^9V&D{NgkFUOl^FZl@uij69s=-0Sl&{_i*Y^Mw>_ zge5(qBpSsw;$77uC1aHWwbt7?uX=6JVO4QT|7e|)wVu?Vf_4kGiXY-?6Ye8GO4$1- zb7B<2`n_XR^+{0bJ>vGm?ythfjMXcXQGa~wtOC2(6izp4D#|Lk>GcW2u+ z43(3PH^?25!>{d9AwqWm+Tq$8po}+e!X53gT>2Xv3AA(CbKlv~8$n`A zj|FX_f~zH}`gtHs(jb@P-wuzw7Vg^7_CaJ&FpokD{hR#R8wEcHZI*LwlIqYSkLCwh!J)lDnnJ-@BW=7Bav0x8gkl_6P5R zh*^4(3v&k@V4tAR{%D&w4~16YR|Rf@tx*@L5%b9AD?osy`I0Fr9H&rfx82*be)t~H zV)baNm!ou?-k3jB2DjFT*YdSF`;@#q>ed~?jOX@2>4rII3*4QH`%K>Lq~2oY;hN0H z3)Ts5h?*mWhneMN6*6aL=FVT5l`@gpvK6Warkh;-QkG8iw~3r9(rEJ>pO4WP<2M3E z*~fBWc8(O(Yq@-hW$dSX9Fo!kJSR1It??Y~wOMJLBcTnh+!B365i=pJm##d@4c8hR zij~aJSkc==5;f={OS!ir++doH+6QJ5g4OBA&^?~Ihn_(8I$0fwhydQV7`0vf^&HXc z%?(5hVlacF%hYa(Y^@a7+hian=8olL%-Ikye!Gr;UZLPlYF%R&8_BBs7KUHzLT83^ zN5*>6>a29lOS+}{jeL9S5*#4--0@F_3W)+YsbXUQ569Cm?Yn}enjuMaCcf%B`tKo zu~V|n>dv@c9SJJWg5tRs$(ca}RN8NwnUb^G#-T8lRv%8!d3hkVMmh5dk`n5Z5mZH=IEDdO}x-5@id}O(R`1CN1IGg;`h07a%dwlg%5td-v}l{{`I56ey69X(ci!@W8E>pU<`Pa`S9 zf^!AuCK)#)IK<3SlFKi9WWU8u9ae5MhJuC6LvJ2BvKXHR`!r%ycZXEcW$UQxwATu@ z7>#X7VWqYq%~|n-w-AhY{^{+?cza!sA!t>5fcfM$h@ejAoy3y zb0J4!jU~LIfjMyPxbGD^ve|a6+?4jNRSQT_)DJN|Bvqd4ya<#RBuGCTA7YzO)d2>S zU?a~s^pnyKl`^S5BHG}az3x>za!{(acORE4wX2G)ov^dq!Sb6h-*e4!V!4nA^a}d~ z`wUFGoZz>FUmmz|uAfzXW>%3$W4Zty8fqWef;nUCN%73;2<^lPvd z@q`+@QhF=Vg$A{w_&HBFRQmo958PDL==L#dHDr@Ldh4@P?=|x7fo90&5EFU%?ZY}( zGOW%Et8?aNd)&}a$6`Rv>4%OwJx%7J>%N>+$qp8y-AQoYG`pE3q#kwO($2k(=dH4FTpDuhbP~G3_#3$>g3@hr%s1)`t!&1?TLt4gk_cE!1>Mz?JcUF4(I>@B*Iru&8?S{A)s}Au z#_zQ`0avv@V4na>M8vXH_HE%825z$8s|5|JEPN7kAu+1Yz_aSatVHG=hoViTY;lq- z%j_#sp~cz%9Pa;C#iX0jleL*PHz_BOF6bn=IZV%);O_GbtA6ZWlBxzxBAKxN zlPGdjnPxv*5k=M!3Lz{3wF*9tQ}S+vfPdK^K41G@XOuS#1t%W#u@F;ZM$Xl`pq3+c z`~2qlHHxd=KJSVUr3$pUf5sPz7KoEIo;~)7EArS2x7fd?YV9x4%#`xe954o1`}96dQ#Z{xH~l2DLH>ZDTIzl&GLY z1UVVG7&dGNot{SfJP{F_$}5a3uERF<2-OWXbr+=V)s)_$W_5$W4IQ_v@9OM0$>0@% zHwN7dHssnGAtDR4)v!p-V2ys|hN(7(erqv`XTh59iE!lgD~E!1K|jiKo>ukvYR*^+ zh<-v!I&kH@mA$&neXS`|cZHb

*aELKg)GxW^&HVW1E5JX=(=xG<{C@jN!Zc$Ue2$?}O_v#nLVlJmCV();0dZ z?(yr>7ju*jI9S*noIepU!db!(k~1Ov#yc&T$TLOqB(E5JHAecFQQJz(hfEfi^TpY^ zwYlT5BPR8s;_OgXSTY2BRuamt#QyvuFQ@^RL7t;b*x0H{?tNCSRlME5|(#q`q`8 z!0E8}q2%l{C$E4=#T|?=C@|^M*Qg8iHOwM)dwh)&MQOK~%}PM=$(%Hjn>7=V+L@U> z5ry!E^XiX(cgr8k>Hj+SvR?kb>-E0~AQpZ6QMvsGltW3f;7kuJe;Pd*C>X|3Dpb!~ z(XFU4W)DnLmLiMlx3XHj!p|XD))`hp?)65~&2g7Drzn;z*@-?Ev|M*{tN5J^0qQIS zs})uEi!LcW;)c?4d<>hmAM^*SjZHfh=_z@t&Xd)vIU`2hi;>O|iqCmBd!ZNVTO*1Y z%8@ULP}Q$;YiWroS+qy|sKWW6AKitu@O1~%Y$8{0fF>f@oC#(G+L@f1$9u?4(FREymh#f<4d%qoy%W_;uBwVg*s0GTn zs7Nr}7*b)Gg|Y&+YB{c<@oQ9u*7|Dt6^!Aut2yP+WuL;=mBX{c6u>1L-CSqD}z2vg#e z4)t0rNQfaHbh3*4ydeFYY)S3P!J zxR966X{3P^wK^3mY@NwY=8f_U+N2?=)Jtw8yQ5oP350N#^#|mQLBzhj*liQ$L#Nx! zNZQU;a6GeTas&wC;VO;?_3mfnTuh3UUYa$LBx{LDbpo`Nri^_`#7)lzh(I+`KzH{C z+`gUIfdH&b3-baJ!tHAi-611C4DnVg(o~rGpyq8e=IDCGC~w}Pn@!PRYsQ@v>!m$A z%m-Qy&Yg&lzzy;q)}JD8Kyd6IJ7(y~CfXL;cCA^(jkj8{XLVUKgWkB#z5Qf65Cmsh z9TB3dZgYsNdU z;BLuULUprY;YM4!zWG=un+bfh)H6>bnJFLW?npYoZ#E>xfe16D-hezStnq4JWe$9j zcW3+`)a&zlf8#kb^Lgp62ShuV|EEZMINeH3t668{pHx1q{@u?)qpg_!*aiBT`0)W0 zixz#}oD#LRf>AM6-Sm!S>ZU?>Q?rrRfI_t)Oawr#Avj)6-XHzp8le?wMxIU`8H6RFY#FgRhRyy(X6X7!e+@p=Z1Sg|OT zv}kQ|G(~=~J&P(CFm!>4Ji8X)NCK#krUiLB-KNrVTCdxSXDSvXu99viYpvRVRKQrh7Rri+h?o~5lJygD1OfAfd`<@~ps`_J|AA6VarRw74et^go~Fi)%5vQ!5`t<0zaJZwbWa;5{`(tB5= zXYTzmRh#p0dib?n*vd<(I-6J*^tBN$r7BjSBIGv5@KjUFvNBrgf+y2Cb$m(-qjCZs z6+@z^3!<6YBOspzGm;*Y(T9dz^>vmMH6SuKHjxs3A=u<_dr(;`D#~#;h82DI(~QBq z-jqj)SWoLquRL0+4dwfiww0A39(@8z^4Cn=1#Z+rlRv1NztpwlJ%H}FPIJZn5Rm@+ zdMTOy!^5ZRb+%yqb`Y8|uQG#k7s%i;GF@yYy&R0nwiAy^KBG9j!64fm?a(+Vpso2x z>(P}j=6!@kWeufW3pD0JNw^o3KJ9@uO{KJgmKzkZO1K5~yu2Pn|$9fPA;&Cl? zQfS3kNkxTO3mJCRIYfhI=Qg{D+Mem6groT%CDrH^I8>-xmZ5sKw@j8+8T9oLy3OFJ z8al1Z6Ag*?y7YR2_G{HE|1`)pKjGJuK@e7XU zU?PHCz>ev_bR@dbBs}T4Gy$Zo>8AnjaLuqh#$9o1YPG4SQ{bo5v6wx(BlC%tBlAKe z68@C+UBaKhHLWH*Cyhq7rq-4?YiCR(9aD73UPc`Is7rqfd|m;bG}ZQ|HW^bYB%G&)gNuIyo}~ zttYUF>anDTxDO5K+ZOBzzq*lFw*<8z4Ksrr`T0x)W(aQQ+u`M~91pU}dR|HTT{Ug( zVk`zt+T$8p=^1!{|S|viF>F?*~7E~L&VPU;(1wcx!m5*;d&z?{RK{A z>DLEpE>A*fW(kGtEE4OCwZ*Fu9|p?|86Hma%6Kq9qvf2AD;SI3qTRstSjPfYPCI2P z%HJ)V4S)=0DuX?LJLpMEZu2UwB>P2(?l;QU(-F*`%BEXxFpgGo6YGgm{xee#4Lxov zN~^a+1M(1J?1cZx(KOvVG&$1uruT5pK^1$we`2e{eTHO8~HKf`_6fZ<aD#Zbz3pNHIZ>Wi*Jc(vswAy0{kZnbB^5 zI-YCDbt7uj{IN)xnF&C+iu2ANygw}dxBx38efuh~I=Oc|)^SypwMwXStV%@38`jgw zrmqGL@PLR0bgCH#(Jo}Dz_0xg1h8YbEaV+T!u1v|SA@XcWv&dAGCmt4*&^s)*&Nno zLG=zM#d&(EXhrD(irImzbrV3Vr7C4*VHnhn)10@`Hm-HNkpAnHzvHOt)|B$hcXX9d z4!cA>x}`y4L{UJ&6ttE4FuKl)whCbMeilV(2++kc-)ZLy5>s3R2d!60#=YTerD|n} zYFemIaZXXB*aq!(plbevS%s_Za%jy&@t<_b<_6j!cgA8gjW#GBwTIaqoquH_g!i!i zDd8s}?G+gF(x$$n%?b{MGpRELg?zrpfjI^o5U>JkJaM91kb5ZIt(a$oPIkW@O{EpNL%8i(sfnV=Q+5=q$h&jo_(;x)yc31I9JrI6#@Y{z|C~5w zSq77VLVtgbXS6o@XY9W13`|Y*d(uEfJS8Mmy;P!N4JPL0OYJ2SNz9ITVRvA6WL}s& zu*!OdyhF~w1tgPc$cDgLtFAXaRjb!~&E2fB808!Wu2W(?Y{Wvm@J zRPphv4Te-KGnBubLT`xR@QvN#4}Um+lJLJIyoL1*>qX?22xrgiy7Q#0lBrpH(MJ%t zoTQtrK%yWG@=Sz!5U%1~E0pJ4ydc%+l`5uimE{{&nTaQDx(Z!f;OEyJRoRj%A@my@vfR{WrMw+aIdl~( zabaT>pdiIjR6!L?Rs(*G;`e%n)qGuRK-ALpEJch_BaJZxBkYv1I)gnluA z)m~Iy?2!?OI#_h`nD|N7eA#|uV4zFjiqlDu_mF*bR;Kwe|-z#_8#_ zwm`$IsZ%PbEhb!kB{5cM4&9l)bvWHW3<)A3u;;_eAs$*xAoe{S`Cl_e8D30Tjabt- zU^6b4eDDH|4KBU6^wE#&|pa0l;?2Xbz`Uf0VNB9;Db+Nl%*HMhZA z&R`xV^98~j53BqVy`?A10KKraKT~O_f7(@>OnEtPm?#ewoat9unhD*sFsT2ZjA^hx zsSfN3POe_qw>y16KpFH0OL7MCP*L+wG{w~0S_bLAx{6Rw3$SY^9jG!R(5+NSQjJma z0NB+sC9saqiGUmUqw1wg6!!EH2xIueu&%DO;_I%B&5dYmnH9_(K!bi)`gti3h!^Kx z$Q_ADZ>g94ikY$(ee z4+4_L1GS$149Fe&K5?s5qcY`FL%&;UsKMKWZgjHfi8zpuyn)H_-^d*ZYc~e|4@uh^ zmxRL8BUBwxjIuehJF+`56XYi0r-Zk7dlYfLqxz|>^3-Sm=GN>Ej6HfaC_7&S)%iV~ zw$=!raRW?&$+Xa#YGzKGJRrAly-0X-%Qt`c+j97WmOn!H$?h2FhlDd>-TF*1qQ3Rl zB3MGBE z-l1oNt#uG6Q#VFx3Div7?S+H(jRST&4n1WSdM+D$+TORiSyuw4%P%%HK5fm~m&~nR zPr}%pJ|NV8%7m{Pw;%F3o=H&uK9X29GIyE#Q4g1a8tNWfgqk24rpIHEC%_eoW@%3MCpgRyz#6Dol8Y`93tyw^~Ys-IfOOb-f!q^><}RDZx{QzvxJH39L6{XF&Nkf z6y=_{iVT#R5OOl z_{^5iAYry}^Fdb;3W6hYIcE;=iCsw1TB&IxWyz?*4y%G*0mvrO}n8m0@p~5LjG({OjRTG-1 z3J`jdw2fC><`EPy&V2PxJo$7`X0iWWJS73EfU}XW@2`16~2P**OJUB zgNn*d97RmT?5O!fk{h1y-V9awAL+uR>fTj`!8;_{%csI|ect>z$gj~_F&{+S?d%$! z`=DChdXpplLzkm#*q^EKIGsdD3Qr3K(>!h&8>dcdh{m^SX^q+fxP?XGY7Du;Xxwqu zMMiiB_G3=oJ`cEQ`7!*5Qm+G(BI6R{-tbG(p2?SKq<(HQ2h#I3a$^U|KWxXHB5CTD z8PgC-KX}aYdGu=5sM-iq-hsgrt!vf1}L?W;gNXt zJUNfb#-=qPcFmb8Lzo?3<&NsSa1*dnaNaM6Wmy*Iok$Uxj?rvo#bx+x*-R={XOa3c zb#-VSVrIFJo0tX*%Nz6Gj#os0#eFgQmL-cq3_BO^zLa$h*PDdEn!eT;nkb-_oU*T~ zv&rT}+;%`!-LVhxvP!krl~zgXKo7EO*4k8tP_)X#W|MeyR8X8ORZ(}6#zZ2)RmuWW z#c~lh_+;O7;D#9B_J>A}aVc&yl;4HE#>AY|D7cPaqQYBq1ay>K-nFbugeG;@W}MN& zO(ukM@6y}vRIE=2KQ*P*>T$&BsLiYm;Iwrk_fHKXu9NmK%pOfkQfu0BI{`$F-GN

cB1L3TEIqrmZC++TQ4?`v*BGenB_~89F$1h9{a)Vp}(U3o3y=mM|oYu=@)DOuZ z8f%Xf?sdg4I-HnFKVtGAPqa22>8r5fCi0Vsoc~UAU>1Og){Zv%05Q7yPEe&-5&eeu zxZ!tgW84O=5@&PGMC_QDcwzGF4(#F~K?J!$-bLOZXTnOvM4s615Zi~l#D`L^X3FH< zj}WKbVr<8T36N3d*0~nBeN!97yGAd8y<1+~@_kvpyXA!zKrZ3-#_LZB7vM?|f`AF+ zND3S%MU}em$&4Ik)J7(c69IW6ouOEXs2_w13x-XNrQqf6l@>}2w02#UMsQL#W7g4& z2yjG1ESV;z@(D6uZ=dtY9KI7M>Kmvp-WT&%$)L|l#r zvi-fZfTNc8($G#dt0cwIYTq)4;bdx(YT!=GkM+kp?K zAybXP?aPd3=Nm~C-kPn=>h39x!PB6WmL7B+TMOeKyV`C)Z#!ArM8xs%kQ?AZ3pwwX z({j762%yQgUhaU@r=A{+>C7kEq-$l`0-K)57>h=?QMY7(h?$p#oDXYQZ#P+2%D166 z4SH{lU|%!zS~03OP>&&sxQNktC@{{C8B#MGs6i;^N8cm2MB1HgYoYA%wUn8`Xu#)) zmrO?GYnKr5oZb6iH!<$$PE)c`a;%+fmNWKL*}(qWrjNG7p2WPcdnI-V?-G7s{Rslm zG8(P-8R9#o_L-ZlY$kH#Jo8JAwYfshKwW?MdZ~G0CK99@tU3ZeBR#ip(|Qq{_V}7? zK!`Zr6%AVJs_+EJ{dC{ahxM1gr#6ZqC$Ak1W}|vdTCl%ThKy znYBzW7~CR2#aZW*xr3-#S<2-hos`C(avQp$1bLFoE-t~aTbP~Wg~+F4q#=+Mc$aV^ zT!9s|5{bFoc(Uk_o3lTbHIIUv3uLlVl^g)H9S=2{+{3V$!6~fiKctPQl>LY}wv|(C zk>wVHaWeIV95bS)I{CTZepIZ3m6awB1w`&QT+V0KyhNCe7|Z~!vf?Hec8haI=N5nX z=9X_DA>0V7gp0^3;RaqID@h;{sHm3Ij^!3GwM-D#H*_eDtLqwESkCX6h$X@SsHzAP zk)&`9b)XK}157HYq?0O0gQ*;GTxr(2!KQ4LmBM0HWK?Z-YLdg5r1~$jf~aWBths!k zx6F)Vh3l>o{3qXm&~dPvueA>464>)5Rm`~t3W^i zLO=+pJjMs5igAM;8(1K<6jj7vOU2U>l0aOQO0|l*^|D-1R^t@erM=9BP;6c)QKTQS z+d9gfmk6R1qSoZfmN^RD2yyU~ndLr{x>o42Z58fvno^HOgf_*|yyUplr>=kh+DPFp zZAr{Nu+6&zWKx*B|KfxUur{TEppJvshTm1JweGru8a}%Gp))k|kTyIgpbl_-mDbTl z`wXS!21J~HHq%)3*EApNYLOOas<04|#6|1W`WYCLz%-Q@I?7kmj3j+Yt_@O#hfGqH zRw+b8#R%xkoYnE6nwbmCSVaTj^io752s>VmO9-;w*8H|qF*6yBsD?BYTJn&X5AaX| zFXyo$ok^T+F6XmFRBGo%=1S~BO^xS*${%*N1QihQ;+;RRb8=hdc0>8}9r2pTn+{g? zmh_MGMvb(lVl7)E9rBsW@wlPb5UPh=Z(#EP7ZAa#zK?^FX0(A;Vb%JKPZQ%?~ ztsf}Al1!?W9yKDRdTO4?wbmf|M*T4Z7!(=I?3oY59&(oTza+e6W3(1u7A^e-7F7hI z-n4YoVl^lxR=IP)RR$78;F%q}1M`9T#C%{9;0(C|t168VJZ${hoj`=R+(GOJLKH}? z*%HyjiQFV+lD;nDhKK?Y6MNzV;rf+`O3JR9T!mI5@$S2G3(=BYk^m+%9oYi4@w0hV zXF8@gl&M;trcj<0l@)iF4ScDW4M00rG4qrZ=PO>LGj>Qe&~$nCla0oKCIZA5|eiIoxS927oCyE>EGUtwcOvJBUWtMW7r~143ak=tBr8!!Lmz4>pqRX+ z&31cSIu-ucr_Hx+vWT3dD^2@-C>Ep9n3>ou%cr%uW&W9Y*4bXe@2 zd74AxQU0c7o24|Dbj~je2;q8-r`sr_zD4us-5k?46=mM%T+~6A3}vI}F!l0m-0tOx zFO*{1iuLlMT$t5(t*mYJ?)Ab=ML*avX162F*d0FKNvUI%HHpU>{BsQ8ekgaP@QJnl zRnlj&Uc&R^=X_#!Og?zRqi4~xIakc_^Pyvy56a`f)W&{y|DAd3p?-Fvs;`$v45o)#)KGggBX7S1cqdUT2%2-dwon~<59A% zrsulo(5zB)f9)c(Yq6-MqSzlw&`N=s8a`L#3RgjC`U|TfwGr4ZpXk4KU8%AfFYc;r zn^m5whPHpOx{zE&#$~Vya<&qwB4zRvOs(-n+KLUUp;?0f29B?%D$Y)hHQrN zcOuTAKw7Ly6p4-Uk%EhkR6S+YT*dO{5w$a;X<}U#ClWs$e6fFj_=|S7Z(0+m*NsN=Vpc}# z(H0{jiLq{yZib1Li5)w~>^K=JW^zXB^yhW7go0G0Zp(}Hm^+g=w5C@C ziS$owMi5aB!`FfBh=WLM6DA^;wN^4^WtV~Oo89>-H7F`A+;DRC5N@(wuwHFc%E)+V zl|*@j&SX+Ib~Xq3rN&N8hP-~%_VtO5wHOChQT6be6+ahdDVP5y#^FpY&7Ap@n@(L6Iz0bBG!BRUqsuGF{G3buZmqkRz{cqmtDH+$o@km!SQnm{a+`+UHmS*xWnrPI88zN#x zy!^RXGynk*U?mPDS0F&jUN)D$`|@^sjU0tLX!5?^(EM)w^`M zT)2d=kXku0L>9to#e$D(y<(3F8F_Gt+oIBNnfg+yq9!Qj!>>k_?zguVpk%ZQM`}5z ztTO0j*6MpHjIKVb_sVpD7+4Mmzj$K83I0&OJaG0piApbLqP*7POe>RKxT(UeuA#C@ zHHVVVW<@YqxT)fEW(xu338!+eTJ1EHQ~DgAFIZ=xvXIyL4{TzDYP*%^tO{B1rZTZ1 zJg>-CCtmG)DCgt&jM`quH#g)GV; zT8;Ga^h}-qS?T^bWG6{c)!7r1W6lA69K&as5SQc#E~!J`c5RzXM*G?)U9F<)mi6cI z!tOB`>`@cc!|T)FWU-^CM;Sl^uXHe%JR4?5Am>;FA_5@pm%;eZ^sh+*xq*)0gZGPb zi$jpTodDx$<}f|qB@3>PrFdAJbIv(OfUIj;xbY)>`ivP1SDf!w|M(Z+my zZ~B)p#N!_F8QuFUM6|mN)Q_)adi>O`QodX-jS=gEqVxy9*Zy zaGbE{M&+hd%yOK3Rv`(56>Z5KS{}KYW7j7PzHmm6kC7)Xe!4VNV z<^^=iFh9l#+twe@Sh74Z5VyMFO5}(ZcAkmzXXkmlwv#S3m1>IFGBCed%7{3}-GGQi zIU+^3bBHZ^N8E&*l9zR-ma!C+o3-lu>ZQ zQ$nUMA25T#M0ZxPu^z)icGHNCvRXxY$zL%Eq?tMhB@{{xSG5w0Xs*hJ{Q(;Bfb!_6MK}nITb#T}$6}nLvwFY7QsD>8Q(-h3SW46wo zaTM-MV{0U+g;f^kJ_{mnI$gL9U_}tAQ?X3!Rz8C#=|1JH=4EbYhEFCU{l(RW(j-1& zWJWX}TDMiRYo5z?GCh^k-7Tf|Fzd`^Dbw2moulv0Y9X7#krqWt<}t@Sjw@Qtt6I_U zq6umYOI+T8vem4K8774_uMUk%EA!OV&q}v=BJzAF)-2f=30PNz8*Z!IZX!~~f_M$x zeG@#S=B^)k2X5Eb?^Z|9o%g&f?3{Bh9#NIrHv|%b2nd7~B5K`oq&In#t2Vi9PqY;+v(-Xr^VsahNHFt6 z*^JV~3z!Qo9>VUr{2D_Pt(=2*&Mn>@oeYwj9*CtNXR+oy$b*Y#_BbX6<>4sMhy?)@&%fUGiC*WxCPgGM`Rm@voECB>=lEd z)JC~O6;gGc950?7BZ!1G#vr6%s=Q4u=@u$Kw!g<>pb~o3jFGAt)KD0MNCXJ%oI4zs zNr19xCNm5WcnBq;cL9t|`2%`S2K8I;m z6S`$Nr8=Iaa2wdUp*BOdq84w;)^?y0*;XvMSVe?raqI{CWirji!L84BC^LD+(u`{i zG;u^D3(*RRO&lnOp;Db!-2;e?Un+OlH`|{%(v^s!T@ogr-t(s8_jvl!h#Taa%$fK= zZl!c-@uzYyP1-&Oq7`7G8zWC^iMN7)MvDlavv5;`-OG^~q$}o$o*6F;F654?4yNcx zrHqAgho^jETpACM*o;S$y7o)<-ZbiS|2ioe>Ydy@s@wZTH-cQL;uJeC0W6dbLvh4$ zDcUjl5=WYYt-qByk%IU}& zasfdFqOO=sy0+S^DpGd+5>9SQ=^rIak-;5I&2tGvs$ZE4u4dS7{>Fb?9HUuHFG!n+ zA_OK^CNbK2q{W})hkzZ62!kX9h{Wb_SqNt5jy{AdVk{9yI`jov>P(dH?9viTk>%0L zD;uU1%Vxy{1%2hwY4KJ^ip##LD6FEPC69=JN(U)3UI*W*C`p}d$I{CyFq4c@pf*X} zvIOOvDV;`@2&sBoiH!<%Zgl*2Y^tj}>+(4wE-m54HCrZPc=8Kmp+msB${KmLj>);Sz9#`9kN^>pfFOwRR`COki~y>vEE zpHY2uTQ~L;;ZgJz}}K(}`1O_ul!%EsH0|&hc_s zh@AupYsia4vo5HJ+wP_N)CM95;+$KK3k0&>h>6|oWOPfw99K))Q##B~)Em+!M4sKU z#9P4SdLxn4kZeot!MC$mDlG9+ms?Kmc=E@SUykG!a+Ge^lo(f@);KsXd=e%8TXB~t z#A^RL^IJQD4zMI55}*J{yr_i}F4?g=3cKax4lg7i_m2^G+tb$P(l48QeLQ`P{aypN z@XWr0eOCJDF!PtS8H5msg79J{%PLs68!noNo1C9kmhJiWO&zsF2m`!i>!C$*6(I;n z?8c~HEHfc;l(rTL#e0yxGJRy>X4kY+Bv3pzCFB+dENqcn=E z9o#L|E!>cuAPmM?b(&_`8(}6#YFe24gr=X>Ir=SXMZTIYHZ!I?%i#=JUF4!?W_yyI zR&QI&*f;BGrttxkzmoO=Jv|oWIYDgEwiqrU=H*lHKUGgF*S7G@9aZ~p(1;nbK4)PwkYkl8*q}3%Iv~Pm?&IV5lO9@ z70fI`8S<1UN|T_Pha8i~M1zH!%uEUBYVAosZ(>r{sahql{ZMMDe9OCu64HGqCTB1H zK{`~b(8N}Znx>*K(3)BZ(^?TP1xsJ!pa3w%ug`5;)A+YaF0IX=EeryaaP2c~0ujii z4O4;g5=?B7d<=?n3ol3a!X9*xAlFN{Uc&V(w*UmNV|MM$hb@)|)5di5xs(NBmjbHE zM_HHSsz?U!>=c`x?Q$R^o6K>Q;q?DGE&&3-Ozhp^=$0cp?>S%pxURCUB0<865CBQr zf<%ba#Bw+tZdl(azzSGjO#32RSnRB}X0ypoJ-F$$rB|s2HihEL^x2#plUuwyxWmih zPlwZCku}`TyxwjSz}{G;SjVBvLyZ4s)CQ`87C?Cd>*^372s15t*2+L?n&dJbN zOhnZ(gUCcj=7KQfl465U`~x+rC~})HoC3A!B!y))t4f*i+cvRd$HeOzWDP7R3X?#j z3#LjW1QF#Jr4*8rUK7-@IBZa9j-&Sn1c3bMbolm%e|cs0BCA}_A}b_N{0L8x!V;>a zye=Y&J&TD-Gy|0Y>B@dZ%%)Qn=T8#-Dmo^&R4H0!y@C+K8=07v*V-VB*o~GL0ocN- zVY#cK(;>8;YRdPh?U->?m2;n}`{=0C3YrcYvRCVi}VHs?}NI%Xv+zU~J^#KkOF z9=Qr8^F?jyb2MY<7m}Nb(8Y}(G_<4>C~M1i3#%UZlpFGwvdwOBC8-;ug1t@+N)#KgQXIUw$n z>d%Xt!yH52N?#grLqr7JB1_I4C0syXc+&Db=^jJ-`pr=6eI`%eMhpYrmq%}V$FhTr z(sz`tpG*lzO+?EiCI368`IA#&X9Ekm*7*e_HHg$teyVr3C`O+Dn7dhv55e9o#_a?0 z6gSS~vkC!$$lghB6jfgiEp}Bv=~C4_6Nv*N{kUD7{W0?%rQp6g~057-H z<}oKS$%T-~95il`i(GQ8m?yfKHjQ1uR4WTw60i$5T3}GVbGsU#7H-l6K&`=P)ky~n z>eiqXf0N_z81Xe%%vG_GqZ zIkYe%ge}~j1D_AqDM0&M0LwcagIg4~Cb7pb0!%J~E3GqLoyeaQ(2b|savPGOZ<5cl z$U;L57S~l3l6mU(gjaWkQ^d`Nxr{qyj0YJbwc^5!C-0fS&O7JG9lT>c9Nh6Jw-xI( zgdlMt03deHY@hl)Q|dtWTuEoDmOO-%)=?WmKA7$_zbQdK2=x{~^vxGh1T!S_p;jph+;+9nZ*+)en# z2;x^9UJlOj;k2;BdJA!FOE2YfMryk5FzuRg)2rhGJQUKi5}uN5%@p)~k_sH{GvwVrHpOSdRyKW)bWfrJ7(@0KIGMb>M$y?@~L znWu#KRo3qpSk-+fvP!t(pt@(bo4X~CmN4}y;mz`DEkZJC#EE- z*Em(Pwc9=XnJ6ao*X#oNFZWIo+f1H_AvckDiS}m^HW0<#g`3Bk-zsnPdsl*EKE%VH zf%UNgWN+Gn2|N4f7B6NMXB77NW^UODVN|xej%pK^Ej%dR7c2Qq=Ac2dqs5%5AGuy+ zyK9f@uByzc=qZVwoer^&T6)Bjf(`Wn1w_ntT+YKQh^a;@DN^jbIo47BVtiS4z-3`} z6rp4n-C+GLU9F=$TwU7uvok5Ai*}Sr z^YachLq)`zg{0Otl&q{}$z1FBkpdsYfOG8PwLy*)CH()vYo`o@a# z7Mj$`6W2CYvK*{tb%Tc zhEE+*2G@1A5hKKP^=bfzojL}gCV+x~LRho8_nw!?@9X((U2hVP z-RifO*l(V@(u~@MdkiMm^l>JXpJc1I<;2U69o zNGntT0w!YT>lcZk>dwE1=TM``gbKD)lwDkh5EYYzt|qL+4gTniv^~7lk36i$gB((s0)l=X{E;!=%HAP6y-oLf%I>F-2tz5H0u|B!HNIj;g~7&pbX zgWXhi(UMGl`L8y`JPl(X^0=-LRZ{D^SgUw2=(^F#ELvLy9%z6Pk4gh%Z*2YMp-(!Uxim zluV|7T}Igv@OUbe+1bNaq}dQ)(_>)*ZDHPccNfx!Ixw=KpFwH;?1#lY<4s$iDM(iC z8pewx|6Hk)-`ohJd*86{(rLKQDFEw-2^F01^sY@;&Q1Kcv{@tX5f_F4Z~*&-nfY{b zi$5F=?sk>iMQ*Fa18$iI$4>p8$#_a5A+UP+yQJj*lXHALy5+!ban3P7*GmWi*GpKh za=iu#Bq2rD)b;RZd*XCOX)c;U72J9M91RbGX)8v>y%IolA{<_p<>*6L`{%`ShT6&RFK@(ZXEAL;*nv)BB`U`k0RkNMPsu@wB{r^T!ht zh3h3;FA`QDUk157_V2+xoOKRn^}7=Q2s^(VUfkh@h{NqIoPUJtPdfztRR(DrFE-1R zl>)XozaY&B>vFcp+2CuQ->TSXjb>19Tar7|(C53ic=0=?`RdX?O#%@!FT@KGfEBdH zqrbis>lZED7_TUELvz(}3g{nTKQVLQ%o0$sJg)tR* zLKC2doxC>O6-#U(711XaLDyzQ5z03SH8*ZrV8H#|sdGQk)1NVI%@L^9j;SLv<`a@q zj1`X-L!%hwMPj~{>YV5bs%-CJl^dTyl+PEj!i(q(T-F6zH02XR_PX`0U9~9-4P0f9 z5&23*&Gz;kNzV^am0F)tWqp_`3d?E%qDl^;s&YpDLS3&)(0r*6Ch61_F5No}Q2o9f#nLjC^|^avan@aTB?yDP zW5)y_gmkORu)~nOayvsc_e%~CYjClskce9jhi~7P(+NPhUc%)aA+U4#s7AG85ShP1 zls;9Ml>BQUMQ7%#fAFH5TWn{L`-cK)!OzB4cD<;VK|U*Yyn64<#sj%~-< zjI>1IXh(m;eKvw6=*Dz$)j?sZ8tYjd7i}2}WXkYM70x;d^`JL@>jaHeXZGG&VOgQp zOdvC-$GJj$heCxE*@mkvjj9PCs`uN)JF~bFqj>2}+bGtilR-mXB2n=N)3F-n2XY;D z=)PEeVg+ha*_?AwA3O1Gc_^L4QtmMP(Atd-)Er6rcb^cp(?F6j$1xw67a#!FvVpRA zOCw7TD;w3p$0(Y&_yrL+Oa$P{d}KZl3G)RA9qIqWQ!kC*RXBP`Gr9Z}%>y;q)(kam zd~zsL@cQ`t~I3~JY!n)#mS+7^DmmmTNsGb#8;q|L3zsR-fyv<>2VK#Rdk(hQm45IL- zKZ(W&l31eo6gg)jWaX*qZzAJ%{X0D2hws8T6I$yBx+fhg`;jSDwWTUI-2)z7|J zEgF+E=3uA?U6C1@S<=HYaf&2D5R{2@9%N7}K>f|lyc;Ws;^GGrv2)AubU3}T_v`g6 z*9%FgEZiTHNTFu)YgQAgQ2Vm}p7l5Y5Mjsu@ZHB)o9mzJ`5)o>6EeJdX0zF}n>4C8 z@mf>HaJTqqNV_Y|rb?L_qb2oo(tTJ3E2j@udpxI|sI{YB_kmH~Lba#j6REgcX5V7l z^g|zO8{#l!p7ip|*N!5;{}{eaVbQU86bu7bkxMhP+b6?^MX_h$X1fF2?H)`-z)IxV z9f=m^1Lu3G)foM_j?724IjO_>&#i6>3HE#BI5j z$AleJe7!mQDK{F2=4wl&UWATZ)_4^Di+f5#QnD6YeQL3ickbO02EvP7ef=iiP8s8j?fRSAF znuX%X`rDjQM@`QHujCYny?;-Ubg$9}FzH=OlTHTIk6sZrph<&J zuWm7~M_6GEVfK{X^gZK9TV4I$ z_BqC{@|Rs~mNRy$E7n-CQD#-hVPTzbGKWJDh?)JtonHLm=!xk3zP|l=yI#V23sL1= z+(bX;$h36#g>gt2n&0=5HgP@7(mC6cEwF{cj4iNMWtw2iOeRm+nz3VICjpnASLfZb zc;}s491tK|6R#Fj(~tWyXv-5(I_ZYldB-bD5OU^d~$n)6eYDATAdgfzCoviFgtfR`s2xYFJYDQS=NV2MnGRk00(U^L0-_D1vN!bNg%G|bmK4{TGa()awr7rNJR z3@z+2Yg+80nZn_$n{7RO^VmboST~YS<%sK_v zOGFtHv{E;umtnbEPn?mKlqIs)uN>Lc+O`pmnk$8<#jB6p^_R+m@sJg-`lD0&QI7ys z>$!ULHug-PZ(VFmZwpo9mI#6jAwH*iXH6%32OB z1qIpaN!zPTtKPM#MqnLdQLx+>rEFs+ww!w(xUEf{JGWW5lmrlHC1DN{+_LcN>#{6- zcnjzE^?FU$k*CYzCh*(rNkffy2mTr~^~2rlyM;YXn% zZNZ@B;15T)933;?F5z;HSF?vT2qd4>t>|j7^lCZTw_M*PwVsJcuNRKWY|YlvIsv+gs7sQO}MS)80~-fuiBkXW+&42zJCQ zYd{DU}>JWgUnC8n~C1#0tgU zNsKBwkQ$9(C|A$1`Gtx7@tZroGI6;4xt@Q7>l;YSXj|06_RLmxtDNoaxSlwEeshJCBHO3j*lykw(C-PT;F?_g#vyD?hXfhmMp8Y;b@W~C6SrzBPy zBB8;m?>-y4J*;afu69O~qWm5duhu$Yu$2r@5V5t4z5T9ou=ET(;NBDM+1MzNOKEA0 zA#B698NCWMMS5m8QYW8rHvthbADEA7)kcWQx%bUDtVi7Pyd|d%Qh&i#shF5ZfD7}9 z`9w}AT|X_uFzQ0iHrwTW=+xGMbnjG+M_ewWVQuSM%RI7 zlU{91M7F!qp4QdNd37?U!f5OV2{n?$7BA7SW;sB&!#%aog6tjl_V>_}>)#gtX($H8 zqb{%|F9mHX?W@{0H)hhO?{o#Z&BmodWE^n@j|Vn7rfT$sJFgnJ#B}`+0l& zbCn>>#LUh=L+QS8WHWu9QccI>xi8%mZrFKnfX{OwCSne^aCy7D{Bc?qFRL^T=m7F? zxR}|`5o#bE1J2F*P%~s$cvFxgf_J1Tu>Ry3ZbpF7467`crB5d!2r_L@40HETYUi;a z=e$3?u=C+|k?WO+>t#_py_G2~y$Wtp+tJxNA0Psl94`lV_~!i~oPXTj{xe+PAc5kI zk8Nb&bbgot-lJ+TP3?)Aqj~@s_Z-g7@2M{jwpt2a7h= zW;*O{K!*K0)whq^laHm3fPKcj_&}9d#xXC<9EgCy(3W=A&9PqMshOgX$}nrJg8a4n||w9 ziTri6z9dr8+cKlG<7M-pvD;4LB z@=;O~8Dm>O=-kx~Z)p~;*D2OoJH>HvGqtwo31+&AVqvSgZ4Ex4%Hn6yr zPis2ue@MY+nW?r)rMM`zcKn7)Ub0Y83`U*a(ltDGQsS;$!IMlkEEh%ZjMXT#X%L-7 zAGVREqte)HNYdquP3blo&VK5n*|S27LUg;np;V=Fp6pa@)96(Aqf&R$ojTekKM3P4 zHBo5sOk&Rj!40^Apu%m+Kbv5m;+%eM#0>}nTA2c|WA{ozz&V@GCmHxaGJ5wHrN4v* z+`~94Frs!B2lomMgF#GQ58*E2GHmGbcgWLEXe{__ALsO@qLMa*8K3H`od*Rr!3-I~a|;%rcg)HN-y{8kMS! z=p7xPLPTcStr@{%R@u2(nsH1H;wr;0G-aBjyY_5QayPZP`gQq=pj(`k8m@!lW73}C%V2zX2^Uz}L|_x0&nrv>CZdGC+M<#=@K zHN5{^-+tb%mq1|W2bOB*U3Ys*_49(q>i=3_?Q?L&n<|+AtTzd_fKxnohS8qdG$*3i z0je6NXV&AY`HY+P6UNsmsBwIrk>AEr51L$k!ZtyP;|aW_VmEw3h=`YkmxY+q)#K1- z?P_vosD~4$g5oK=JeYSSK$<^=~Ffbz;JbKINi$)lB%B*YzvJ{KFoT`C8l+ zGmq_Lr`Tf~?a_!@+41r-of$Bi_|~rhTyr>sM&9?D${D|g^`Eg^2kSb`=Q0zZ(0rQc zY7W#}A10DcQ7H$_kUIEPuShA1lY^LJ zZ-F_BXN0U^jQlXE^J`lQYhg3m7p*P!sR*$4n&^NCgZ=Tc`wLTV50_^;n=PIp(%tXl$mv4HrYxrUPoq zzsT;}pxY!ovAZD$$uk-8);e5CH59dl?Y$B`2f52->(UXHf1rJVra=BDJI%;<%fuVU z`&gX9j#A=$Bjvt`idlAP{@U2jY`+uYwEVH#zJE1FQOr@Wy0DithPeBYi~3 zA8eq~!-sGl5_MmASbZnK5An5GcqbFnVvW7{48@B>_!sIhhKo#nsl18i%Lv(!zIp?* zigpK9dgAug15E4*86Bce(~G+OtBnw*Pg9Uj05O!hT9B%kK4N}8p1RwPpGY5+`d0Ok zh#WI9iQsk<333$w?fc>Nb%~zp^%~aoQ$+KrsjS-kX307B2X{Pr=lOaGKYv`$Zy|^f zJ$$0gT^sUT`XaC!xSmZ<(kMZ~8k~3DI|(vAcWTd6;;w=r&nWZa(S4Y|I&~B3M`%bD zg~m$?pOcaRy#ZU(NHru>1HF%cx3f1jTcatTIiA4T;ZhKs2x1@h*FAaNZrvr4M81A_m)dq zpla`H=VzJP#e-23y}CobZ1Luf&?0;`c?>&mRI3KiGPKJMF;WTUqXRb_mA@w9Xru$B zAQ@@e)pVstRS?~}q1H{!n2wg~~ml>N?I0PP4D7>sx|! zYc|v>9nL3EG-o7qVlv5)H+rw&SB5hGqfPHgASl0b=K|~9DTCN7tlGA3C zG8i-l)W1X}bfQrkHHhf(mMMOU&*#qgw1Y`S)^>OeC&~zS>=EV2i#o97hyzg{=A#9( z190OI8HuohtOmsHbc!xTp&$>sg|BvWW+D^95HXZgp_(r%oGuJ z>mKU=xhcE1&e7rEjwkONU#{!VAGi1SkY*Xb)j>gDoSw4VQ#f>QZA+y*yAYBf-aBqg z>f>GbOA7zaX*l)!*d%0caxP68#m?Thr_b~?!XJ!$VD@e~_~nJ!t=G5P`+tSYPe`D6 zZ@Q`0&$>5|++|=VhyC!3$H(l?lI9G~tT{io6`zCFh-*)~+||zK5+={HoV_6DOFOf? z4{`TAp>qIVW{eQyW!W8w7;+VP18!f1Z8PPCn>;tTm-dz`sT&aETBra*xG{N0M{<7% zenQ@f%V}c|`Y})Y0y`N>#b%|W$`8M_)+#wBpan8CoP8`$3Ct+R+YnJ%j;-=+#IM zg0O_CXT$;6WeZ(IgLLux8GMZVM&JZz^1r+ z0_>pH9(tORW+AF`9<4#@6a+4)bWKoP7^=uj7tUXPIm#LoFXgBwsKk3n)IDQeZ!Wp5 zpp>1-I!>fcZ=`xZR?caqRBn+|IT2c_*sG0GeLA_KN#8h$Hhq{a=kes~QW>j#R^513 z`&8wt8qg*%!*q5iiD@YmJ*n-PBSfG`N(7ZA$)K(E8dxh)Ez*1!)N+f-O-C$qAd!X9 zaylwt6sDn=$%<{BtaZ1PGBc4-Sn>Y8o{sMO>%kGriVypqsnefxG`@7~{TKYy;*Yal}0hPHF&+sjQG z-{G=Z4;Z7L=~h1N6lkrtfXl5|9SxvPo;A#IBx;j`G|(Dksu!C0xHd;WYRGC3s!9e3 z0K|AD<>O!kS>k#Qf;71-9RW%GDg9H1I|BLq<<0A~A8a}GU^ z*g$4nAFYH`zFz)>r~@eDP|K)JY~wd{0J1)tt-A^*>rT3jD0#0b5+NS^fh4f8B&o^o zyG_pNyPvkUdW57VI;l_q0^H$vc=pMi4eeDLMg*3#TV@OpLQLm8pmY+i3 zj6qO|HA`)m#Kw35D_`E z(P5_(vm0^y3QWX*NyJSi0yluzy*Pd$2O$C0DRKh2U{(pN6Wu9 zZBsISoB7a2cP~T(;H#3h2)~1nX~go^3ZeQ0VSCk ziFpRwyeDo^vY)~X|DQtxDViSAbNbDqpNMti6_~v{9R2c2#Nqa~p8spT{Fx5o(ciD` zKzoSbk8>x#yi4y!Hz@c|qoFGOLYnuW8kYHIlJSB(Qn_Fe zONz+{I3Hu8ikTRq8XciGRQH{u?=)za!Kkll$rJ0y57YYP<5SvPLQ1|7Or^|jr;bmqlA#bIm1dW_s1}Ez zQ2he6H|tb?{*0J#L(AEfr ziHJn#cEvlb$CLm5$AKKJG45vAg*I8n{UVPf-A*8aQ73d|+gD>#x>a~cdWo)JPp8-A zaPZf2`18m0dIsZ_pgaT zDI>b2rf^&)fOG z!}?Al?0mMjjouc5H7=AcRTS0uGhJcZRkS9SKd6O^A~BOA1~|ptIO9MLB&KxRWM+dLIeZ`ZtAd`XH??f z>9EyF1zVbk*DwK9kaC=u$ryeF|!v zSUc~RlV4t$DBRv|=l@O0g`^MAyO)D!ZMz zhOij+<9A)obB5v=>!D^)&0wr{DxO=#O8yCeb%$&Vlz=96j4*oAaERR;ICLmPQId|D617f$29T)1ZFUk*eKzyLLD=#HHCfDt=JP;`=#n8@tltY)A_^U^fzJ` z&Off_|B~A~A+TF&permubJ$9Kp<->yZ|JjZYFkhlB$TQ(Gh4-6eX?!p-l7UF|*uS6_z0p3N0=ti8R$`+j5;-~j z4p%sVNC?+>u@<|7^WWKhCtA|08nHd@e*aFNl>RN=K5h4<&yv_iPu_4Qo(B6=DyE(E zXz``&ZeYFI75+!<{q)0ulL zOu1CKsvkWq5i>`8JU!5r`ImNlJAb`XwyapW<@kL$ekY=EeOu4}2-lw?Yi{bt=T;vc zqc;Gj1D^end5O#IyN1cnL63Xq>0qMy8=d?UiGLhaS)&{rVD#xEfe;9>y&RM83iMT; z!NkhD-%lLA9^^dJebi)kGm;Mm+CkB|uo5 zJF$Cpyb%2qDXTVj=O);ZW5^p3y200;l&ep{++nULv2xF$}6Di&($lsU>NVN+Q}71mbMDne#5$ZV`>_PQrFN+@9H+C_`>YaR>V-= z6UexbkRUAxKDyno{(9hu}=^I3*w$*3dYcvXVGE0UJOIac~!$qai9+fd6 zGNY^4XJmV$k3`7*+8`mvlH) z(;K*XhN{nnO;%-!yQU-YwPF!6yIAw{ykd61$Fh0!Sb$SeQJq2ZhS3 zK{&UeP54jFDH_StifVYejV6wC#a(zQHCGOa+^E;Sor&*rQfDQIm^|~5`IQKQccjyZ z1Eqzt0TsXUPKLkyj2oQ~m#5h^}`^A4SS} za8mP?JHRyFCwOeCPeeurmnF z6ulc`M*>YR%8 zVLOKbD;Pj{sQgKh{Ruz*pO4tLa(b0^(2L|dcY5r%tqWPr1= zP<3=RCzzOz_O+afWT0qel#Q+}ZVy9=L>4bX)4V5k>b(H2`15QIl?Y1TXbLCYfis|>RP-jQFSjgLcooU0 zwN#8tXEbt0;e6W%A<5rF$XU9O<;Ln)`ODc4%C|VAWsa=D38p>KrY2UP3ck#0Rb_*6 zKjrg|ax@WVQFkj%4InKlZi+!wHYXvWc{GsO{#2#dm_37?%V^$Jp=xfd4p7b0nX$QK zFnH_J3bKZ*LmBWn=bcNfN15sWa z<4(;qk8yk%Y%d&-W5#xI1Ha%^)U;Pg+8OIE8%V=oJ+3fw6oKq)7JBW&-L|x{Q3gFu{dI} zUT#s{=BK5jWH5B>84LjyW(#VpvPe9{ljNMZi?}BO1^54oktban)u!1rrQ_8-XR+3&}^s7+aD;)t` z|BOtM>2$sAKow4PTbQUU++0XzMmd*=6-4f=>sTxHepY?cUaqr$jV z8Ot6UC4V%dYr99|3+&8qcB|;Jb{^gnlnQ~81bSb(!#>7CXr@Q^TeOojhzRxq^2`KN z6;eNYOPF#+*cmFB9YblsD~<^5SY_@z-le+AsWvDp`kK-SJFAyX!9hJGe|z#XBjc(Q zzu0{hl=_$|S@19`BeC^4xsFuBgv?3Ay*yYGG3qv4{Y3R$NOcvnS&TaRbF&RVD?^{; zn%eF-$4cYTsU?q_o zU!;J`Wg=M?R$Asrt4p;W>&j60vi523pkGz&t;R6}1)|yD)ntrPy{mj|1=>+ytd^=f zeIWG|eq_`-H{LN*2=Jb~2NBJ)JU>sswyuGBus%pfO<=JdI$%e1!T#4(8<;UPMOwUj zRZ4yU2sF)dxjGSEm$2OYx_Tl?o22%6pzCu9L%^G=sPWQ2JV4gr-bql8cU?MxM+QniQYq}yWjKm=7G*uaehFWz3 zsbxahzC2bG`(FyOs>n2$$tgs8G?$D#oo==>fv7q5TYn`eOzRk=*5*PL$lSqjZD`W{ zH}cLor}pB0Uy>q*6iL+N=uG=u6bJc72+Zu}Z*qAi=I!=ld;eEhUVyFJ5UOQnyBW-% zCY5bjWx!P4S{!zvxE)x=;t9ouq7Z61N)r6$n1SU7?kvdW&))k?CCjuRyc2yVo|$LR7Wq}jDG#0b%B*_Xs$ggO4urgK zd7Bq)v9SyuOegC#^zSefb-zk8zC>zc@ZCDpGE&BEL<4_U*8ns7{# zqG>!ZA7j3kG|Sp)=%@LOJKL;*v;(nj;$28*5}PB7TAUTy$P?H@)+`2(OLY>oq0_Lc zRkN86!5#P7riLNf{2jM1QUUaY-A$d0zjlZ^Y>)xovo6!H+_}opef3+o-Mk2Y`!;zW z)-{~&JReMr1JRRqYXS%6XP&wBS?3Fk;p?AUw^!z^O$`yf5OyG%T=(JgCjZ!?E%)>c#Vz?{iV$pN{| z5jTTWgLf)53YR9M;Jq`g9Hqg0@todSe~Vc}W;Z{}blq-$uCM<)tgi?gix5kuu#p9J zpq-+~9=LWsnN|RTxT{?MOWZev4dET|ps;^@avM@Hf`7;t0`Wxb(i%tHC$&fCDcfUS z?GbiXM5USZNiw9_y4zF$1rRZFyr?TL<@}6sF0QL=z>RPt7UG#DAl!(;=mYj|n0{N@ ziaB=7j`_mmh(f?Egg1ou{PuI6-%m)xg_{p}sShM|5$tgaz!vCDFcUeJ+3_DlTfhRW zzzX>6_sf7h&0UR5hi0tYcF0!L&FL>(iqs}Dt)MX(dB$pkZmhBrby+IPf+%}ZG|2XI z{n*2JIQpU?$w}rqoKR|GDbWNjAB!OnLyhb0D69&o-S^=xHs8GC*AA= zQ)YNvZAfqa!TP@gdOHNi0@ovADbd>N+!I)Mo@JWl?PYs=-TWGa<2kr>%0RU{tLo}V zik753`bLu4vx`Nys%7uFAY!OWbwsd-g0(?NGRu&itEo>zkYSKp2mR6e<_iw08A zLHa=t*eRrN#}Mc<*V^V&u$c9miVKMdg%wS^?yy&^v|ilO;fI<_RibwcIRWE*zO3ru z1r+DwV<_+6H%OI5)x%GMXE*m~%*Mi#y>~gYSt~WMuGJGLCldE(awOubc!)X)>@D1= z^G)iwoZ@S_?B9vbV_@d#^6chs#IoJ~+}{7`msh~6%MO%KnqEEDbHjzb(|X8YX&0#H zffzDJKv)T2ac=%T&HoqkC9Hq??KQ=k55x-GHYPA8HVs2IW~+`GZ_GZ5Yiqra>R}}@0{vN_o?W< zLa!v*J4gwu_uCI~Ebf_U^4Ne4WxH030llqh^pe_kuxi?ElGagg4L%+<>hU;1Hf|vK^E}CwO@z{SFZ~eJW#00Nz7bnP=v! z$c5-am_k^XIfShnDt?pHx8<+Icv_QA!Tdn2pU(IWjyBQxyb1-Ackkw>??;4Ziwmw!8J9=AFYtmp9?!bYfw)>-=jWQ@a5=;N0 zuiK1*R#K?{u>yCzduE@$!)lfR7I8Am{4CSF-Cow0|LxaTgq7H(^Tx*jGp=1Nf&F(# z(xHkC3YU#bAb@3d^EWsDm&g@<4cjaHjac%59XrQ99%DEtT$y0zi3spZv)KgtpK+Jj z1$sB#StJ`0-?rUJXA%StI3mY1f$ndpxrgFNA(@2;VS6F4$c1HMxqL7^xuR8McEL(e{*Xd#>0qhmcd1Ek89HIp&H`RD*i%Ql4Q9SX1{Q9W_$t?eO@mepwk9SJ`!V^WD~?MHP#nXg7+a zN`XRz0-bTdR9|XRlT!=Ir#bJ4g0n0PLYBYeVnsOnt0|g9;jyRZR%V!J_t@s9yQ-VKkqIPCbdys^RjzDBTAqr7 zTT4MJ2lw=6Ydc6#B2a!NawcaCNwjQ;)G>S7G!IT$${)JSVcz|mOZTMFQUZ6yOh{Qp zS)1JCq;r%sy4p!=(Ky(Iu3tATSUdlTd%t$n=HM!@el5(fKXj}+BXM%GMc-|Qg+K(r zZ^3(+96vodA86aWsFQABtMqBF>-1TBGA+u+y3xQpDCBkOVJK!LMh*Yt)k;l*-HyUMZ(HV zs|cZ12v%+CyOa-%vK2|iA|e}ohRn~?{GFKB_aEE)pMHG<09iJT0W_p`JVcCv5P%KL%$I5Yuj%@K%k&iddsttQ*HDU5ROcr09kntGtHW4Aq)-cR zR{()nm?y-Wg2SedwxQGtP5hMVHv>&;cvelW9|1NLwg+TI9fumo`ztIOMen+p*2XyN z6lMqF7?#xl%ThtDq%^k@V2nk}7!eT(Q6PliZ+`nzrtjjOiKjp>z>TmGQ{H;gMBTO8 zQ+_AUO|@dy3RPubSNGsGIgS0^?um>sc8m#~BlVTn;TCD5j` zl{Cw zV*vNwqFS>BRO@1U?cdIgSK4FT!&Sg)2)&C`Fy3?hYjw6i_w+;aRWh^#84F0)1G)RR zc`F^>zM`h^As(AOTpE-{9si5FPxVi>VkysOP#;mHplN_ZTvRR;vbD~DB~=}J@U^3^ z+#)u>YGG0JFlvY$(>W%*jz+1aLV--gT=iW>WCzX^^^MZ>HO*k*SC371+H~~VAbn6L z9@L^t{%)P719dhWbpTG7$7fRRq`S(uOH$wWj`ZOqHAz3e0MtXNw^F({#Qb8Pjz7Z$ zt8{yJA+q%-npfOr5T(l~@2B40qh^0L4GHctnP}U>?dC6&e0!eO6}Q{=3!8J0jz_`W z^l$}olUy%uUBlbUwl02js`y`%;*gVk(&li7VmCuufCA<@`GaA zo3U&Br&8lE>da51U#$c?)DA>}!?ySu0@``diMhS35`8J@V!Y8+)8aT6G5Lh&Fh_M&wv71&{WZqn26=)F1SDV{2g;1L#t8^Rl~ z<~b!lI%52sbVR_kqix0F+-=K)F?Q!wn!ADM%t<#TF%bcQ!VR<$3$r5CfSd(tv&u1?<%_l}*Dfj|@(J|7zvKpsf* zs=ZJ3X^zv%tu3a2aA?jS$wOxqSbWc&K7c_bsD<-#Q`-sNz;^ zzzEibYLlf39EgNiDpro`ed?@|dTo8|ZphEdNac@kRVB=(ZtIc6m-@aj)AzYP#=?1G zr%SO}?zaX6RAi7dl;$G8Qy(%LIaa_)BpM02!c29;n>8Qme3T^9_(@qg0P3Xa*50&M zNp*BWZUW>O4K6P3@d0;=;_LG4O>>M$t6G4fJpAjK<$NMA|T@}`;Z ztv~ml0BnEKTG0phD56y3mXX1NJ@5j!D`4fgUvmhJnTWinbq$wU=2<4kPV&}3^rW4l zv;XXj?x7YgRrG%yUhMv;IZgNOt3VfKc8*0T1l(@kd&IMCm~u3qBF|_=GoRRz!l0#w zQ_(2ts5T)Y&KoG&prL5%l5!<~%pQh9*^jV%r?4IF3!8s*xF(hPWyO``V}fMu?RDBw zY_KE_OcPYxx^On+k=Rg=pNao5Dg04N%2YRJ=<4tIhO4F}km@qx=W^nrlSQ7R8Eodh zwZ^8YHP1zPkc@B|K|$%XmV`CEN=&DwRuHs(tp~oeOjXRe(pSOuvWO{P*H<|uRre|Uow%6ca z;TKaScLqg#dBiS)N3m z5Z)1P2ur%Sy4sp*ZK;tvD%_6cmZrg3c&Qkga$`0I?bw5AQSQDn)ppf`h>09C=O`Ot zmV8gC4`Br@6jp?lD1d@;ybjzGo46^vN9r-}A4$J?#7#-c&|)KsE4UN$Of(TYCLx-L z0`mrVqWEt>4BJae3FUY9kh|Z5Ri4u=T8A!W9~yhbr5yJcG4%^K5nzb9U?LXoyG}!* z*sK}_gF)erU^@YcOZEBYVlE6J9r|mrCg_lu(xHjcg1T%sFB( z#xcgj?Im~PiHK8-T+<+iNHICa4Jjn0g_~SElgBNz^qZxHk)@jd>B%SsiriCH7D22` z6@spwUzE1yLURGhs&J7ilYtm#yqv?IGcjq2O_UIdTB#4sacx4>ijO`DmosIVd8T+$ zb74G*O)7lmxSEv-q+EsLdRf%U;y|b|6?S+6GnMnSzch9JTFoc>YOmy3*lp#<;hj&M zG1NaDu5x_%Sx_)pm3*7OlT-v*J|nU80Fm0mS#%$ZHL7hH9KNGVYJS*s#Bw}%TZIi2 zwIxo$FN=na72avw(FVeXsECZMj(^?BGCM8a62nA9Az)n*0^B6?Eb}DZ@3?=zHWl)Z z{lN!PE-cLA#EFDJ>l)T2gaGG$T5sGV2WhgE#zKi_M#sL!oR>g?X-LWLeeQ(SfD&~V zMdoQ;{dPZExJkXrS_3K7BaNW!qkrX#B>$J zZ0;HX{Cw&WoHF2p)H1#|v%NFYsclNB9rpq~4j_P-S!NJ|U*T6KVxD+%X;mhD2-4%2 z@-!IL_E)Ig-!;6;>iH{0+P~!C ztFB_5P}C239A|uBkZ`oYV4?tcU;!53Mj`IhVIrYBq13-ung-H0>8JLb^A=srJySgf z0-nTVc}*#?L_5D+h=qijq^l(b&-fV$qZZ~8+&F;LmN9<;oj?iYxQ2%A>-ZOJMr^rDhdGIPX4Rdk#Vk((=HNl|EBXITc zcr>S})rrgs(O|~K>BExnMdXS3!aT7AfhS^Sr^v8>SOYA8%le4M!nwoOJlh;*J{1>k zlF}&dG9^s$>zEqhQXvQtAf;GT3*jt20j?kiT}pSoICW}S5bg-hdfF%X$Z9_X!> z9xJ*kx3)lqR`I58tQE8TQH8}|M6RC~hiV84jCRF|e26i32!YZ^L0`0uh`}TxRjW*& zuG9H;dXn^Bd5(sC#QAc6%1#N#s)d1Wbyb5bU)*YUGyW*6Pq;b7g9FOf$T+`B!A6>j zOhNg_wtI2oR13v5ICNi7?EN9e254_kW7H}Xk%$7(z;8xp#V+y((&Pezg;|7&S@K;z z@n7MACg%m{)*UuPqUp);A4Nx!N9Aa^7O11WS_T`cT5D5_52qeFj4=cX0r}tfU7BSW zY-$khfc|&_Kt8|)a*pR&0Kcwab<)i~DFfk*$LzjL9`-qhQE;m=l#L-V)d|ae=Iwnt zTH#~+i<8SN!n|$4uYm%zuQApw4O=PsF)_`T+S(VQy#rwbGl`Hm5ht!5eZY*euvfrT zY~hm&H&u_2yJj86D8CFT>WTq)AN&@)4-9<*QX#{@(6*$Kf@au_krx=wLy=3=$b$G? zPY2jo;3a9%RE`1>lbIqJ2uK1)s!`|oU3Y5_8e?X_EOy3M#f??eKue=UKE}g5I76ni zc9HimfRq>tP&JBm(&&VedRaX!nCzE1P|+Q9U~Z~7GNR*0^o@qyX$oM&g_^#B6(b_JtKcO(7g&h3wHIoYJ&ORkn1mQaj!b}qBL>IFMuLjC;RYr*xw{7~v;|ek z#=S2;T3ju_q-H%ynug+Tq&jJ8G$L+5q?vuN>cY(*bvBJD&vtg6JBH-#9=g{j80DJw z3QbLsJDH?^ph)cmQom)_fjvjQ=oZNg@+5L)zLGd0CKeDg35%MmG~0`6+;-V3G+ekT zV#!|-+u2bQc%r83TgkOo4OUBJ2|F+B1*foBF{=7W7zH&~VU`r)p;riCATHp> z^&8~y?l>w{-}TGl6V9h**!W3RK9?*)edN_~dVtCTkuAQ#sG6^oWr|1Mib!4&NDDWJ zT3$50?60dtfhewulA2DHxAj!AvIJoNQN}R3hH4WKx2B*tXCw7$MOhZYZEhrMBzL{Q z90KQ!3y3Q?h$}d868CGhX4`@YyGfp&rfow!hwjT$Gf3QLaV*2VH)Ge4l>-?coXF)O zM6|qzWjVdW|71tH2P1X~A(DwjWSUq+WD*e(K_1dr&(Kh+QgVUqbUAz8Y_lkXJFjFW}$PKbVe+QEK@oLxae zJ$zEyEEIfL-`QQ5#m(Qu%@HmV_`3@?hNjX_GYfSvtB!umGoF7iAS|~)+{5mOCzgqY z0S~`~?IrkkgpJdN@uBXX$Z4|<+66>`coKId5`+a|p%4fqq;IFviL9gQA5mQFJU^5I z(H4(GWx0^d6t;LU^G5-mOxf+gg0L`cOe^Dwcur?Da1^)n(y8NPHQr?=sE$u>5uzJd za}TILsagCh|LlTo3a7_15cRJPD&{FeboitHRu1Y;3pX2KBZ}c{l0zZ?Ez{w`jW!*3 z(m~N9D7>>V@J{72$4)P!35|!CY8F7fn`fr|B_1;c z9Rn=9Sj74JZ3|O*OI+%~6rdq@YI@u{Q+7VXO}$bjRZB}AM3m1G&2uA0S=@obHGpYP zf#|124Q2ITv+k4Yfr=zFSRw}VBs^2PjI4as5VML@=61QNT^`~NwPq&AyHuhS8D`;z zbxwv@79KMFwVCX~O${c|YZYuiaGAiF(ZWqrtP{Huug>ATprtSB?!#(2Ok8KM&BBc? zQH5bBC-sXf85)glvZB+e*XPolS9GF(l%o;+=ELU08oUoa06_(25K*0L@5UHw!PnDw zN?jEoV}+83w9+f{Q!bgmGNb&XhbO4~DqcN6mcu=UV{6scW3P=Xy!47;`>m>HnAC#d zBFB*oR0F1|qa`D)vP%Vwptimy7O ze4x?8)RaZhBKxYLE1h(+!AqDgZoY_f4B`~`v^VMxPiz^HQ8B`li9(b3H^p9<)e0hN z?lZ}djjkIs9ZkkOF$+-uiLi6BZDCow--5MPsqEm3t3C9mw-!B{{X_~N?;%Xn%umnm za-A+0$C4tk;)-=*q~(nZgc3n$Mb+Ypf4KIP`UggY0HssrYk~5Ph=@d3WSX64q8qk# z3q+8r9(Al4eW^B*%5&hJ^wBv|S`MsCuEA1`WPX_s!eFB`1*z>C$nk_oiYQ88eg}?5v#*Ovr-r_X_`u@F3JO?ysn+Q8DY?w%AHmD zw~ByJ^k{ye$Sa-!6Qy{H8q1w(*u_Yrku5bXr@8=F@`~yKA#86F0#6rq!g8TBj#ROu zXocaT-L%@1q|Z>}QoXczQ`iu04YQdOY!p>dTv*utvCb(cqhqq|IDv4w9=m;mjdHW*E&4m$z> z&&*6S(}mJ8nxNv1X7)8`-ARl~Vv6kV?5l>xF%!Xu?S=S_Xkwl?#{O^$vurQcsy_-F zP}d^0z0!vKH6V~VB{F?Hn<&+d#*B0{DX=AtKY{4>qY!e>0T7S7jh}2ojXX@Tk$SO) z={{kZIh(q0n>{$E;C~?f)+vQZa1UW8oU6~|?zwQ$ zvvcpc_1X!Fm=Q^3rq;4x!?zFc@{Y=KSD1*Mjs6)4Z_5L2z)#%a0Ih*T%Nh>qeEe;y1KkN0S)y;7*k!Hf5B-? zsldvK0NGDqsv{CtpGJ+zYDI=BcCr(EQ>>INs5w(oh^IUw)@zh@0mg{Oo*AnOXp$7J zS~78G%GFnD#-(C)tR}=sbzanB&gZqpemp2Hfw*Z=^*r}=7Lx=FyQ$WRx} zh|Hp-7F+s_tU)!m{%M_!(ZUT^NDX(*ZzdvX&IYMej%wr-7j8tviA)aSn3?8TMA-Wf z51q;TCqAszmgXHGG244=Yq(t8Jd1N8{F~e~aVPDa8rc?8PeSB`L@0Qyx3H~&@7&7z zH3^^qF!4OQ>s787xjs2@P8_G+5Lc?>wdAexsoB`h!cCt8HE0&;@MK zea1u>KkOmVGY}B@bCAE|2sM;#vCFn z%)~N_dkW!3w0^~DDQCn|G(I4B#CZ$5yl@kBHQk+3fGYKcQbn_HW6v<|Ca6R5x&^PG zY9BJwR_sP749hBe95+3c@c9^J6Y>SqItrg3xBb6qDhoGXW~a|4WK+du8hWPALCIm0 zfDqKKUJ%O1(xxueiKEUN{OH|jWqEA@3u6QXSqRzL*LoZFneEPb?N0^m1X8E^9S7YX z^^P@t2z9E%p)3Rtgc(f8chCeaPUZcj`Hc1l)%3d|DX8kI4BIMk(|2BqNL6DuX1S2N z3Vmmu5#B?1Ct_lkXD%9LC5fxQ$>r;L$`Vbv7Ea7&F;PO5*UL}PU2vj^K&-7LE0`wGsRH4) z7!j9%BMn`zDAgzzy$vS5lb*YL+KDg=i#VB_b0Sv|6GDLBw#Bc@wk`-;2m#DOQZ6GP zjT;qm{RXQjia${I#;EJ6@{hs=|e$ZQ#Q&uA*4bTNww|vU5*Lf5|^ncWw)!`6{Mt zbQc>eF%ZlvBcHAEX{Ys zL@$}XRZ33U;u#`U_ch%yQ3=o;m zqS6 z`>FP(v>`1`sPqPkFg>Bj;4_)XC$rT4UWbR&nrhIaVNWrQu_GlU5}{Z2Eqir1Xh<(& z(Huv|Sw&)Sj_xVB#)eVJYZXs(ln0_*rtDc}O;`-E37n#cOdlsgCmq%9jEYQgT6CkD z_6kR}O^6@_!TU87HnK#aPfk(8zUe`=El~#}9cz>60uh&ZjsA=qs|mH~k=bRY_<0aK zql$ld#Ldr6eIKx9zoxD=Oeg8(sMI(GQ=om-z%cW=&LDwpI7ZB78Y@Y*Pf_i; z$B1?dACxm&2~b*Mmb0=+*XO9l^IuirY!XsD2=YxS>AV}Z&(v{kJn0s_;lW&bmHj3rLOH>QK_vP% zv3k}{9QRnt!c9_@Dp5ALtaz!o*#%rPXlvD#BA3is+{0Bp;JTET%cWJDv`<7h2pj~A zFtISRuuStTlbfgc3cqdJ7O=r@@PUXRvbVvc$?MI^*`#W!o_Vh3aokPsfYFDkJV{Vi zU(3;Bs8i%?h|M$4ZgL`13bA3(w)hZ2*uu7jEe1{nq!W2T@y6fIeo$4u_y>l-l9igk zcB8ZvRMW2)X1Z~v+9OnyV0}(mOhPEg6oo|bdI(KqS}Zt6~dy?5Iho}Q-5 zMJ^X7%**PR1vr1()M-T=R0V~A0CGgm@ifW0hVbY`S05k%o!Wm0%gEy!RAiw^SeW3+ zdw5@`;6E!x(;C3cPfyeH^YrvQ&$9qnx3Df@-C}nLk&w8GtS=@jZbyv3vKFHC*6VM8 zh{RkD1tzNd1E@}ciff?#aZp^I0YJhsImf^jH8O5gGdn$}Dyc6?J8*PEq@V^vLNdd8#RqkApWCc- z6j1RZ4NsQrT4HbeGy`+;S@wt1QjFZXDvm50Tl2 zViD#|OtJg2mQEF+ZRXNFxM^?wmq)6i@q<9{2qAkGCoYsX4$cb>!%+nNX!nOX{#?VxPI+as6uu534LFk*cy>|I2O? zXY0_d2ODKA19@y8&e461byopLS@^YjS2lX35n_^vBG!UbR0WU-xk)b9`FeFC6av=8 z-)v+@fX4^>ZaHQ2a@yeuHpa27! zTeYG<)WW&S$0!!t7Goj`0q<|y`y0K#`)}W-e|(?6eVgRQ-f!Ec1Ee}I`yQ0Vc5i`# z2}C|XJjA6VVBq8S)m_jnYE_Ec44q9xDZVCMOrd56wh3%yg#`*j!`6t_*l^^CofBd9 zK6oF>mwe4)<608gUI#{5>)n407eg_Jv1)@Su zTUl|oe|dC@uv6p)TdT=x8#pUKWrD4&o&5}Fh%~}5GSIk8jWk}?A9X&ZdTAmI<^7;L zpx{D=*93?E~9hKLM>0FgjJi$A4hQUdWbqWRv*AxkP52Go1lS~WWG`%_r+F#R>`EpRnS1e zv6vOmA~P)trgl24q*Rur8e%kbfZ&-gB3F?qgvI;!fQ=C1Avb5I>P!B}3J)o$&!-nvwW!CS4l&*>e6q?ILccE2g&Z6!($7l& z@ewzD;-4ndp9lE^X~&yxM}h;V`w>(?)9H65QH=*W>^f)1MSnCMuL#~xoqYPCbX>US zu9o-+O(Tr`D%0JG&47+0j&cDuO?sD$rtn#QlUJSMZw7Q#`@To+Nqx?Tp%5`MKmvFI zf`^-!*||yN`!rp{wruO-*EJxdn4*I#1Mi|W=7mN=8xJH&F>1wLN#7U_5|vB z&xm9dl`-(zn|f%MU5cO5G>LJed*sNTJ(HWV5Fr8rF5&R==*^hX9}U zgvFpLVUXy(V zG%+)+Yw%l8%c)=Q5R{b%0v}M+xGUltjiVx*h!Yyq1X-vtYdMb)hn*kLlCJ|o~Fuv zP~*-_3b~>5{K+nABmx>{_Ee_VR}5-@C{+`^uj0e3;be%%zYd1%BP?+|=#`9NhNoQi zes9YI=|dv3aPyD|Ga)bJ?xBUQ{SmWp$eq#~^*C-f>e}L7OAd;(HWvW1$n=dsVf|;= zeh~SQn4$jL>%$F^ZGSl}y$@U8lO`sK8t@1==8L!|gq6Yy1S!Wte@(gz=gWthztbpv z#fBPd#SBwouz>5ceO7YH9)6GcJiH3+EA7+L)Q!nQ3N0W2buo93%?mubEdp0?$E zd3#;gB?OskZr!e><+RXCjU5I4HN1Y8??4AtDX|w_5;~Z43YP z$L0CS5wNWx2j+L<-6Nm`=5CQ8scmqH@(OJ z$;w}%$DA;Twk`NA#bq^9`hj|MbT_)G+3Rz}@$$7p&7 zY-W9z_c&SBdNH+wa%(^|W@~_YMaf*V&d{pXNUT~kJ0ja=Rv3#t;R?DTY}BzUc3mvQ zj?FwXUqX2I+Y9_F6Hz+zrD42?x`SV%lX8WozLH+$R4YG$Q&zKCr@>eU_HMg}4R2|C zd*7-|0Kz*9iMzsi?_VgajnWT3=k1xzMp3)IKQ4BpkAwAPZp$lJt#<9vxyQiCG`2?{ z2XYd|XQ(#f*a36|{}^{{kyGAYvv5ucf>-gR}T2@7h@ zG3ECtE*0z7@x5Zo&nlc6z@t|U$>X0r6&W4!u9CxvM-C0ffURM(v^g-dKzk^x ztwwrOOU=SnvRMwKMV@zwG+26GdQY-}r3cQKUz|~f+TYT;0jWL{*(DI|%kn6%?iGTv ztl1yqN<-}u15s;!(`U%MBvGYchN50ym~@SS@EU-i?EMu8t=_0HG=>i-4hHKvO%E-y z8me)0%s~jj(?&}O!G+lgyZLez=hk&wZrgSXegiXeJP>YZSav6AUHnR=J6|wbHyY~r z>(p?mp%I^rj|(Dogas%$G^txQ{g{e^q`zHYJS90ck#xooDJ2Z*ZPZz<`n@46T9G?$ zj)f;?nkF}2+&nuLfydizSr@~irR^!kvT|?0 zI9@&tqXeDCHDdv%k)sv2Y5f+)=!5Sbe5M!=^7^{XlT3~$Cr?+mtbX%|cd&iPq3O(H z^BPR)h+<4mrYYV3WmQsrMtz6|W!+8^nvh`ds>fVU4Oq)1hjWN%Qh z_RZH0^j0=`XI8e`N-G-eL-7T2uh1>p5^2sXN;B3`fJ&meic&iVV+TyK3sf$KDMaV7 zgJ6SS)g|!;aB7WatwVJw<i@ImAXAccL!toW^VVJ;Mj(_IHd+Hs#}^Y30KYAMTfHpPG*6f7bh*0e;$-H9*V|jZ`k}*^qlCC! zaXZ0KYB|#}8sf`*VFxe%D*f4mKy7?3w2Rm}+Zos)AmW}rq7L2F6cDS1rH2@gY<#ih%+UtTt0 zetw#st}X!E=8t;liFEhFTb%tdlL$+`c}`P0@Rq+kHIDb^k}<={HD?O&8$yf*I}+$) z+yC1<5CQWf-@nb1lV$PCEv?#!9Ce5>C0~*B;?$6U6!XgDCYfh9&(1I5<Y<)D!9e@Z|KR|C0M`!fpq!MeKns9B5YL|cB2 zcmNwkIGG;x^wgv@{l){WZ|y*PC(#Zry=Fz|Fe;#fRU2J+B+enm-MscWQInDE!jGcBXUvWUegSQYok%)XF z@gcl{LOki_G?Ax>8CBf-`ODH@h4&YxuNf`BCY@fmIoo57%Z=WqJW%#`PDfy!nXnb} z!L=tGoe+R*oKH?x`d56|`J4I7P#~2^`_-i>tbf*GC&+Fq7MAyTv#_GAYF1&io1)Hb zLr-~6%xLnLlCEOBixYIJCo_V^8=Z(~6k6Sefn+CTUui4j9cwvQC6_H*R*>ec6?*(2 zcV`0EFkOfws>=kG1~nr>?po2eN&?VA*xjI~<1ZRQ;+xj#&TrKDt7t@(5N3hzG^Uzh z44@W*bi;_@HNe)>Yld#9H^%P}W%AYHCg#D^7VsEev59u!;nan7~gz~8W--E2n^F!>Mt^; zVym?kP1V|aIT1m!inVFME9>2DzDcYV___HyPV)}d3ZaA@Xw%dU1&YojAH_7x-%>i> zdj!?l>o}uheH6u%uU=MkDCuEj!qikici!kX6>+d4vgrLz6K3ryW~miK6UW?uD&8G( zENd&-c5_qRt)EMw7`VMtjk}(zTT?k&xj!h*<4B#E%wf45FHxP+kgTLoL{<7lk+U(A z8cZRAWfJ#|h_>~={q`@Sz$_ZCTAk5tgsf0GYiI(-JF0Zc*?+4-=24{u3iVGZr>kka z%2DqFl#En2_OBvWao;KU5Z(dLJXp9H*fETVjBf^GU8f}WRj<9;t>6Y1_r>m|wp;dO zoCI>OV%x^!cVC=_fZJ)xsL&mX4qorJ^j`Y+jkx(~&6dp8zi*1;V&AKK^SAG=@`=oS z$fGIuh=4I2caVKFl>rYwxrNVbruIj^kfJj5?i@RsU7~y67&CEITn`FPwdBz(We;kn z2n{=fjVh$3e#0r?{%D~Zqs%V}A$b4pSDEI^JYT2zI?Xe)Fp=NZ5d0pgdCZf~!**VkovUqK`y zl4krnc4a^HLv5=q{W{1H(4ek)RbLNj`=m+RseS~nsRAM*VJ0TO;r-n&&;IG+X5o2a z@73PZ4#vrJpiL;pAkbjBinRIrs!<)?f1gVS+~pezQIfRh+nj}VKY@gKnq;1Z9s4CL zO8_BmKr6sg{Bbk$3_Gcxck$u(MYP0<&~3Kz8!0zOiu>cay6K)lgX(oGsD7~g@H=x3fvYR zAhZ8kv4Xm{6!%E8Tv)EmE^KdM{a5fW%&wenb8pP<@^|cjCUS3#MVqrP*unGThER)# znGmqO^2Fl4^Yo3w3%oxBi|*f#p+8^u!?=1lAHe>1`m!`!xcQ`;c{hCuKGfoO2XY7* zIj5cX?GV!qYeytEFQ6g__Uc5F&EuE*se&`^cr=cAE`9{q@?{Q4I3Tr?Ot%@`Itj(V zMmo?w>!DMPehMs99;+%)G{P3pSU96k-;YprrjCJRYHI=-v2>GN^TmUym0f0EwNt(S zI0n_2Lrn4&$)w^W3OxOFwvt+B?)HFcA|lBL(lkU=c_s2|71oYg|J-D1X3J+%ZYolq zuMy=~AI*^A+@4g^$)EHI%e{6pDZ!)+B{Y>(9TPNW{Vgqo!aWmF@Y@Q65Zp9fuFr0o z*W2su{nc-t0ddZL##3AvtE23Xx)3=Z9c)QOt$<2QJwzs{XPz}?!jq;a-@V4)pwOf0 zkya@~b>p;}PG6P0oMg&6f##bE78pa60*IeB`zT;?f+@cQz8ds}Yro8JO) zj?pMLAK9qFR$|&l>Za74$sQU}Xlz+F2Y!!sM(Wg<$<)urpRYQ1RAuZ&oL0(qhLa&p z)UNU6Um;*!u&$n&FSFd>w+%$x1K&^g-L)dFfILBCa>63lFh^$(h2&=$RI(m)J()+vfP z2STX*zjIM~8rsa$Yq|GY;na(J#&EaTz`~s&;`IKGvr>2@jbZ-#M%?_=gpt$uACP_x zUtfqt4YOZ{1zp{bf*MnI2<@oK&*=6o4k8snb0FQju*eQ_t!?WD+^4iZj$*jU1rBbxA*nWKi`(y8a!Nzv{5YF>mUCKCi`$6|1}tx zm}pu2`#V2BO_$lNYe0^k|0yYZdS#g-oY~p(VMIUIjjz==P9r@zygH0_w4V0A(Zn z9!as@?j$v0GIQU3W5=cNoqI}-iZvdDdZom^gS^Y^s2>ngN)H^%3MGB(j~}`2$e^T9 z=F~f-D-H2}m-b=;BIYJwhpJC73^}P>pc84swWg&sMXpi3#FY!bQF`Yv5sN$QLiO6s3?d`?QmwBF_zWpI?THao_bpgmZmn>JI z1*w_!W$mnFk|IKoc+1?ECSlXn%C2YDhM_Lcpfm=@QdOJE!Nn=KDLKAOtwa)9jv}FZ1ly)xW*m-rv`C@q~B^akJ*6OlsTFYga2T zjSACoLVs3w(;f0fY;{m-Pq{Ars+fp;j#Uq>BCZNpbNhZ)$Q&}mJRB>Pd2h=z5pNrA zOL)3saw2Rm=Xr3{)0--W6`>4#oUjvvf<3}FchF=fcR1=tl80RVMoEURs5DzKLb-*S z;J{21bG^@J7wKO7%b2izu9$h6n2Eg)@X)Ho!+Ha}=nI&U$`Z*V7V2PXE5^U+TpOm@ zx##DLo85BTUVbdMw|Mh+wgs9x&KO|(ivmj>F}bf4GdC7o0a#krG76~X)ptnuM{_9& zXxdxECwh>wCDEAdkNoO(jTmI^owfPftJ9Izv*ci<#Up>@E!d$~2Pc zn5^hx=_Lu_d7duU*@^hozx}xV`0=)FfmzJg_ztoe^naBMK7eODi~bE#{=3 z`v|GOqjl#;jdn^8&TauBFi$f5L)>!+H^2OM@P7g;^Gqx!;NdZ){W%HEUur1YA+KF0 z(TRxy!iF$$m`P@t{(z_8-_$4Pzti98vuSVPX2%Ol*CIDo)I(PY{S%VDm9V9rDtCz_9Tznq?-Ud{Y$VXR@NU;wN>x65<4g z3q1A7u4d+pp`QU_)+BM$Miw^}d8KeRARbnlp+TzhjCEVhy*nY4-{rGMWw<52g0w(@ zwkeB3BFEj-jWcXGEMNRsv&2Ni21{yz@_D2%rk_g_@Q^TOAqK+o_BvlK)8*;<^vuLJ ze(}o+g2mvB8&F=Cv&(pNrwwE98w`BZhKlrZaZ$)F2&(u6k%_O_73w#Jj(Ss|G{EoO(!z zHW7%SS#o@X^^uQljphJjI^_c~1}uJ2X7!P?g~vo358luPFeYPY^;*i&z8lm+_g0&+ z^pqp~3Ne8=-YgZ*B_@zsRP7<(rJ;8kVoH~cGYbn71L-`S)LZOjWS{}dI@NyOC<#=7 zn6K{0GQeAinS=bs zb9@t5r>;|>94locgRVD010ztN0A?rd>ZWhZ&fos`w)}4jTZy%)@{A$TTxDciq2)#I zwopB+ryoS+hSnN|h0@1r31gV*P0?>k`f#hQpnFBeYE@c%Owx-m)B2up5=0x zC*f@kZ!gQ+%i;srUJds}=@V0kpVWMxw!Ob1-J_7(B45bT0jSZdUm?ujs=xAlL*_QK+rTtCPqmJpLBDkSW_m>fLLYe>C7>}<_v@#HtLt+J!mz*JHqCu z@Q8>8i+a`{tQ&ZSe)W~R4LiyoTUuV?qBs-v_a#Q2G<7}w8dZ7A`Rlf(zOY%A#_NK5 zyjus9K1Oi;7(|7za{fl4$07AUVrv|}rY#q*`z-a1{No{3I@c@tEoU5<_O8tyW6hH)7 zv?RvVOq07_=F2?YmgUEvuWxTFF~wLL&YfVGU|Y4HWM->iJDQT7q1#xMk|gVC%?I+3 zM9joM{iK;}vTCCqQN_6Tma}HxP0dMMC0qBcQuK%x0uX=@knSa_X49Y})xegI{gm}F zSsLOTi9!rcXO89bICgMmNW~-%3XU*|a307ExXSPyeI0R;%}%Wtf5ej{LYGg)eirN3nqT#x+>HiEK4Ya;4S^`Hp{c`A zkgj=B!DUP&DGmXejgGILS(CB>0MX9bN*NBbJr=G;eB{5yaOnnWY1$8J8W5K3CEALL z4pmsu)!GD(Btlh14+_^8MH5P6p%7!_ zq0JBms5M7ghFLJI76U+xJnD`vFslUFcF3VOhGl~>h0B82%`*QX?rB^9$KU>6gm)IP z6Q#m}wl#ZvDY|T|4W&bxG|S50(2Z8It7I)YWm!VhBn^FV(EfBMN za?}4s2wS+7{@0m0t3oQucc6uxz0d_aKW9gYbG;EY_w7%udD1TIroHM+G&XE!5k5rT z0Tp|NPQgETyVD)crMnR~=rrML)5qceY|6eOJ=!e8F8v>n`awg#`V7B&)zO((L(ADq z)6deN)5s#1d_AAKLN?>wiBgZH$LjsGF7R1<(bX5G5WEL3 zHxYKzbp7_7m=J;w+wNlM04)8UUH(gGQl1~2|L>VT+Hz+?6F)qg`)C?A z!jUa)+w_p8xSRw}#BTb=(?3||;9vdvKYslmBK)){k|9+e6W_1tsCG>yEP){GgsLs-*N+s6g=`x%|H>A=n&l>Cd*Z*1A7!+FDw?0&*rV{Z|%qoDOsxqp>l9Z{1aY-E|pj3rx)!dSbzJ^q@a-;~;PbzjS-CIO<(@XvaM`aS~HB{9)^LYhq2~*8VOu`KI-rwF|FHhet*Cz_ke%)vd2!X(y zH#X?omRWrqS7#;lGL5fliy(>4M4KeIN^v*?X2gRAxaKQMkRW#fLF%%%fzCFM`Qwrj zEr{xoK&i*YcpY&vPwsM={O14s@%Hw*0OUl(uI{iRRVjkKqYWscXu3yls~ROU7PB2j z@*z;9>oD6+I8;E}gNu%S_g0juDrqti1N@lDiMw8>r^|F(wm*N|-rrULB1+xWcsQU02EHC@_0d9H%fiUN z?-XYEBg%mq@qlEOY*0|DWQGdAsB{#PknVs)Q2_0&a8psylLU%R&04W=Q6GE7l`b}D z%6u$Xj%%|aXBY|Jj3o(|DuN!B%FHNII9OtBY_uxI{{0}BP5ohYjt{{3V!F`TyMbg1 zVdV!35sFa*swW2K`P9NqEz=y4W~8Jc?LAj$Enh{qbrTNySUI+bm3AUiL>MsD5Caee z2H+-{|8UbEVf(Qz|6ik^?_nF{e8yG-{}ie?~?omX+%+b zlyIAVCL6T$V>*9%qo;xT_s}#ZLhu{5WpU2QG(CO$etX0E{tDm& zI{+R}DC-di?b{u`Nq4J!iXx)Mm0;oP(>%}9w)ywh<^6rx)=fl2ei|7cNxRB?(2)Hs zY*_3UsKS3{8m^lBPU+D${gfuCsq*iT^7+<0&+h3uIVa1qy}mAQudDZ7&RXfj&&U*f zF{_|>Iagk@MAu{U#_BPZWooAKkETMDr(Jd52kSS6utD~{^bzsw$e53cJOohBGCykZ zD$FF_;OhsNRA2}8KJmu+den2dvt)DP?N8{oT4#_Zp-fdLM-$+q>2%479VDc4?1^Ri z&eJ!MEBu>Z|J$$sO<^@8>?M>n%QI=GPYmAbOpIUck#@93@PTvyA%yVG2<(LU>Zbo< zAow?gB`?C?Rl(oJ*8iP;n>79WS=TA+J$kK9_^7Ho=y1w%o$Ldw{@ok0jJMd`wX|3G z;rB#Dd&ftNiR_qe-gGDrNl-7lpo61rJmbXxAVPr9kDp4w}sJ`0uO zF#5(Qgt4N+u*E6vX1%?J%k#I(^$EfI^%lHGy7r9p7ZDw&#YOi0={8zM1Dh1bedI<^ z@Vnp(*08K*8Ew@zW2lxp#*%5AujgUT$@O|6rk6k8USDqB2N4lT<|difEQF!GN86ya zMW6_NpVT$4 zpawdOG3IO`cdTJc_I+nk53(Ai`tKZ&tX;Kh9MpqV8KQi|h|L|L8uX4aL0NE!XGYoR z22Fq-m~8bHRWWiSD{soBTaZxOtMODCBKH8+Q(6xBX3Y`o&&(|oyGbsW>3W%NtN+)Z z@9*y$LP(dyDMhnMH;GZtdOp@(RU=yzGh><6Cbw`9yLY^ti#&jkIxcJLZgG}llgg$i@pt#_U)1qZi@(Ugzh;tk-azoQwRD3xsz5oj_DeJaC*r5K6!R^F7!O_DmSI?AxO0gY1VVx|fx5B}La>LW)=jFQq1)9v$( zT-utp@FT(T2y_;iW&T5^e+2*PZ~wn-`=7XK!#w4Bh3sChHD4NKD}>S~I?vEBu6P6 zq=A&m(1~JgmHmaAza*XNajN835#GV+!PiDO(;OPg2>uw&5#(C5bghG)Ri^<*Co}Jm z@v#(5KN~3>t=#pLXny*DokFCMYJ;@{#izL&7CUb1k(Q(lRpYkN72>m4avxAG3oN?6 zXy2nwUrvDS_+cgrA@~5luFg&KdD3b|mHKi>ii-tbUIB)9kPH|B0+QXuXb} zj05si#XC_Fk9NUFQ(YMxAjqEnham4EyyyHpl=dv(Uzkqn$dOtfc5fIXL z#d((N)9faWu{Q7TtKWjy1)=jSpHRbM6skD1V7h&WZFOOuCIL~{Q0y|>p5D-qUbRMi zpTKN;nexU_?7JdIfDYeaXqlRs9_w7Ht4{4z>AE(wO!f#s991fd8rqJtH~@4;Hvoif z!?f`3X59PqlDh`Nj`pkW1x=z+vnv~I7ppv+IgHoe> z3f_;PxksdV-xn@95t9xtNFEF2S$cQFly)Z+Qv-DvvD^{VM)btbT*~h<6ZH7uLCbCT z`X8H#R^axypN7beFSXu?(o$V~%3#RR)%7I2#a-1VMT*pj7S%TCOH^*P41{Y0godmd zNXtq!t~%SIh1oA$M9#Sar^bgH_-$L$SIFGhMhHgblfZ8+oH!+%&7fq6}NN3R6DCk1CQTqoK|ySfkXt_IlI;-R(Ec+iDzBDcw7V z7&}OJM|(6sN=F&~chY$<&8n1f6n#_UU;4{~23&N*OwYwAm+eEZ3kP7RYjNr%(T(J) zvAC4U(JVXItSyc9*}O$32$)}EV#-ed%sfqUy-wGQTet1cA8&7OD*~lE;zm^M3PqLR z6HTC*Q$oan%Phl} zkJPiYPO8zyQ$Wii72dNR<a;wbhCNo|htr*Ur(`?n zvM2qhU!S@Y9`{nu$5(Nd#{k)$nP_#c24xgenQLNi65!>M*P54dH!Z<}x z)0?wjWS0giYF+kGTn(Ly{?_ucDUWU*b^?n%YgadA6=VMWfk_gL1hPAqyeFkfFygh( zNdJtgbu!FPtyxx5EF!Fq6c0;}u-@+nV+Pqi2k*CanWlM~=6SxZ)6M%0A+XeI7fhA1 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
++pH3=NEw& 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

?n3xpMQr2o6O>!rUw}K9|25fv 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

DbQQG8p!k{yKD|~ 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

`t_aU}8j}u_!i+YFRg52T4`Ffei(?S41;z5bRH? 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 M07*qoM6N<$f?#J`I{*Lx literal 0 HcmV?d00001 From 247ff33a5387ea20f2d19e15d0f8742eff58db89 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 24 Nov 2021 19:47:49 +0100 Subject: [PATCH 0021/1271] moved UI to new branding --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index b028252d..61dd9f19 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit b028252d30a32e80c12fa890c306d3c5842e937a +Subproject commit 61dd9f192bf7111e5ec85ac45b45af6d201d3958 From b7b54526a2a7fd0dde9732a3493d8073051bd3b4 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 24 Nov 2021 20:14:32 +0100 Subject: [PATCH 0022/1271] Fixed linux app config generating script --- utils/Zano.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/Zano.sh b/utils/Zano.sh index 33188ef3..ff08b6aa 100755 --- a/utils/Zano.sh +++ b/utils/Zano.sh @@ -25,9 +25,9 @@ create_desktop_icon() rm -f $target_file_name echo [Desktop Entry] | tee -a $target_file_name > /dev/null echo Version=1.0 | tee -a $target_file_name > /dev/null - echo Name=My Application | tee -a $target_file_name > /dev/null - echo GenericName=My Application | tee -a $target_file_name > /dev/null - echo Comment=Doing some funny stuff | tee -a $target_file_name > /dev/null + echo Name=Zano | tee -a $target_file_name > /dev/null + echo GenericName=Zano | tee -a $target_file_name > /dev/null + echo Comment=Privacy blockchain | tee -a $target_file_name > /dev/null echo Icon=$script_dir/html/files/desktop_linux_icon.png | tee -a $target_file_name > /dev/null echo Exec=$script_dir/Zano %u | tee -a $target_file_name > /dev/null echo Terminal=true | tee -a $target_file_name > /dev/null From d62eb236fac0931ec5f4529fba8a76ef1899cab8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 24 Nov 2021 20:44:25 +0100 Subject: [PATCH 0023/1271] More tweaks for linux script --- utils/Zano.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/Zano.sh b/utils/Zano.sh index ff08b6aa..e0eac8cc 100755 --- a/utils/Zano.sh +++ b/utils/Zano.sh @@ -12,7 +12,7 @@ out_file_name=~/.local/share/applications/Zano.desktop call_app() { pushd $script_dir - ./Zano + ./Zano "$@" popd exit } @@ -29,7 +29,7 @@ create_desktop_icon() echo GenericName=Zano | tee -a $target_file_name > /dev/null echo Comment=Privacy blockchain | tee -a $target_file_name > /dev/null echo Icon=$script_dir/html/files/desktop_linux_icon.png | tee -a $target_file_name > /dev/null - echo Exec=$script_dir/Zano %u | tee -a $target_file_name > /dev/null + echo Exec=$script_dir/Zano.sh %u | tee -a $target_file_name > /dev/null echo Terminal=true | tee -a $target_file_name > /dev/null echo Type=Application | tee -a $target_file_name > /dev/null echo "Categories=Qt;Utility;" | tee -a $target_file_name > /dev/null @@ -41,4 +41,4 @@ create_desktop_icon $out_file_name xdg-mime default Zano.desktop x-scheme-handler/zano -call_app +call_app "$@" \ No newline at end of file From 0ec0a8cfeab191665a965df4404ad5815fd9a2eb Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 25 Nov 2021 14:34:40 +0100 Subject: [PATCH 0024/1271] tweaks on graphics --- src/gui/qt-daemon/app.icns | Bin 71298 -> 89211 bytes src/gui/qt-daemon/app.ico | Bin 110252 -> 111606 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/gui/qt-daemon/app.icns b/src/gui/qt-daemon/app.icns index 8004a8341ffc9c0eba87e98295a24a81e624c59b..50e0fad5ce9a7d429e6ecacfd5dde34033e2cbe5 100644 GIT binary patch literal 89211 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�ZNBIeijYhp_bRpQ|mv?{vWwIeS;79xo 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

rQmYP#m8;nJ&9s{|A8U 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 literal 110252 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 From d19afd6a69457f71996b2c5bda9255745dec566f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 25 Nov 2021 14:36:00 +0100 Subject: [PATCH 0025/1271] pulled zano_ui to latest commit --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 61dd9f19..d32d0f9f 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 61dd9f192bf7111e5ec85ac45b45af6d201d3958 +Subproject commit d32d0f9f239589b0cdf0ba674991124c231734de From d6ef8155665501f0f34c3c9d53d0251c00315bbc Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 25 Nov 2021 14:39:51 +0100 Subject: [PATCH 0026/1271] Version moved to 1.4.0 --- src/version.h.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index 2f4959ae..117c1306 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -4,8 +4,8 @@ #define BUILD_COMMIT_ID "@VERSION@" #define PROJECT_MAJOR_VERSION "1" -#define PROJECT_MINOR_VERSION "3" -#define PROJECT_REVISION "2" +#define PROJECT_MINOR_VERSION "4" +#define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION #define PROJECT_VERSION_BUILD_NO 135 From c7788ab181970ba1892797c1a002e1ba6c836620 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Thu, 25 Nov 2021 16:41:04 +0300 Subject: [PATCH 0027/1271] === build number: 135 -> 136 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 117c1306..69cd610e 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 135 +#define PROJECT_VERSION_BUILD_NO 136 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 3dfebddbddb5d7d3f19d5ea759ad2b045cdb5365 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 25 Nov 2021 15:28:04 +0100 Subject: [PATCH 0028/1271] pulled zano_ui to latest commit again --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index d32d0f9f..6299e301 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit d32d0f9f239589b0cdf0ba674991124c231734de +Subproject commit 6299e3014aea2f1c5055405f37f0e9de4afc60dd From 88a8d58e35aab0ace2e02daa9b8011fb713154d8 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Thu, 25 Nov 2021 17:29:04 +0300 Subject: [PATCH 0029/1271] === build number: 136 -> 137 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 69cd610e..79c7c646 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 136 +#define PROJECT_VERSION_BUILD_NO 137 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 71ef04d9e6277cd2c6af0ef0a6577a10c699b677 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 26 Nov 2021 16:06:44 +0300 Subject: [PATCH 0030/1271] checkpoints: get_checkpoint_before_height() added with unit tests (+ unit tests for is_in_checkpoint_zone()) --- src/currency_core/checkpoints.cpp | 27 ++++++++++++++-- src/currency_core/checkpoints.h | 2 ++ tests/unit_tests/check_points_test.cpp | 45 ++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/src/currency_core/checkpoints.cpp b/src/currency_core/checkpoints.cpp index 2d997691..3dacc47e 100644 --- a/src/currency_core/checkpoints.cpp +++ b/src/currency_core/checkpoints.cpp @@ -36,11 +36,11 @@ namespace currency if(height > blockchain_last_block_height) return false; - auto it = m_points.lower_bound(height); + auto it = m_points.lower_bound(height); // if found, it->first >= height if(it == m_points.end()) return false; if(it->first <= blockchain_last_block_height) - return true; + return true; // this is the case only if height <= it->first <= blockchain_last_block_height else return false; } @@ -68,4 +68,27 @@ namespace currency return false; } } + //--------------------------------------------------------------------------- + uint64_t checkpoints::get_checkpoint_before_height(uint64_t height) const + { + // returns height of the leftmost CP with height that is LESS than the given height + // ex: + // If there are two CP at 11 and 15: + // get_checkpoint_before_height(10) = 0 + // get_checkpoint_before_height(11) = 0 + // get_checkpoint_before_height(12) = 11 + // get_checkpoint_before_height(13) = 11 + // get_checkpoint_before_height(14) = 11 + // get_checkpoint_before_height(15) = 11 + // get_checkpoint_before_height(16) = 15 + + uint64_t top_cp = get_top_checkpoint_height(); + if (height > top_cp) + return top_cp; + + auto it = m_points.lower_bound(height); // if found, it->first >= height + if (it == m_points.end() || --it == m_points.end()) + return 0; + return it->first; + } } diff --git a/src/currency_core/checkpoints.h b/src/currency_core/checkpoints.h index a845e6eb..76675e67 100644 --- a/src/currency_core/checkpoints.h +++ b/src/currency_core/checkpoints.h @@ -20,6 +20,8 @@ namespace currency bool is_height_passed_zone(uint64_t height, uint64_t blockchain_last_block_height) const; bool check_block(uint64_t height, const crypto::hash& h) const; uint64_t get_top_checkpoint_height() const; + + uint64_t get_checkpoint_before_height(uint64_t height) const; private: std::map m_points; }; diff --git a/tests/unit_tests/check_points_test.cpp b/tests/unit_tests/check_points_test.cpp index 543e2ac6..0c9e5c26 100644 --- a/tests/unit_tests/check_points_test.cpp +++ b/tests/unit_tests/check_points_test.cpp @@ -39,3 +39,48 @@ TEST(checkpoints_test, test_checkpoints_for_alternative) r = cp.is_height_passed_zone(11, 12); ASSERT_FALSE(r); } + +TEST(checkpoints_test, get_checkpoint_before_height) +{ + currency::checkpoints cp; + cp.add_checkpoint(11, "0000000000000000000000000000000000000000000000000000000000000000"); + cp.add_checkpoint(15, "0000000000000000000000000000000000000000000000000000000000000000"); + cp.add_checkpoint(21, "0000000000000000000000000000000000000000000000000000000000000000"); + + for(uint64_t h = 0; h < 11; ++h) + ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 0); + + ASSERT_TRUE(cp.get_checkpoint_before_height(11) == 0); + + ASSERT_TRUE(cp.get_checkpoint_before_height(12) == 11); + ASSERT_TRUE(cp.get_checkpoint_before_height(13) == 11); + ASSERT_TRUE(cp.get_checkpoint_before_height(14) == 11); + ASSERT_TRUE(cp.get_checkpoint_before_height(15) == 11); + + ASSERT_TRUE(cp.get_checkpoint_before_height(16) == 15); + ASSERT_TRUE(cp.get_checkpoint_before_height(17) == 15); + ASSERT_TRUE(cp.get_checkpoint_before_height(18) == 15); + ASSERT_TRUE(cp.get_checkpoint_before_height(19) == 15); + ASSERT_TRUE(cp.get_checkpoint_before_height(20) == 15); + ASSERT_TRUE(cp.get_checkpoint_before_height(21) == 15); + + ASSERT_TRUE(cp.get_checkpoint_before_height(22) == 21); + + for(uint64_t h = 22; h < 100; ++h) + ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 21); +} + + +TEST(checkpoints_test, is_in_checkpoint_zone) +{ + currency::checkpoints cp; + cp.add_checkpoint(11, "0000000000000000000000000000000000000000000000000000000000000000"); + cp.add_checkpoint(15, "0000000000000000000000000000000000000000000000000000000000000000"); + cp.add_checkpoint(21, "0000000000000000000000000000000000000000000000000000000000000000"); + + for (uint64_t h = 0; h < 22; ++h) + ASSERT_TRUE(cp.is_in_checkpoint_zone(h)); + + for (uint64_t h = 22; h < 100; ++h) + ASSERT_FALSE(cp.is_in_checkpoint_zone(h)); +} From f86459935a4c98eeb938ef97a26951831f34651a Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 26 Nov 2021 16:08:43 +0300 Subject: [PATCH 0031/1271] coretests fixed after moving to json format of gui config --- tests/core_tests/chaingen_main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index b50044b6..763b1a94 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -57,7 +57,7 @@ bool clean_data_directory() { std::string config_folder = command_line::get_arg(g_vm, command_line::arg_data_dir); - static const std::set files = { CURRENCY_POOLDATA_FOLDERNAME_OLD, CURRENCY_BLOCKCHAINDATA_FOLDERNAME_OLD, P2P_NET_DATA_FILENAME, MINER_CONFIG_FILENAME, GUI_SECURE_CONFIG_FILENAME, GUI_CONFIG_FILENAME, GUI_INTERNAL_CONFIG }; + static const std::set files = { CURRENCY_POOLDATA_FOLDERNAME_OLD, CURRENCY_BLOCKCHAINDATA_FOLDERNAME_OLD, P2P_NET_DATA_FILENAME, MINER_CONFIG_FILENAME, GUI_SECURE_CONFIG_FILENAME, GUI_CONFIG_FILENAME, GUI_INTERNAL_CONFIG2 }; static const std::set prefixes = { CURRENCY_POOLDATA_FOLDERNAME_PREFIX, CURRENCY_BLOCKCHAINDATA_FOLDERNAME_PREFIX }; std::vector entries_to_remove; From 5c15ef7bcc1e9fd1e88212cac6382c312f9fc612 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 26 Nov 2021 16:10:30 +0300 Subject: [PATCH 0032/1271] coretests: checkpoints_test improved to support multiple CPs, gen_checkpoints_set_after_switching_to_altchain test added --- tests/core_tests/chaingen_main.cpp | 1 + tests/core_tests/checkpoints_tests.cpp | 82 +++++++++++++++++++++++++- tests/core_tests/checkpoints_tests.h | 10 ++++ 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 763b1a94..2dc1eef6 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -814,6 +814,7 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_checkpoints_reorganize); GENERATE_AND_PLAY(gen_checkpoints_pos_validation_on_altchain); GENERATE_AND_PLAY(gen_checkpoints_and_invalid_tx_to_pool); + GENERATE_AND_PLAY(gen_checkpoints_set_after_switching_to_altchain); GENERATE_AND_PLAY(gen_no_attchments_in_coinbase); GENERATE_AND_PLAY(gen_no_attchments_in_coinbase_gentime); diff --git a/tests/core_tests/checkpoints_tests.cpp b/tests/core_tests/checkpoints_tests.cpp index da68a5fd..d1ec860a 100644 --- a/tests/core_tests/checkpoints_tests.cpp +++ b/tests/core_tests/checkpoints_tests.cpp @@ -36,13 +36,18 @@ bool checkpoints_test::set_checkpoint(currency::core& c, size_t ev_index, const { if (pcp.hash != null_hash && pcp.hash != get_block_hash(b)) continue; - currency::checkpoints cp; - cp.add_checkpoint(currency::get_block_height(b), epee::string_tools::pod_to_hex(currency::get_block_hash(b))); - c.set_checkpoints(std::move(cp)); + m_local_checkpoints.add_checkpoint(pcp.height, epee::string_tools::pod_to_hex(currency::get_block_hash(b))); + c.set_checkpoints(currency::checkpoints(m_local_checkpoints)); + LOG_PRINT_YELLOW("CHECKPOINT set at height " << pcp.height, LOG_LEVEL_0); + + //for(uint64_t h = 0; h <= pcp.height + 1; ++h) + // LOG_PRINT_MAGENTA("%% " << h << " : " << m_local_checkpoints.get_checkpoint_before_height(h), LOG_LEVEL_0); return true; } } + LOG_ERROR("set_checkpoint failed trying to set checkpoint at height " << pcp.height); + return false; } @@ -895,3 +900,74 @@ bool gen_checkpoints_and_invalid_tx_to_pool::c1(currency::core& c, size_t ev_ind return true; } +//------------------------------------------------------------------------------ + +gen_checkpoints_set_after_switching_to_altchain::gen_checkpoints_set_after_switching_to_altchain() +{ + REGISTER_CALLBACK_METHOD(gen_checkpoints_set_after_switching_to_altchain, prune_blockchain); +} + +bool gen_checkpoints_set_after_switching_to_altchain::generate(std::vector& events) const +{ + // Test idea: make sure + + // 0 ... N N+1 N+2 N+3 N+4 N+5 N+6 <- height (N = CURRENCY_MINED_MONEY_UNLOCK_WINDOW) + // tx1 + // (0 )- (0r)- (1 )- (2a)- (3a)- <- alt chain + // \ + // \- (2 )- <- main chain + + bool r = false; + GENERATE_ACCOUNT(miner_acc); + GENERATE_ACCOUNT(alice_acc); + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + DO_CALLBACK(events, "check_not_being_in_cp_zone"); + + MAKE_NEXT_BLOCK(events, blk_1, blk_0r, miner_acc); + MAKE_TX(events, tx1, miner_acc, alice_acc, MK_TEST_COINS(1), blk_1); + MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1, miner_acc, tx1); + MAKE_NEXT_BLOCK(events, blk_3a, blk_2a, miner_acc); + + DO_CALLBACK(events, "check_not_being_in_cp_zone"); + + // 0 ... N N+1 N+2 N+3 N+4 N+5 N+6 <- height (N = CURRENCY_MINED_MONEY_UNLOCK_WINDOW) + // +-----------> CP <- checkpoint + // tx1 | + // (0 )- (0r)- (1 )- (2a)- (3a)- <- alt chain + // \ | <- when CP set up + // \- (2 )- (3 )- (4 )- (5 )- (6 )- <- main chain + + DO_CALLBACK_PARAMS(events, "set_checkpoint", params_checkpoint(CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 5)); + DO_CALLBACK(events, "prune_blockchain"); + + MAKE_NEXT_BLOCK(events, blk_2, blk_1, miner_acc); + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); + MAKE_NEXT_BLOCK(events, blk_4, blk_3, miner_acc); + MAKE_NEXT_BLOCK(events, blk_5, blk_4, miner_acc); // <-- CHECKPOINT + MAKE_NEXT_BLOCK(events, blk_6, blk_5, miner_acc); + + return true; +} + +bool gen_checkpoints_set_after_switching_to_altchain::prune_blockchain(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false; + uint64_t height = 0; + uint64_t transactions_pruned = 0, signatures_pruned = 0, attachments_pruned = 0; + + c.get_blockchain_storage().prune_ring_signatures_and_attachments_if_need(); + + /*for (uint64_t height = 1, size = c.get_current_blockchain_size(); height < size; ++height) + { + r = c.get_blockchain_storage().prune_ring_signatures_and_attachments(height, transactions_pruned, signatures_pruned, attachments_pruned); + CHECK_AND_ASSERT_MES(r, false, "prune_ring_signatures_and_attachments failed for height " << height); + } + + // make sure only one tx was pruned (namely, tx1) + CHECK_AND_ASSERT_MES(transactions_pruned == 1, false, "incorrect number of pruned txs: " << transactions_pruned); + */ + return true; +} diff --git a/tests/core_tests/checkpoints_tests.h b/tests/core_tests/checkpoints_tests.h index 67f84a12..01ff5f68 100644 --- a/tests/core_tests/checkpoints_tests.h +++ b/tests/core_tests/checkpoints_tests.h @@ -23,6 +23,9 @@ protected: uint64_t height; crypto::hash hash; }; + +private: + currency::checkpoints m_local_checkpoints; }; struct gen_checkpoints_attachments_basic : public checkpoints_test @@ -109,3 +112,10 @@ struct gen_checkpoints_and_invalid_tx_to_pool : public checkpoints_test bool generate(std::vector& events) const; bool c1(currency::core& c, size_t ev_index, const std::vector& events); }; + +struct gen_checkpoints_set_after_switching_to_altchain : public checkpoints_test +{ + gen_checkpoints_set_after_switching_to_altchain(); + bool generate(std::vector& events) const; + bool prune_blockchain(currency::core& c, size_t ev_index, const std::vector& events); +}; From 0b1b2d5bdf5f0f556d8a507c8e8f23ef1750f272 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 26 Nov 2021 16:23:41 +0300 Subject: [PATCH 0033/1271] coretests: trying not to mess with blockchainstorage too much --- tests/core_tests/checkpoints_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core_tests/checkpoints_tests.cpp b/tests/core_tests/checkpoints_tests.cpp index d1ec860a..33698163 100644 --- a/tests/core_tests/checkpoints_tests.cpp +++ b/tests/core_tests/checkpoints_tests.cpp @@ -958,7 +958,7 @@ bool gen_checkpoints_set_after_switching_to_altchain::prune_blockchain(currency: uint64_t height = 0; uint64_t transactions_pruned = 0, signatures_pruned = 0, attachments_pruned = 0; - c.get_blockchain_storage().prune_ring_signatures_and_attachments_if_need(); + //c.get_blockchain_storage().prune_ring_signatures_and_attachments_if_need(); /*for (uint64_t height = 1, size = c.get_current_blockchain_size(); height < size; ++height) { From ba912973cf21e221f6fdd1615c41b5513282ed93 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 26 Nov 2021 17:23:50 +0300 Subject: [PATCH 0034/1271] bcs: minor fixes --- src/currency_core/blockchain_storage.cpp | 8 +++++--- src/currency_core/blockchain_storage.h | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 54813472..3335c94c 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -578,13 +578,13 @@ bool blockchain_storage::set_checkpoints(checkpoints&& chk_pts) catch (const std::exception& ex) { m_db.abort_transaction(); - LOG_ERROR("UNKNOWN EXCEPTION WHILE ADDINIG NEW BLOCK: " << ex.what()); + LOG_ERROR("UNKNOWN EXCEPTION WHILE SETTING CHECKPOINTS: " << ex.what()); return false; } catch (...) { m_db.abort_transaction(); - LOG_ERROR("UNKNOWN EXCEPTION WHILE ADDINIG NEW BLOCK."); + LOG_ERROR("UNKNOWN EXCEPTION WHILE SETTING CHECKPOINTS."); return false; } @@ -1036,7 +1036,9 @@ void blockchain_storage::purge_alt_block_txs_hashs(const block& b) //------------------------------------------------------------------ void blockchain_storage::do_erase_altblock(alt_chain_container::iterator it) { - purge_altblock_keyimages_from_big_heap(it->second.bl, get_block_hash(it->second.bl)); + crypto::hash id = get_block_hash(it->second.bl); + LOG_PRINT_L1("erasing alt block " << print16(id) << " @ " << get_block_height(it->second.bl)); + purge_altblock_keyimages_from_big_heap(it->second.bl, id); purge_alt_block_txs_hashs(it->second.bl); m_alternative_chains.erase(it); } diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 7cb02c41..cad2ca91 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -396,7 +396,7 @@ namespace currency else { CHECK_AND_ASSERT_MES(*block_ind_ptr < m_db_blocks.size(), false, "Internal error: bl_id=" << string_tools::pod_to_hex(bl_id) - << " have index record with offset=" << *block_ind_ptr << ", bigger then m_blocks.size()=" << m_db_blocks.size()); + << " have index record with offset=" << *block_ind_ptr << ", bigger then m_db_blocks.size()=" << m_db_blocks.size()); blocks.push_back(m_db_blocks[*block_ind_ptr]->bl); } } From de15869c27b7a28e0eedd20a0c9699e008260f68 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 27 Nov 2021 03:30:05 +0300 Subject: [PATCH 0035/1271] coretests: gen_checkpoints_set_after_switching_to_altchain fixed to fail if no fix present --- tests/core_tests/checkpoints_tests.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/core_tests/checkpoints_tests.cpp b/tests/core_tests/checkpoints_tests.cpp index 33698163..ab06f8aa 100644 --- a/tests/core_tests/checkpoints_tests.cpp +++ b/tests/core_tests/checkpoints_tests.cpp @@ -945,10 +945,17 @@ bool gen_checkpoints_set_after_switching_to_altchain::generate(std::vector Date: Sat, 27 Nov 2021 05:26:36 +0300 Subject: [PATCH 0036/1271] blockchain_storage::prune_ring_signatures_and_attachments_if_need() fixed (it could prune txs in cases when chain switching was still possible) --- src/currency_core/blockchain_storage.cpp | 26 +++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 3335c94c..b247c050 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -626,22 +626,20 @@ bool blockchain_storage::prune_ring_signatures_and_attachments_if_need() { CRITICAL_REGION_LOCAL(m_read_lock); - if (m_db_blocks.size() > 1 && m_checkpoints.get_top_checkpoint_height() && m_checkpoints.get_top_checkpoint_height() > m_db_current_pruned_rs_height) - { - uint64_t pruning_last_height = std::min(m_db_blocks.size() - 1, m_checkpoints.get_top_checkpoint_height()); - if (pruning_last_height > m_db_current_pruned_rs_height) + uint64_t top_block_height = get_top_block_height(); + uint64_t pruning_end_height = m_checkpoints.get_checkpoint_before_height(top_block_height); + if (pruning_end_height > m_db_current_pruned_rs_height) + { + LOG_PRINT_CYAN("Starting pruning ring signatues and attachments from height " << m_db_current_pruned_rs_height + 1 << " to height " << pruning_end_height + << " (" << pruning_end_height - m_db_current_pruned_rs_height << " blocks)", LOG_LEVEL_0); + uint64_t tx_count = 0, sig_count = 0, attach_count = 0; + for(uint64_t height = m_db_current_pruned_rs_height + 1; height <= pruning_end_height; height++) { - LOG_PRINT_CYAN("Starting pruning ring signatues and attachments from height " << m_db_current_pruned_rs_height + 1 << " to height " << pruning_last_height - << " (" << pruning_last_height - m_db_current_pruned_rs_height << " blocks)", LOG_LEVEL_0); - uint64_t tx_count = 0, sig_count = 0, attach_count = 0; - for(uint64_t height = m_db_current_pruned_rs_height + 1; height <= pruning_last_height; height++) - { - bool res = prune_ring_signatures_and_attachments(height, tx_count, sig_count, attach_count); - CHECK_AND_ASSERT_MES(res, false, "failed to prune_ring_signatures_and_attachments for height = " << height); - } - m_db_current_pruned_rs_height = pruning_last_height; - LOG_PRINT_CYAN("Transaction pruning finished: " << sig_count << " signatures and " << attach_count << " attachments released in " << tx_count << " transactions.", LOG_LEVEL_0); + bool res = prune_ring_signatures_and_attachments(height, tx_count, sig_count, attach_count); + CHECK_AND_ASSERT_MES(res, false, "failed to prune_ring_signatures_and_attachments for height = " << height); } + m_db_current_pruned_rs_height = pruning_end_height; + LOG_PRINT_CYAN("Transaction pruning finished: " << sig_count << " signatures and " << attach_count << " attachments released in " << tx_count << " transactions.", LOG_LEVEL_0); } return true; } From 03a828228c12e14729bba1e487b3078951812bc1 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 29 Nov 2021 04:50:30 +0300 Subject: [PATCH 0037/1271] coretests: 1) gen_checkpoints_prun_txs_after_blockchain_load fixed to reflect new rules on tx pruning; 2) final touches to gen_checkpoints_set_after_switching_to_altchain --- tests/core_tests/checkpoints_tests.cpp | 45 ++++++++++---------------- tests/core_tests/checkpoints_tests.h | 1 - 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/tests/core_tests/checkpoints_tests.cpp b/tests/core_tests/checkpoints_tests.cpp index ab06f8aa..254a05e6 100644 --- a/tests/core_tests/checkpoints_tests.cpp +++ b/tests/core_tests/checkpoints_tests.cpp @@ -400,8 +400,13 @@ bool gen_checkpoints_prun_txs_after_blockchain_load::generate(std::vector attach; + attach.push_back(tx_comment{"jokes are funny"}); + + // tx pool won't accept the tx, because it cannot be verified in CP zone + // set kept_by_block flag, so tx_0 be accepted + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); + MAKE_TX_ATTACH(events, tx_0, miner_acc, alice, MK_TEST_COINS(1), blk_0r, attach); events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_0); @@ -412,11 +417,12 @@ bool gen_checkpoints_prun_txs_after_blockchain_load::generate(std::vector& events) const { - // Test idea: make sure + // Test outline: + // 0) no checkpoints are set; + // 1) core is in a subchain, that will become alternative; + // 2) checkpoint is set (in the furute), transaction pruning is executed; + // 3) core continues to sync, chain switching occurs + // Make sure that chain switching is still possible after pruning. // 0 ... N N+1 N+2 N+3 N+4 N+5 N+6 <- height (N = CURRENCY_MINED_MONEY_UNLOCK_WINDOW) // tx1 @@ -941,7 +951,6 @@ bool gen_checkpoints_set_after_switching_to_altchain::generate(std::vector& events) -{ - bool r = false; - uint64_t height = 0; - uint64_t transactions_pruned = 0, signatures_pruned = 0, attachments_pruned = 0; - - //c.get_blockchain_storage().prune_ring_signatures_and_attachments_if_need(); - - /*for (uint64_t height = 1, size = c.get_current_blockchain_size(); height < size; ++height) - { - r = c.get_blockchain_storage().prune_ring_signatures_and_attachments(height, transactions_pruned, signatures_pruned, attachments_pruned); - CHECK_AND_ASSERT_MES(r, false, "prune_ring_signatures_and_attachments failed for height " << height); - } - - // make sure only one tx was pruned (namely, tx1) - CHECK_AND_ASSERT_MES(transactions_pruned == 1, false, "incorrect number of pruned txs: " << transactions_pruned); - */ - return true; -} diff --git a/tests/core_tests/checkpoints_tests.h b/tests/core_tests/checkpoints_tests.h index 01ff5f68..672d9b42 100644 --- a/tests/core_tests/checkpoints_tests.h +++ b/tests/core_tests/checkpoints_tests.h @@ -117,5 +117,4 @@ struct gen_checkpoints_set_after_switching_to_altchain : public checkpoints_test { gen_checkpoints_set_after_switching_to_altchain(); bool generate(std::vector& events) const; - bool prune_blockchain(currency::core& c, size_t ev_index, const std::vector& events); }; From 5f0b5f8f55c4330103a409a78cd68a8a9bd648c3 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 29 Nov 2021 23:46:33 +0300 Subject: [PATCH 0038/1271] unittests: checkpoints_test.get_checkpoint_before_height_1 and checkpoints_test.get_checkpoint_before_height_2 tests added --- tests/unit_tests/check_points_test.cpp | 47 +++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/check_points_test.cpp b/tests/unit_tests/check_points_test.cpp index 0c9e5c26..ab2372c1 100644 --- a/tests/unit_tests/check_points_test.cpp +++ b/tests/unit_tests/check_points_test.cpp @@ -40,14 +40,53 @@ TEST(checkpoints_test, test_checkpoints_for_alternative) ASSERT_FALSE(r); } -TEST(checkpoints_test, get_checkpoint_before_height) + +TEST(checkpoints_test, get_checkpoint_before_height_1) +{ + currency::checkpoints cp; + cp.add_checkpoint(15, "0000000000000000000000000000000000000000000000000000000000000000"); + + for (uint64_t h = 0; h <= 15; ++h) + ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 0); + + ASSERT_TRUE(cp.get_checkpoint_before_height(16) == 15); + + for (uint64_t h = 17; h < 100; ++h) + ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 15); +} + + +TEST(checkpoints_test, get_checkpoint_before_height_2) +{ + currency::checkpoints cp; + cp.add_checkpoint(11, "0000000000000000000000000000000000000000000000000000000000000000"); + cp.add_checkpoint(15, "0000000000000000000000000000000000000000000000000000000000000000"); + + for (uint64_t h = 0; h < 11; ++h) + ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 0); + + ASSERT_TRUE(cp.get_checkpoint_before_height(11) == 0); + + ASSERT_TRUE(cp.get_checkpoint_before_height(12) == 11); + ASSERT_TRUE(cp.get_checkpoint_before_height(13) == 11); + ASSERT_TRUE(cp.get_checkpoint_before_height(14) == 11); + ASSERT_TRUE(cp.get_checkpoint_before_height(15) == 11); + + ASSERT_TRUE(cp.get_checkpoint_before_height(16) == 15); + + for (uint64_t h = 17; h < 100; ++h) + ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 15); +} + + +TEST(checkpoints_test, get_checkpoint_before_height_3) { currency::checkpoints cp; cp.add_checkpoint(11, "0000000000000000000000000000000000000000000000000000000000000000"); cp.add_checkpoint(15, "0000000000000000000000000000000000000000000000000000000000000000"); cp.add_checkpoint(21, "0000000000000000000000000000000000000000000000000000000000000000"); - for(uint64_t h = 0; h < 11; ++h) + for (uint64_t h = 0; h < 11; ++h) ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 0); ASSERT_TRUE(cp.get_checkpoint_before_height(11) == 0); @@ -63,10 +102,10 @@ TEST(checkpoints_test, get_checkpoint_before_height) ASSERT_TRUE(cp.get_checkpoint_before_height(19) == 15); ASSERT_TRUE(cp.get_checkpoint_before_height(20) == 15); ASSERT_TRUE(cp.get_checkpoint_before_height(21) == 15); - + ASSERT_TRUE(cp.get_checkpoint_before_height(22) == 21); - for(uint64_t h = 22; h < 100; ++h) + for (uint64_t h = 22; h < 100; ++h) ASSERT_TRUE(cp.get_checkpoint_before_height(h) == 21); } From e99d4ad8e24602c3c30f57215cdbb83d7b934eef Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 30 Nov 2021 00:20:51 +0300 Subject: [PATCH 0039/1271] get_checkpoint_before_height() fixed (rare case in gcc when CPs < 3) --- src/currency_core/checkpoints.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/currency_core/checkpoints.cpp b/src/currency_core/checkpoints.cpp index 3dacc47e..bbce9815 100644 --- a/src/currency_core/checkpoints.cpp +++ b/src/currency_core/checkpoints.cpp @@ -87,8 +87,8 @@ namespace currency return top_cp; auto it = m_points.lower_bound(height); // if found, it->first >= height - if (it == m_points.end() || --it == m_points.end()) + if (it == m_points.end() || it == m_points.begin()) return 0; - return it->first; + return (--it)->first; } } From 0ad4ab6c52a3146c8a5a3d22136ff34d6a3f4d6c Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 1 Dec 2021 05:19:11 +0300 Subject: [PATCH 0040/1271] coretests: wallet_spend_form_auditable_and_track test added --- tests/core_tests/chaingen_main.cpp | 3 ++ tests/core_tests/wallet_tests.cpp | 82 ++++++++++++++++++++++++++++++ tests/core_tests/wallet_tests.h | 9 ++++ 3 files changed, 94 insertions(+) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 2dc1eef6..3e9cbc80 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -743,6 +743,9 @@ int main(int argc, char* argv[]) #undef MARK_TEST_AS_POSTPONED + + // TODO // GENERATE_AND_PLAY(wallet_spend_form_auditable_and_track); + GENERATE_AND_PLAY(pos_minting_tx_packing); GENERATE_AND_PLAY(multisig_wallet_test); diff --git a/tests/core_tests/wallet_tests.cpp b/tests/core_tests/wallet_tests.cpp index ab62563f..f4f2e027 100644 --- a/tests/core_tests/wallet_tests.cpp +++ b/tests/core_tests/wallet_tests.cpp @@ -3553,5 +3553,87 @@ bool wallet_watch_only_and_chain_switch::c1(currency::core& c, size_t ev_index, CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(7), false, UINT64_MAX, 0, 0, MK_TEST_COINS(7), 0), false, ""); + return true; +} + +//------------------------------------------------------------------------------ + +wallet_spend_form_auditable_and_track::wallet_spend_form_auditable_and_track() +{ + REGISTER_CALLBACK_METHOD(wallet_spend_form_auditable_and_track, c1); +} + +bool wallet_spend_form_auditable_and_track::generate(std::vector& events) const +{ + bool r = false; + + m_accounts.resize(TOTAL_ACCS_COUNT); + account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); + account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(true); // Alice has auditable wallet + account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc = alice_acc; bob_acc.make_account_watch_only(); // Bob has Alice's tracking wallet + + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + REWIND_BLOCKS_N(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + MAKE_TX(events, tx_1, miner_acc, alice_acc, MK_TEST_COINS(5), blk_0r); + + MAKE_NEXT_BLOCK_TX1(events, blk_1, blk_0r, miner_acc, tx_1); + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, WALLET_DEFAULT_TX_SPENDABLE_AGE); + + std::vector attachments; + tx_comment comment_attachment = AUTO_VAL_INIT(comment_attachment); + m_comment = "Jokes are funny!"; + comment_attachment.comment = m_comment; + attachments.push_back(comment_attachment); + MAKE_TX_ATTACH(events, tx_2, alice_acc, miner_acc, MK_TEST_COINS(1), blk_1r, attachments); + + MAKE_NEXT_BLOCK_TX1(events, blk_2, blk_1r, miner_acc, tx_2); + + DO_CALLBACK(events, "c1"); + + return true; +} + +bool wallet_spend_form_auditable_and_track::c1(currency::core& c, size_t ev_index, const std::vector& events) +{ + bool r = false; + std::shared_ptr miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX); + std::shared_ptr alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX); + std::shared_ptr bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX); + + // make sure Alice's wallet is autibale and not watch-only + CHECK_AND_ASSERT_MES(!alice_wlt->is_watch_only() && alice_wlt->is_auditable(), false, "incorrect type of Alice's wallet"); + // make sure Bob's wallet is a tracking wallet + CHECK_AND_ASSERT_MES(bob_wlt->is_watch_only() && bob_wlt->is_auditable(), false, "incorrect type of Bob's wallet"); + + const account_public_address& bob_addr = bob_wlt->get_account().get_public_address(); + const account_public_address& alice_addr = alice_wlt->get_account().get_public_address(); + + // make sure their addresses are linked indeed + CHECK_AND_ASSERT_MES(bob_addr.view_public_key == alice_addr.view_public_key && bob_addr.spend_public_key == alice_addr.spend_public_key, false, + "Bob's tracking wallet address is not linked with Alice's one"); + + CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Incorrect txs count in the pool: " << c.get_pool_transactions_count()); + + bob_wlt->refresh(); + + // check the balances + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(3), false, UINT64_MAX, 0, 0, 0, 0), false, ""); + CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, MK_TEST_COINS(3), false, UINT64_MAX, 0, 0, 0, 0), false, ""); + + r = false; + bool r_comment = false; + bob_wlt->enumerate_transfers_history([&](const tools::wallet_public::wallet_transfer_info& wti) { + if (wti.amount == MK_TEST_COINS(5)) + { + r_comment = (wti.comment == m_comment); + if (!r_comment) + return false; // stop + } + return true; // continue + }, true); + CHECK_AND_ASSERT_MES(r, false, "cannot get comment from tx"); + CHECK_AND_ASSERT_MES(r_comment, false, "wrong comment got from tx"); + return true; } diff --git a/tests/core_tests/wallet_tests.h b/tests/core_tests/wallet_tests.h index 17c83bcd..eb54960b 100644 --- a/tests/core_tests/wallet_tests.h +++ b/tests/core_tests/wallet_tests.h @@ -276,3 +276,12 @@ struct wallet_watch_only_and_chain_switch : public wallet_test mutable crypto::hash m_split_point_block_id; mutable uint64_t m_split_point_block_height; }; + +struct wallet_spend_form_auditable_and_track : public wallet_test +{ + wallet_spend_form_auditable_and_track(); + bool generate(std::vector& events) const; + bool c1(currency::core& c, size_t ev_index, const std::vector& events); + + mutable std::string m_comment; +}; From 708fb4c40bea0db2d5fd354882c6ddc4b60ce83e Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 1 Dec 2021 12:29:28 +0300 Subject: [PATCH 0041/1271] bcs: minor improvements --- src/currency_core/blockchain_storage.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index b247c050..6bb47719 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -594,7 +594,7 @@ bool blockchain_storage::prune_ring_signatures_and_attachments(uint64_t height, { CRITICAL_REGION_LOCAL(m_read_lock); - CHECK_AND_ASSERT_MES(height < m_db_blocks.size(), false, "prune_ring_signatures called with wrong parameter: " << height << ", m_blocks.size() " << m_db_blocks.size()); + CHECK_AND_ASSERT_MES(height < m_db_blocks.size(), false, "prune_ring_signatures called with wrong parameter: " << height << ", m_blocks.size() = " << m_db_blocks.size()); auto vptr = m_db_blocks[height]; CHECK_AND_ASSERT_MES(vptr.get(), false, "Failed to get block on height"); @@ -631,7 +631,7 @@ bool blockchain_storage::prune_ring_signatures_and_attachments_if_need() if (pruning_end_height > m_db_current_pruned_rs_height) { LOG_PRINT_CYAN("Starting pruning ring signatues and attachments from height " << m_db_current_pruned_rs_height + 1 << " to height " << pruning_end_height - << " (" << pruning_end_height - m_db_current_pruned_rs_height << " blocks)", LOG_LEVEL_0); + << " (" << pruning_end_height - m_db_current_pruned_rs_height << " blocks), top block height is " << top_block_height, LOG_LEVEL_0); uint64_t tx_count = 0, sig_count = 0, attach_count = 0; for(uint64_t height = m_db_current_pruned_rs_height + 1; height <= pruning_end_height; height++) { From ba35377d45dbadbbdcf5ca6cdeecc755f2dbb6c5 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 2 Dec 2021 16:45:25 +0100 Subject: [PATCH 0042/1271] fixed #313 --- src/gui/qt-daemon/application/mainwindow.cpp | 34 ++++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index acdaef1c..083c2afd 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -548,15 +548,29 @@ void MainWindow::restore_pos(bool consider_showed) } else { - - QPoint pos; - QSize sz; - pos.setX(m_config.m_window_position.first); - pos.setY(m_config.m_window_position.second); - sz.setHeight(m_config.m_window_size.first); - sz.setWidth(m_config.m_window_size.second); - this->move(pos); - this->resize(sz); + QPoint point = QApplication::desktop()->screenGeometry().bottomRight(); + if (m_config.m_window_position.first + m_config.m_window_size.second > point.x() || + m_config.m_window_position.second + m_config.m_window_size.first > point.y() + ) + { + QSize sz = AUTO_VAL_INIT(sz); + sz.setHeight(770); + sz.setWidth(1200); + this->resize(sz); + store_window_pos(); + //reset position(screen changed or other reason) + } + else + { + QPoint pos = AUTO_VAL_INIT(pos); + QSize sz = AUTO_VAL_INIT(sz); + pos.setX(m_config.m_window_position.first); + pos.setY(m_config.m_window_position.second); + sz.setHeight(m_config.m_window_size.first); + sz.setWidth(m_config.m_window_size.second); + this->move(pos); + this->resize(sz); + } } if (consider_showed) @@ -645,7 +659,7 @@ bool MainWindow::show_inital() { m_config = AUTO_VAL_INIT(m_config); this->show(); - QSize sz; + QSize sz = AUTO_VAL_INIT(sz); sz.setHeight(770); sz.setWidth(1200); this->resize(sz); From 0b1c09154724ad6129bab8e6adefe02203ac58d8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 2 Dec 2021 16:47:03 +0100 Subject: [PATCH 0043/1271] pulled zano_ui to latest commit again --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 6299e301..20f9219e 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 6299e3014aea2f1c5055405f37f0e9de4afc60dd +Subproject commit 20f9219e5f782e019983680791a59b79701e0081 From 72c51c34d34a4cd7aba3396b31e0f928443abde1 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 3 Dec 2021 13:10:57 +0100 Subject: [PATCH 0044/1271] pulled zano_ui to latest commit again-2 --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 20f9219e..93802cb8 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 20f9219e5f782e019983680791a59b79701e0081 +Subproject commit 93802cb8fe1c79daf320aa4b72ba454d27da31d7 From 36578193bcd20300cc4a27de78d88ce6eb9cb39c Mon Sep 17 00:00:00 2001 From: zano build machine Date: Fri, 3 Dec 2021 15:39:16 +0300 Subject: [PATCH 0045/1271] === build number: 137 -> 138 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 79c7c646..04925058 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 137 +#define PROJECT_VERSION_BUILD_NO 138 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From e689f281a3033e4e2493233d9a3f0d3bdd861bb8 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 18 Dec 2021 08:33:41 +0300 Subject: [PATCH 0046/1271] comment for clarification --- src/common/db_abstract_accessor.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/db_abstract_accessor.h b/src/common/db_abstract_accessor.h index 71be14f3..77a5dfdd 100644 --- a/src/common/db_abstract_accessor.h +++ b/src/common/db_abstract_accessor.h @@ -600,6 +600,8 @@ namespace tools bdb.get_backend()->enumerate(m_h, &local_enum_handler); } + // callback format: bool cb(uint64_t index, const key_t& key, const value_t& value) + // cb should return true to continue, false -- to stop enumeration template void enumerate_items(t_cb cb)const { From 8645a9ce2fd76397b339d2580f588b071942a6de Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 18 Dec 2021 08:35:48 +0300 Subject: [PATCH 0047/1271] crypto tests: cn_fast_hash_perf improved --- tests/functional_tests/crypto_tests.cpp | 86 ++++++++++++++++++++----- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 86d00401..eafedbd1 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -959,33 +959,85 @@ TEST(crypto, hp) TEST(crypto, cn_fast_hash_perf) { - return true; - crypto::hash h = { 3, 14, 15, 9, 26 }; + //return true; + const crypto::hash h_initial = *(crypto::hash*)(&scalar_t::random()); - size_t n = 100000; + std::vector> test_data; + test_data.push_back(std::vector(32, 0)); + test_data.push_back(std::vector(63, 0)); + test_data.push_back(std::vector(127, 0)); + test_data.push_back(std::vector(135, 0)); + test_data.push_back(std::vector(255, 0)); + test_data.push_back(std::vector(271, 0)); // 271 = 136 * 2 - 1 + test_data.push_back(std::vector(2030, 0)); + + for (size_t j = 0, sz = test_data.size(); j < sz; ++j) + crypto::generate_random_bytes(test_data[j].size(), test_data[j].data()); + + struct times_t + { + uint64_t t_old{ 0 }, t_new{ 0 }; + crypto::hash h_old{}; + double diff{ 0 }; + }; + std::vector results(test_data.size()); + + size_t n = 50000; double diff_sum = 0; - for (size_t j = 0; j < 20; ++j) + for (size_t k = 0; k < 50; ++k) { - TIME_MEASURE_START(t_old); - for (size_t i = 0; i < n; ++i) - cn_fast_hash_old(&h, sizeof h, (char*)&h); - TIME_MEASURE_FINISH(t_old); + for (size_t j = 0, sz = test_data.size(); j < sz; ++j) + { + crypto::hash h = h_initial; + TIME_MEASURE_START(t_old); + for (size_t i = 0; i < n; ++i) + { + *(crypto::hash*)(test_data[j].data()) = h; + cn_fast_hash_old(test_data[j].data(), test_data[j].size(), (char*)&h); + } + TIME_MEASURE_FINISH(t_old); + results[j].t_old = t_old; + results[j].h_old = h; + } - TIME_MEASURE_START(t); - for (size_t i = 0; i < n; ++i) - cn_fast_hash(&h, sizeof h, (char*)&h); - TIME_MEASURE_FINISH(t); + for (size_t j = 0, sz = test_data.size(); j < sz; ++j) + { + crypto::hash h = h_initial; + TIME_MEASURE_START(t_new); + for (size_t i = 0; i < n; ++i) + { + *(crypto::hash*)(test_data[j].data()) = h; + cn_fast_hash(test_data[j].data(), test_data[j].size(), (char*)&h); + } + TIME_MEASURE_FINISH(t_new); + results[j].t_new = t_new; + ASSERT_EQ(h, results[j].h_old); + } - double diff = ((int64_t)t_old - (int64_t)t) / (double)n; + std::stringstream ss; + double diff_round = 0; + for (size_t j = 0, sz = test_data.size(); j < sz; ++j) + { + double diff = ((int64_t)results[j].t_old - (int64_t)results[j].t_new) / (double)n; - LOG_PRINT_L0("cn_fast_hash (old/new): " << std::fixed << std::setprecision(3) << t_old / (double)n << " " << - std::fixed << std::setprecision(3) << t * 1.0 / n << " mcs diff => " << std::fixed << std::setprecision(4) << diff); + ss << std::fixed << std::setprecision(3) << results[j].t_old / (double)n << "/" << + std::fixed << std::setprecision(3) << results[j].t_new / (double)n << " "; - diff_sum += diff; + results[j].diff += diff; + diff_round += diff; + } + + diff_sum += diff_round; + + LOG_PRINT_L0("cn_fast_hash (old/new) [" << std::setw(2) << k << "]: " << ss.str() << " mcs, diff_round = " << std::fixed << std::setprecision(4) << diff_round << + " diff_sum = " << std::fixed << std::setprecision(4) << diff_sum); } - std::cout << h << " diff sum: " << diff_sum << std::endl; + std::stringstream ss; + for (size_t j = 0, sz = results.size(); j < sz; ++j) + ss << std::fixed << std::setprecision(4) << results[j].diff << " "; + LOG_PRINT_L0(" " << ss.str()); return true; } From 2ca7c556d9b31d0bfcfb38d447ab6b8b33d4258d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 23 Dec 2021 19:41:55 +0100 Subject: [PATCH 0048/1271] deep links: ipc inital implementation --- src/currency_core/currency_config.h | 1 + src/gui/qt-daemon/application/mainwindow.cpp | 63 ++++++++++++++++++++ src/gui/qt-daemon/application/mainwindow.h | 2 + 3 files changed, 66 insertions(+) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 4dfa5e12..29e76ab1 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -212,6 +212,7 @@ #define GUI_SECURE_CONFIG_FILENAME "gui_secure_conf.bin" #define GUI_CONFIG_FILENAME "gui_settings.json" #define GUI_INTERNAL_CONFIG2 "gui_internal_config.json" +#define GUI_IPC_MESSAGE_CHANNEL_NAME CURRENCY_NAME_BASE "_message_que" diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 083c2afd..1fbec761 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -740,6 +740,69 @@ void qt_log_message_handler(QtMsgType type, const QMessageLogContext &context, c } } + + +bool MainWindow::init_ipc_server() +{ + +#define GUI_IPC_BUFFER_SIZE 4000 + try { + //Create a message queue. + std::shared_ptr pmq = new boost::interprocess::message_queue(create_only //only create + , GUI_IPC_MESSAGE_CHANNEL_NAME //name + , 100 //max message number + , GUI_IPC_BUFFER_SIZE //max message size + ); + + m_ipc_worker = std::thread([this, pmq]() + { + //m_ipc_worker; + try + { + unsigned int priority = 0; + boost::interprocess::message_queue::size_type recvd_size = 0; + + while (m_gui_deinitialize_done_1 == false) + { + std::string buff(GUI_IPC_BUFFER_SIZE, ' '); + bool data_received = pmq->timed_receive(buff.data(), GUI_IPC_BUFFER_SIZE, recvd_size, priority, boost::posix_time::ptime(microsec_clock::universal_time()) + boost::posix_time::milliseconds(300)); + if (data_received && recvd_size != 0) + { + //todo process token + } + } + message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + } + catch (const std::exception& ex) + { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_ERROR("Failed to receive IPC que: " << ex.what()); + } + + catch (...) + { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_ERROR("Failed to receive IPC que: unknown exception"); + } + }); + } + catch(const std::exception& ex) + { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_ERROR("Failed to initialize IPC que: " << ex.what()); + return false; + } + + catch (...) + { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_ERROR("Failed to initialize IPC que: unknown exception"); + return false; + } + return true; +} + + bool MainWindow::init_backend(int argc, char* argv[]) { TRY_ENTRY(); diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 57fcf129..5470602d 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -6,10 +6,12 @@ #include #include +#include #include "wallet/view_iface.h" #include "serialization/keyvalue_helper_structs.h" + #ifndef Q_MOC_RUN #include "wallet/wallets_manager.h" #include "currency_core/offers_services_helpers.h" From f2e58daa8c938e3fa577f99fa48afe089c0e9311 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 27 Dec 2021 07:09:02 +0300 Subject: [PATCH 0049/1271] crypto: ge_cached_to_p2 implemented --- src/crypto/crypto-ops.c | 28 ++++++++++++++++------------ src/crypto/crypto-ops.h | 4 +++- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index caf20a07..1233da47 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -325,7 +325,7 @@ Preconditions: |f| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. */ -static int fe_isnegative(const fe f) { +int fe_isnegative(const fe f) { unsigned char s[32]; fe_tobytes(s, f); return s[0] & 1; @@ -342,16 +342,6 @@ int fe_isnonzero(const fe f) { s[27] | s[28] | s[29] | s[30] | s[31]) - 1) >> 8) + 1; } -int fe_cmp(const fe a, const fe b) -{ - for (size_t i = 9; i != SIZE_MAX; --i) - { - if ((const uint32_t)a[i] < (const uint32_t)b[i]) return -1; - if ((const uint32_t)a[i] > (const uint32_t)b[i]) return 1; - } - return 0; -} - /* From fe_mul.c */ /* @@ -970,7 +960,7 @@ Postconditions: |h| bounded by 1.1*2^26,1.1*2^25,1.1*2^26,1.1*2^25,etc. */ -static void fe_sub(fe h, const fe f, const fe g) { +void fe_sub(fe h, const fe f, const fe g) { int32_t f0 = f[0]; int32_t f1 = f[1]; int32_t f2 = f[2]; @@ -4310,3 +4300,17 @@ void ge_scalarmult_vartime_p3_v2(ge_p3 *r, const unsigned char *a, const ge_p3 * ge_p1p1_to_p3(r, &t); } } + + +void ge_cached_to_p2(ge_p2 *r, const ge_cached *c) +{ + static const fe inv2 = { 10, 0, 0, 0, 0, 0, 0, 0, 0, -16777216 }; + + fe_sub(r->X, c->YplusX, c->YminusX); + fe_mul(r->X, r->X, inv2); + + fe_add(r->Y, c->YplusX, c->YminusX); + fe_mul(r->Y, r->Y, inv2); + + fe_copy(r->Z, c->Z); +} diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index 6fb6917c..318af2a2 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -111,6 +111,7 @@ void ge_fromfe_frombytes_vartime(ge_p2 *, const unsigned char *); void ge_p2_to_p3(ge_p3 *r, const ge_p2 *t); void ge_bytes_hash_to_ec(ge_p3 *, const void *, size_t); void ge_bytes_hash_to_ec_32(ge_p3 *, const unsigned char *); +void ge_cached_to_p2(ge_p2 *r, const ge_cached *c); void ge_p3_0(ge_p3 *h); void ge_sub(ge_p1p1 *, const ge_p3 *, const ge_cached *); @@ -138,8 +139,9 @@ void sc_invert(unsigned char*, const unsigned char*); void fe_sq(fe h, const fe f); int fe_isnonzero(const fe f); -int fe_cmp(const fe a, const fe b); +void fe_sub(fe h, const fe f, const fe g); void fe_mul(fe, const fe, const fe); void fe_frombytes(fe h, const unsigned char *s); void fe_invert(fe out, const fe z); void fe_tobytes(unsigned char *s, const fe h); +int fe_isnegative(const fe f); From 286385cc83d2f51908f4cae11c8f2bf6cf5127ca Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 27 Dec 2021 07:10:21 +0300 Subject: [PATCH 0050/1271] crypto sugar: point_t::is_zero fixed, operator!= added --- src/crypto/crypto-sugar.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 600fe914..0e48e949 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -497,7 +497,7 @@ namespace crypto zero(); } - // as we're using additive notation, zero means identity group element here and after + // as we're using additive notation, zero means identity group element (EC point (0, 1)) here and after void zero() { ge_p3_0(&m_p3); @@ -506,7 +506,11 @@ namespace crypto bool is_zero() const { // (0, 1) ~ (0, z, z, 0) - return fe_isnonzero(m_p3.X) * fe_cmp(m_p3.Y, m_p3.Z) == 0; + if (fe_isnonzero(m_p3.X) != 0) + return false; + fe y_minus_z; + fe_sub(y_minus_z, m_p3.Y, m_p3.Z); + return fe_isnonzero(y_minus_z) == 0; } bool is_in_main_subgroup() const @@ -669,6 +673,11 @@ namespace crypto return true; }; + friend bool operator!=(const point_t& lhs, const point_t& rhs) + { + return !(lhs == rhs); + }; + friend std::ostream& operator<<(std::ostream& ss, const point_t &v) { crypto::public_key pk = v.to_public_key(); From 46594092888ce87f1830f67d32182af3f2858062 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 27 Dec 2021 07:13:37 +0300 Subject: [PATCH 0051/1271] crypto_tests: torsion_elements test added --- tests/functional_tests/crypto_tests.cpp | 66 +++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index eafedbd1..eeb748be 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -1674,6 +1674,72 @@ TEST(crypto, calc_lsb_32) return true; } +TEST(crypto, torsion_elements) +{ + // let ty = -sqrt((-sqrt(D+1)-1) / D), is_neg(ty) == false + // canonical serialization sig order EC point + // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05 0 8 (sqrt(-1)*ty, ty) + // 0000000000000000000000000000000000000000000000000000000000000000 0 4 (sqrt(-1), 0) + // c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a 0 8 (sqrt(-1)*ty, -ty) + // ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f 0 2 (0, -1) + // c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa 1 8 (-sqrt(-1)*ty, -ty) + // 0000000000000000000000000000000000000000000000000000000000000080 1 4 (-sqrt(-1), 0) + // 26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85 1 8 (-sqrt(-1)*ty, ty) + + struct canonical_torsion_elements_t + { + const char* string; + bool sign; + uint8_t order; + uint8_t incorrect_order_0; + uint8_t incorrect_order_1; + }; + + canonical_torsion_elements_t canonical_torsion_elements[] = { + {"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc05", false, 8, 4, 7}, + {"0000000000000000000000000000000000000000000000000000000000000000", false, 4, 2, 3}, + {"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac037a", false, 8, 4, 7}, + {"ecffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", false, 2, 1, 3}, + {"c7176a703d4dd84fba3c0b760d10670f2a2053fa2c39ccc64ec7fd7792ac03fa", true, 8, 4, 7}, + {"0000000000000000000000000000000000000000000000000000000000000080", true, 4, 2, 3}, + {"26e8958fc2b227b045c3f489f2ef98f0d5dfac05d3c63339b13802886d53fc85", true, 8, 4, 7} + }; + + point_t tor; + + for (size_t i = 0, n = sizeof canonical_torsion_elements / sizeof canonical_torsion_elements[0]; i < n; ++i) + { + const canonical_torsion_elements_t& el = canonical_torsion_elements[i]; + ASSERT_TRUE(tor.from_string(el.string)); + ASSERT_FALSE(tor.is_zero()); + ASSERT_FALSE(tor.is_in_main_subgroup()); + + ASSERT_EQ((fe_isnegative(tor.m_p3.X) != 0), el.sign); + + ASSERT_FALSE(el.incorrect_order_0 * tor == c_point_0); + ASSERT_FALSE(el.incorrect_order_1 * tor == c_point_0); + ASSERT_TRUE(el.order * tor == c_point_0); + } + + // non-canonical elements should not load at all (thanks to the checks in ge_frombytes_vartime) + + const char* noncanonical_torsion_elements[] = { + "0100000000000000000000000000000000000000000000000000000000000080", // (-0, 1) + "ECFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // (-0, -1) + "EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F", // (0, 2*255-18) + "EEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", // (-0, 2*255-18) + "EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7F", // (sqrt(-1), 2*255-19) + "EDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" // (-sqrt(-1), 2*255-19) + }; + + for (size_t i = 0, n = sizeof noncanonical_torsion_elements / sizeof noncanonical_torsion_elements[0]; i < n; ++i) + { + ASSERT_FALSE(tor.from_string(noncanonical_torsion_elements[i])); + } + + return true; +} + // // test's runner // From bdfc14e90dea272135057cb87c4d4600efd7fae4 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 30 Dec 2021 16:41:33 +0100 Subject: [PATCH 0052/1271] Create deeplink.rm --- utils/deeplink.rm | 1 + 1 file changed, 1 insertion(+) create mode 100644 utils/deeplink.rm diff --git a/utils/deeplink.rm b/utils/deeplink.rm new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/utils/deeplink.rm @@ -0,0 +1 @@ + From ca8ce3c184daac3323a4929728d917f9b236b562 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 30 Dec 2021 17:30:46 +0100 Subject: [PATCH 0053/1271] deeplinks implementation: proper routing --- src/common/command_line.h | 1 + src/connectivity_tool/conn_tool.cpp | 35 ++++++++++++ src/gui/qt-daemon/application/mainwindow.cpp | 59 +++++++++++++++++--- src/gui/qt-daemon/application/mainwindow.h | 5 ++ src/wallet/wallets_manager.cpp | 2 + src/wallet/wallets_manager.h | 2 + 6 files changed, 96 insertions(+), 8 deletions(-) diff --git a/src/common/command_line.h b/src/common/command_line.h index ee7985cc..d7c44207 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -224,4 +224,5 @@ namespace command_line extern const arg_descriptor arg_force_predownload; extern const arg_descriptor arg_validate_predownload; extern const arg_descriptor arg_predownload_link; + extern const arg_descriptor arg_deeplink; } diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index b14125ad..b053011e 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -12,6 +12,7 @@ using namespace epee; #include +#include #include "p2p/p2p_protocol_defs.h" #include "common/command_line.h" #include "currency_core/currency_core.h" @@ -62,6 +63,7 @@ namespace const command_line::arg_descriptor arg_pack_file = {"pack-file", "perform gzip-packing and calculate hash for a given file", "", true }; const command_line::arg_descriptor arg_unpack_file = {"unpack-file", "Perform gzip-unpacking and calculate hash for a given file", "", true }; const command_line::arg_descriptor arg_target_file = {"target-file", "Specify target file for pack-file and unpack-file commands", "", true }; + const command_line::arg_descriptor arg_send_ipc = {"send-ipc", "Send IPC request to UI", "", true }; } typedef COMMAND_REQUEST_STAT_INFO_T::stat_info> COMMAND_REQUEST_STAT_INFO; @@ -1165,6 +1167,33 @@ bool process_archive(archive_processor_t& arch_processor, bool is_packing, std:: return true; } + +bool handle_send_ipc(const std::string& parms) +{ + try{ + boost::interprocess::message_queue mq + (boost::interprocess::open_only //only open + , GUI_IPC_MESSAGE_CHANNEL_NAME //name + ); + + mq.send(parms.data(), parms.size(), 0); + + return true; + } + catch (const std::exception& ex) + { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_ERROR("Failed to receive IPC que: " << ex.what()); + } + + catch (...) + { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_ERROR("Failed to receive IPC que: unknown exception"); + } + return false; +} + bool handle_pack_file(po::variables_map& vm) { bool do_pack = false; @@ -1263,6 +1292,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_pack_file); command_line::add_arg(desc_params, arg_unpack_file); command_line::add_arg(desc_params, arg_target_file); + command_line::add_arg(desc_params, arg_send_ipc); + po::options_description desc_all; desc_all.add(desc_general).add(desc_params); @@ -1339,6 +1370,10 @@ int main(int argc, char* argv[]) { return handle_pack_file(vm) ? EXIT_SUCCESS : EXIT_FAILURE; } + else if (command_line::has_arg(vm, arg_send_ipc)) + { + handle_send_ipc(command_line::get_arg(vm, arg_send_ipc)) ? EXIT_SUCCESS : EXIT_FAILURE; + } else { std::cerr << "Not enough arguments." << ENDL; diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 1fbec761..3013966f 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -121,6 +121,10 @@ MainWindow::~MainWindow() delete m_channel; m_channel = nullptr; } + if (m_ipc_worker.joinable()) + { + m_ipc_worker.join(); + } } void MainWindow::on_load_finished(bool ok) @@ -745,14 +749,14 @@ void qt_log_message_handler(QtMsgType type, const QMessageLogContext &context, c bool MainWindow::init_ipc_server() { -#define GUI_IPC_BUFFER_SIZE 4000 +#define GUI_IPC_BUFFER_SIZE 10000 try { //Create a message queue. - std::shared_ptr pmq = new boost::interprocess::message_queue(create_only //only create + std::shared_ptr pmq(new boost::interprocess::message_queue(boost::interprocess::create_only //only create , GUI_IPC_MESSAGE_CHANNEL_NAME //name , 100 //max message number - , GUI_IPC_BUFFER_SIZE //max message size - ); + , GUI_IPC_BUFFER_SIZE //max message size + )); m_ipc_worker = std::thread([this, pmq]() { @@ -765,13 +769,14 @@ bool MainWindow::init_ipc_server() while (m_gui_deinitialize_done_1 == false) { std::string buff(GUI_IPC_BUFFER_SIZE, ' '); - bool data_received = pmq->timed_receive(buff.data(), GUI_IPC_BUFFER_SIZE, recvd_size, priority, boost::posix_time::ptime(microsec_clock::universal_time()) + boost::posix_time::milliseconds(300)); + bool data_received = pmq->timed_receive((void*)buff.data(), GUI_IPC_BUFFER_SIZE, recvd_size, priority, boost::posix_time::ptime(boost::posix_time::microsec_clock::universal_time()) + boost::posix_time::milliseconds(1000)); if (data_received && recvd_size != 0) { - //todo process token + handle_ipc_event(buff);//todo process token } - } - message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + } + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + LOG_PRINT_L0("IPC Handling thread finished"); } catch (const std::exception& ex) { @@ -803,6 +808,32 @@ bool MainWindow::init_ipc_server() } +bool MainWindow::handle_ipc_event(const std::string& arguments) +{ + std::string zzz = "Received IPC: " + arguments; + message_box(zzz.c_str()); + + handle_deeplink_click(arguments.c_str()); + + return true; +} + +bool MainWindow::handle_deeplink_params_in_commandline() +{ + std::string deep_link_params = command_line::get_arg(m_backend.get_arguments(), command_line::arg_deeplink); + + try { + boost::interprocess::message_queue mq(boost::interprocess::open_only, GUI_IPC_MESSAGE_CHANNEL_NAME); + mq.send(deep_link_params.data(), deep_link_params.size(), 0); + return false; + } + catch (...) + { + //ui not launched yet + return true; + } +} + bool MainWindow::init_backend(int argc, char* argv[]) { TRY_ENTRY(); @@ -813,6 +844,12 @@ bool MainWindow::init_backend(int argc, char* argv[]) return false; } + if (command_line::has_arg(m_backend.get_arguments(), command_line::arg_deeplink)) + { + if (!handle_deeplink_params_in_commandline()) + return false; + } + if (!init_window()) { this->show_msg_box("Failed to main screen launch, check logs for the more detais."); @@ -833,6 +870,12 @@ bool MainWindow::init_backend(int argc, char* argv[]) QLoggingCategory::setFilterRules("*=true"); // enable all logs } + if (!init_ipc_server()) + { + this->show_msg_box("Failed to initialize IP server, check debug logs for more details."); + return false; + } + return true; CATCH_ENTRY2(false); } diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 5470602d..eb56627c 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -60,6 +60,7 @@ public: bool init_backend(int argc, char* argv[]); bool show_inital(); void show_notification(const std::string& title, const std::string& message); + bool handle_ipc_event(const std::string& arguments); struct app_config { @@ -194,6 +195,7 @@ signals: void on_core_event(const QString method_name); //general function void set_options(const QString str); //general function void get_wallet_name(); + void handle_deeplink_click(const QString str); private: //-------------------- i_core_event_handler -------------------- @@ -236,6 +238,8 @@ private: bool store_app_config(); bool load_app_config(); bool init_window(); + bool init_ipc_server(); + std::string get_wallet_log_prefix(size_t wallet_id) const { return m_backend.get_wallet_log_prefix(wallet_id); } @@ -258,6 +262,7 @@ private: app_config m_config; epee::locked_object> m_wallet_states; + std::thread m_ipc_worker; struct events_que_struct { std::list m_que; diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 2beb7c2f..a27d1744 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -183,6 +183,7 @@ bool wallets_manager::init_command_line(int argc, char* argv[], std::string& fai command_line::add_arg(desc_cmd_sett, command_line::arg_force_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_validate_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_predownload_link); + command_line::add_arg(desc_cmd_sett, command_line::arg_deeplink); #ifndef MOBILE_WALLET_BUILD @@ -247,6 +248,7 @@ bool wallets_manager::init_command_line(int argc, char* argv[], std::string& fai m_qt_logs_enbaled = command_line::get_arg(m_vm, arg_enable_qt_logs); m_qt_dev_tools = command_line::get_arg(m_vm, arg_qt_dev_tools); + return true; CATCH_ENTRY2(false); } diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 10d6a103..ac64041c 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -97,6 +97,8 @@ public: bool quick_clear_wallets_no_save(); bool send_stop_signal(); bool get_opened_wallets(std::list& result); + const po::variables_map& get_arguments(); + std::string open_wallet(const std::wstring& path, const std::string& password, uint64_t txs_to_return, view::open_wallet_response& owr, bool exclude_mining_txs = false); std::string generate_wallet(const std::wstring& path, const std::string& password, view::open_wallet_response& owr); std::string restore_wallet(const std::wstring& path, const std::string& password, const std::string& seed_phrase, const std::string& seed_password, view::open_wallet_response& owr); From a623fc1506a9b25cdea63a378b01cf48a525fe12 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 30 Dec 2021 17:32:50 +0100 Subject: [PATCH 0054/1271] Fixed #316 --- contrib/epee/include/time_helper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/epee/include/time_helper.h b/contrib/epee/include/time_helper.h index 40811420..c2b439e2 100644 --- a/contrib/epee/include/time_helper.h +++ b/contrib/epee/include/time_helper.h @@ -87,7 +87,7 @@ DISABLE_VS_WARNINGS(4996) POP_VS_WARNINGS if(pt) - strftime( tmpbuf, 199, "%Y-%m-%d %H-%M-%S", pt ); + strftime( tmpbuf, 199, "%Y-%m-%d %H:%M:%S", pt ); else { std::stringstream strs; From 10e0137d0e403a64ac2e89f134060d737f356619 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 30 Dec 2021 17:39:57 +0100 Subject: [PATCH 0055/1271] added deeplinks commandline argument --- src/common/command_line.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 92a59b61..244fe5e1 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -39,4 +39,6 @@ namespace command_line const arg_descriptor arg_validate_predownload = { "validate-predownload", "Paranoid mode, re-validate each block from pre-downloaded database and rebuild own database", }; const arg_descriptor arg_predownload_link = { "predownload-link", "Override url for blockchain database pre-downloading", "", true }; + const arg_descriptor arg_deeplink = { "deeplink-params", "Deeplink parameter, in that case app just forward params to running app", "", true }; + } From 9ea35d3a2f6da82782ea7218e197e6d03ca0e66f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 30 Dec 2021 19:17:22 +0100 Subject: [PATCH 0056/1271] deeplinks ready on application level --- src/gui/qt-daemon/application/mainwindow.cpp | 9 ++++++++- src/gui/qt-daemon/application/mainwindow.h | 2 +- src/wallet/wallets_manager.cpp | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 3013966f..0c0ce687 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -836,6 +836,13 @@ bool MainWindow::handle_deeplink_params_in_commandline() bool MainWindow::init_backend(int argc, char* argv[]) { + if (argc > 1) + { + std::string msg = "Command_line 1: "; + msg += argv[1]; + message_box(msg.c_str()); + } + TRY_ENTRY(); std::string command_line_fail_details; if (!m_backend.init_command_line(argc, argv, command_line_fail_details)) @@ -872,7 +879,7 @@ bool MainWindow::init_backend(int argc, char* argv[]) if (!init_ipc_server()) { - this->show_msg_box("Failed to initialize IP server, check debug logs for more details."); + this->show_msg_box("Failed to initialize IPC server, check debug logs for more details."); return false; } diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index eb56627c..bea8c63f 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -224,7 +224,7 @@ private: void contextMenuEvent(QContextMenuEvent * event); void changeEvent(QEvent *e); void on_maximized(); - + bool handle_deeplink_params_in_commandline(); //void setOrientation(Qt::ScreenOrientation orientation); diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index a27d1744..be508df3 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -990,6 +990,11 @@ bool wallets_manager::get_opened_wallets(std::list& return true; } +const po::variables_map& wallets_manager::get_arguments() +{ + return m_vm; +} + std::string wallets_manager::get_recent_transfers(size_t wallet_id, uint64_t offset, uint64_t count, view::transfers_array& tr_hist, bool exclude_mining_txs) { GET_WALLET_BY_ID(wallet_id, w); From 140f43bc5e95a9ee3f3d4bd2f5be38d38d651482 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 31 Dec 2021 06:44:26 +0300 Subject: [PATCH 0057/1271] crypto tests: point_is_zero test added --- src/crypto/crypto-sugar.h | 2 +- .../currency_format_utils_abstract.h | 1 + tests/functional_tests/crypto_tests.cpp | 63 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 0e48e949..5542b9fb 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -246,7 +246,7 @@ namespace crypto return result; } - // genrate 0 <= x < L + // generate 0 <= x < L void make_random() { unsigned char tmp[64]; diff --git a/src/currency_core/currency_format_utils_abstract.h b/src/currency_core/currency_format_utils_abstract.h index 451b46e3..611511fc 100644 --- a/src/currency_core/currency_format_utils_abstract.h +++ b/src/currency_core/currency_format_utils_abstract.h @@ -129,6 +129,7 @@ namespace currency } return found; } + //--------------------------------------------------------------- inline const txin_to_key& get_to_key_input_from_txin_v(const txin_v& in_v) { diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index eeb748be..83c94b8b 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -1740,6 +1740,69 @@ TEST(crypto, torsion_elements) return true; } +TEST(crypto, point_is_zero) +{ + static const fe fancy_p = { -19, 33554432, -1, 33554432, -1, 33554432, -1, 33554432, -1, 33554432 }; // 2**255 - 19 + static const fe fancy_p_plus_1 = { -18, 33554432, -1, 33554432, -1, 33554432, -1, 33554432, -1, 33554432 }; // 2**255 - 18 + static const fe f_one = { 1 }; + + ASSERT_TRUE(fe_isnonzero(fancy_p) == 0); + ASSERT_TRUE(fe_isnonzero(fancy_p_plus_1) != 0); + + fe f_r, f_x; + fe_frombytes(f_x, scalar_t::random().data()); + fe_mul(f_r, f_x, fancy_p); + ASSERT_TRUE(fe_isnonzero(f_r) == 0); + + fe_sub(f_r, fancy_p_plus_1, f_one); + ASSERT_TRUE(fe_isnonzero(f_r) == 0); + + // is_zero + + point_t p; + memset(&p.m_p3, 0, sizeof p.m_p3); + memcpy(&p.m_p3.X, fancy_p, sizeof p.m_p3.X); // X = 2**255-19 + memcpy(&p.m_p3.Y, fancy_p_plus_1, sizeof p.m_p3.Y); // Y = 2**255-19+1 + p.m_p3.Z[0] = 1; + // {P, P+1, 1, 0} == {0, 1} (the identity point) + + ASSERT_TRUE(p.is_zero()); + + + memset(&p.m_p3, 0, sizeof p.m_p3); + memcpy(&p.m_p3.X, fancy_p, sizeof p.m_p3.X); // X = 2**255-19 + memcpy(&p.m_p3.Y, fancy_p_plus_1, sizeof p.m_p3.Y); // Y = 2**255-19+1 + p.m_p3.Z[0] = -1; + // {P, P+1, -1, 0} == {0, -1} (not an identity point, torsion element order 2) + + ASSERT_FALSE(p.is_zero()); + + memset(&p.m_p3, 0, sizeof p.m_p3); + p.m_p3.Y[0] = 2; + p.m_p3.Z[0] = 2; + // {0, 2, 2, 0} == {0, 1} (the identity point) + + ASSERT_TRUE(p.is_zero()); + + // all fe 10 components must be in [-33554432, 33554432] (curve25519-20060209.pdf page 9) + // 2**0 2**26 2**51 2**77 2**102 2**128 2**153 2**179 2**204 2*230 + fe a0 = { 7172245, 16777211, 922265, 8160646, 9625798, -12989394, 10843498, 6987154, 15156548, -5214544 }; + fe a1 = { 7172245, -16777221, 922266, 8160646, 9625798, -12989394, 10843498, 6987154, 15156548, -5214544 }; + // note, a0 == a1: + // 16777211 * 2**26 + 922265 * 2**51 = 2076757281067996545024 + // -16777221 * 2**26 + 922266 * 2**51 = 2076757281067996545024 + + memset(&p.m_p3, 0, sizeof p.m_p3); + memcpy(&p.m_p3.Y, &a0, sizeof a0); + memcpy(&p.m_p3.Z, &a1, sizeof a1); + // {0, x, x, 0} == {0, 1, 1, 0} == {0, 1} (the identity point) + + ASSERT_TRUE(p.is_zero()); + + return true; +} + + // // test's runner // From ca6c7e81e1374081b58cee37f1f9e735bb4696ee Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 31 Dec 2021 06:47:22 +0300 Subject: [PATCH 0058/1271] #316 re-implemented using get_time_str instead of get_time_str2 --- contrib/epee/include/time_helper.h | 2 +- src/wallet/wallet2.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/epee/include/time_helper.h b/contrib/epee/include/time_helper.h index c2b439e2..40811420 100644 --- a/contrib/epee/include/time_helper.h +++ b/contrib/epee/include/time_helper.h @@ -87,7 +87,7 @@ DISABLE_VS_WARNINGS(4996) POP_VS_WARNINGS if(pt) - strftime( tmpbuf, 199, "%Y-%m-%d %H:%M:%S", pt ); + strftime( tmpbuf, 199, "%Y-%m-%d %H-%M-%S", pt ); else { std::stringstream strs; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 5c9028f2..cbceb80b 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3258,7 +3258,7 @@ void wallet2::get_recent_transfers_history(std::vector Date: Tue, 4 Jan 2022 05:14:11 +0000 Subject: [PATCH 0059/1271] Add --disable-ntp parameter (#317) * Add disable-ntp parameter * Also allow GUI to disable NTP * Update src/currency_protocol/currency_protocol_handler.inl Co-authored-by: crypto.sowle --- src/common/command_line.cpp | 1 + src/common/command_line.h | 1 + src/currency_protocol/currency_protocol_handler.h | 1 + src/currency_protocol/currency_protocol_handler.inl | 9 +++++++++ src/daemon/daemon.cpp | 1 + src/wallet/wallets_manager.cpp | 1 + 6 files changed, 14 insertions(+) diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 244fe5e1..6595763f 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -28,6 +28,7 @@ namespace command_line const arg_descriptor arg_show_rpc_autodoc = { "show_rpc_autodoc", "Display rpc auto-generated documentation template" }; const arg_descriptor arg_disable_upnp = { "disable-upnp", "Disable UPnP (enhances local network privacy)", false, true }; + const arg_descriptor arg_disable_ntp = { "disable-ntp", "Disable NTP, could enhance to time synchronization issue but increase network privacy, consider using disable-stop-if-time-out-of-sync with it", false, true }; const arg_descriptor arg_disable_stop_if_time_out_of_sync = { "disable-stop-if-time-out-of-sync", "Do not stop the daemon if serious time synchronization problem is detected", false, true }; const arg_descriptor arg_disable_stop_on_low_free_space = { "disable-stop-on-low-free-space", "Do not stop the daemon if free space at data dir is critically low", false, true }; diff --git a/src/common/command_line.h b/src/common/command_line.h index d7c44207..652d1777 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -216,6 +216,7 @@ namespace command_line extern const arg_descriptor arg_show_details; extern const arg_descriptor arg_show_rpc_autodoc; extern const arg_descriptor arg_disable_upnp; + extern const arg_descriptor arg_disable_ntp; extern const arg_descriptor arg_disable_stop_if_time_out_of_sync; extern const arg_descriptor arg_disable_stop_on_low_free_space; extern const arg_descriptor arg_enable_offers_service; diff --git a/src/currency_protocol/currency_protocol_handler.h b/src/currency_protocol/currency_protocol_handler.h index eaf06bda..cae17241 100644 --- a/src/currency_protocol/currency_protocol_handler.h +++ b/src/currency_protocol/currency_protocol_handler.h @@ -117,6 +117,7 @@ namespace currency int64_t m_last_median2local_time_difference; int64_t m_last_ntp2local_time_difference; uint32_t m_debug_ip_address; + bool m_disable_ntp; template bool post_notify(typename t_parametr::request& arg, currency_connection_context& context) diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index 39e42a3c..03b93b67 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -23,6 +23,7 @@ namespace currency , m_last_median2local_time_difference(0) , m_last_ntp2local_time_difference(0) , m_debug_ip_address(0) + , m_disable_ntp(false) { if(!m_p2p) m_p2p = &m_p2p_stub; @@ -38,6 +39,8 @@ namespace currency bool t_currency_protocol_handler::init(const boost::program_options::variables_map& vm) { m_relay_que_thread = std::thread([this](){relay_que_worker();}); + if (command_line::has_arg(vm, command_line::arg_disable_ntp)) + m_disable_ntp = command_line::get_arg(vm, command_line::arg_disable_ntp); return true; } //------------------------------------------------------------------------------------------------------------------------ @@ -834,6 +837,12 @@ namespace currency LOG_PRINT_MAGENTA("TIME: network time difference is " << m_last_median2local_time_difference << " (max is " << TIME_SYNC_DELTA_TO_LOCAL_MAX_DIFFERENCE << ")", ((m_last_median2local_time_difference >= 3) ? LOG_LEVEL_2 : LOG_LEVEL_3)); if (std::abs(m_last_median2local_time_difference) > TIME_SYNC_DELTA_TO_LOCAL_MAX_DIFFERENCE) { + // treat as error getting ntp time + if (m_disable_ntp) + { + LOG_PRINT_RED("TIME: network time difference is " << m_last_median2local_time_difference << " (max is " << TIME_SYNC_DELTA_TO_LOCAL_MAX_DIFFERENCE << ") while NTP is disabled", LOG_LEVEL_0); + return false; + } int64_t ntp_time = tools::get_ntp_time(); LOG_PRINT_L2("NTP: received time " << ntp_time << " (" << epee::misc_utils::get_time_str_v2(ntp_time) << "), diff: " << std::showpos << get_core_time() - ntp_time); if (ntp_time == 0) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 8cbaf4f1..889c1c55 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -164,6 +164,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_cmd_sett, command_line::arg_force_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_validate_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_predownload_link); + command_line::add_arg(desc_cmd_sett, command_line::arg_disable_ntp); arg_market_disable.default_value = true; diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index be508df3..9171a27f 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -184,6 +184,7 @@ bool wallets_manager::init_command_line(int argc, char* argv[], std::string& fai command_line::add_arg(desc_cmd_sett, command_line::arg_validate_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_predownload_link); command_line::add_arg(desc_cmd_sett, command_line::arg_deeplink); + command_line::add_arg(desc_cmd_sett, command_line::arg_disable_ntp); #ifndef MOBILE_WALLET_BUILD From 271e3eb99e4c0ae9f301516c024f1e5e0f2352d2 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 5 Jan 2022 19:32:26 +0100 Subject: [PATCH 0060/1271] deeplinks: added click even forwarding on macos --- src/currency_core/offers_service_basics.h | 2 ++ src/gui/qt-daemon/application/urleventfilter.cpp | 7 ++++--- src/gui/qt-daemon/application/urleventfilter.h | 7 ++++++- src/gui/qt-daemon/main.cpp | 10 +++++----- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/currency_core/offers_service_basics.h b/src/currency_core/offers_service_basics.h index 16d2990a..809bf88b 100644 --- a/src/currency_core/offers_service_basics.h +++ b/src/currency_core/offers_service_basics.h @@ -32,6 +32,7 @@ namespace bc_services std::string payment_types; // []money accept type(bank transaction, internet money, cash, etc) std::string deal_option; // []full amount, by parts std::string category; // [] + std::string preview_url; // [] uint8_t expiration_time; // n-days //----------------- @@ -50,6 +51,7 @@ namespace bc_services KV_SERIALIZE_N(deal_option, "do") KV_SERIALIZE_N(category, "cat") KV_SERIALIZE_N(expiration_time, "et") + KV_SERIALIZE_N(preview_url, "url") END_KV_SERIALIZE_MAP() }; diff --git a/src/gui/qt-daemon/application/urleventfilter.cpp b/src/gui/qt-daemon/application/urleventfilter.cpp index d7e4e35e..3fb1b24d 100644 --- a/src/gui/qt-daemon/application/urleventfilter.cpp +++ b/src/gui/qt-daemon/application/urleventfilter.cpp @@ -7,9 +7,10 @@ bool URLEventFilter::eventFilter(QObject *obj, QEvent *event) QFileOpenEvent *fileEvent = static_cast(event); if(!fileEvent->url().isEmpty()) { - QMessageBox msg; - msg.setText(fileEvent->url().toString()); - msg.exec(); + m_pmainwindow->handle_deeplink_click(fileEvent->url()); + //QMessageBox msg; + //msg.setText(fileEvent->url().toString()); + //msg.exec(); } } else { // standard event processing diff --git a/src/gui/qt-daemon/application/urleventfilter.h b/src/gui/qt-daemon/application/urleventfilter.h index 54341dbd..1e3e0dc2 100644 --- a/src/gui/qt-daemon/application/urleventfilter.h +++ b/src/gui/qt-daemon/application/urleventfilter.h @@ -1,12 +1,17 @@ #include #include #include +#include "mainwindow.h" class URLEventFilter : public QObject { Q_OBJECT public: - URLEventFilter() : QObject(){}; + URLEventFilter(MainWindow* pmainwindow) : m_pmainwindow(pmainwindow),QObject() + {}; protected: bool eventFilter(QObject *obj, QEvent *event) override; + +private: + MainWindow* m_pmainwindow; }; \ No newline at end of file diff --git a/src/gui/qt-daemon/main.cpp b/src/gui/qt-daemon/main.cpp index f32dde0f..39ecae9d 100644 --- a/src/gui/qt-daemon/main.cpp +++ b/src/gui/qt-daemon/main.cpp @@ -66,11 +66,6 @@ int main(int argc, char *argv[]) QApplication app(argc, argv); - -#ifdef Q_OS_DARWIN - URLEventFilter url_event_filter; - app.installEventFilter(&url_event_filter); -#endif MainWindow viewer; if (!viewer.init_backend(argc, argv)) @@ -78,6 +73,11 @@ int main(int argc, char *argv[]) return 1; } +#ifdef Q_OS_DARWIN + URLEventFilter url_event_filter(&viewer); + app.installEventFilter(&url_event_filter); +#endif + app.installNativeEventFilter(&viewer); viewer.setWindowTitle(CURRENCY_NAME_BASE); viewer.show_inital(); From 5eb356a91d7a40f16df1dcb4e749dcdaa8993bb1 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Wed, 5 Jan 2022 21:42:49 +0300 Subject: [PATCH 0061/1271] === build number: 138 -> 139 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 04925058..a0f77b84 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 138 +#define PROJECT_VERSION_BUILD_NO 139 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 049a8980587ba27c1785bbbf32a05594ba7199d8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 5 Jan 2022 20:13:30 +0100 Subject: [PATCH 0062/1271] Fix for linux build agains IPC integration --- src/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 807d2a16..a044fab8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -196,6 +196,9 @@ if(BUILD_GUI) find_package(Qt5PrintSupport REQUIRED) target_link_libraries(Zano wallet rpc currency_core crypto common zlibstatic ethash Qt5::WebEngineWidgets Qt5::PrintSupport ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) + if (UNIX AND NOT APPLE) + target_link_libraries(Zano rt) + endif() if(APPLE) target_link_libraries(Zano ${COCOA_LIBRARY}) From a5c8fe78acd4c81e32a7d923ad7cf55f1be342da Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 5 Jan 2022 20:32:56 +0100 Subject: [PATCH 0063/1271] disabled IPC for connectivity tool --- src/connectivity_tool/conn_tool.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index b053011e..4b8c1fab 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -12,7 +12,7 @@ using namespace epee; #include -#include +//#include #include "p2p/p2p_protocol_defs.h" #include "common/command_line.h" #include "currency_core/currency_core.h" @@ -1167,7 +1167,7 @@ bool process_archive(archive_processor_t& arch_processor, bool is_packing, std:: return true; } - +/* bool handle_send_ipc(const std::string& parms) { try{ @@ -1193,6 +1193,7 @@ bool handle_send_ipc(const std::string& parms) } return false; } +*/ bool handle_pack_file(po::variables_map& vm) { From 37907e108a5151a18c6cd37a715bf25d39e8b3f7 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 5 Jan 2022 20:59:58 +0100 Subject: [PATCH 0064/1271] disabled IPC for connectivity tool-2 --- src/connectivity_tool/conn_tool.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index 4b8c1fab..d1f2115b 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -63,7 +63,7 @@ namespace const command_line::arg_descriptor arg_pack_file = {"pack-file", "perform gzip-packing and calculate hash for a given file", "", true }; const command_line::arg_descriptor arg_unpack_file = {"unpack-file", "Perform gzip-unpacking and calculate hash for a given file", "", true }; const command_line::arg_descriptor arg_target_file = {"target-file", "Specify target file for pack-file and unpack-file commands", "", true }; - const command_line::arg_descriptor arg_send_ipc = {"send-ipc", "Send IPC request to UI", "", true }; + //const command_line::arg_descriptor arg_send_ipc = {"send-ipc", "Send IPC request to UI", "", true }; } typedef COMMAND_REQUEST_STAT_INFO_T::stat_info> COMMAND_REQUEST_STAT_INFO; @@ -1293,7 +1293,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_pack_file); command_line::add_arg(desc_params, arg_unpack_file); command_line::add_arg(desc_params, arg_target_file); - command_line::add_arg(desc_params, arg_send_ipc); + //command_line::add_arg(desc_params, arg_send_ipc); po::options_description desc_all; @@ -1371,10 +1371,10 @@ int main(int argc, char* argv[]) { return handle_pack_file(vm) ? EXIT_SUCCESS : EXIT_FAILURE; } - else if (command_line::has_arg(vm, arg_send_ipc)) + /*else if (command_line::has_arg(vm, arg_send_ipc)) { handle_send_ipc(command_line::get_arg(vm, arg_send_ipc)) ? EXIT_SUCCESS : EXIT_FAILURE; - } + }*/ else { std::cerr << "Not enough arguments." << ENDL; From 7478b48a5684315f50177bfedcff01711c2126f4 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 5 Jan 2022 21:21:35 +0100 Subject: [PATCH 0065/1271] fixed problem with missing pragma once --- src/gui/qt-daemon/application/mainwindow.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index bea8c63f..974b0bea 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -4,6 +4,7 @@ // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once #include #include #include From e113152cc52384c4f1a976b716c7e28bc0aa92fc Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 6 Jan 2022 16:41:25 +0100 Subject: [PATCH 0066/1271] fixed compilation errors in url filter --- src/gui/qt-daemon/application/urleventfilter.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/application/urleventfilter.cpp b/src/gui/qt-daemon/application/urleventfilter.cpp index 3fb1b24d..910b10f4 100644 --- a/src/gui/qt-daemon/application/urleventfilter.cpp +++ b/src/gui/qt-daemon/application/urleventfilter.cpp @@ -7,11 +7,13 @@ bool URLEventFilter::eventFilter(QObject *obj, QEvent *event) QFileOpenEvent *fileEvent = static_cast(event); if(!fileEvent->url().isEmpty()) { - m_pmainwindow->handle_deeplink_click(fileEvent->url()); + m_pmainwindow->handle_deeplink_click(fileEvent->url().toString()); //QMessageBox msg; //msg.setText(fileEvent->url().toString()); //msg.exec(); + return true; } + return true; } else { // standard event processing return QObject::eventFilter(obj, event); From 8a0e7b33777edca371f37f992c7fff05293fb6dd Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 6 Jan 2022 20:21:39 +0100 Subject: [PATCH 0067/1271] added tor-connect library to project tree(as submodule) --- .gitmodules | 3 +++ contrib/tor-connect | 1 + 2 files changed, 4 insertions(+) create mode 160000 contrib/tor-connect diff --git a/.gitmodules b/.gitmodules index 3e3d7e2c..09642600 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "src/gui/qt-daemon/layout"] path = src/gui/qt-daemon/layout url = https://github.com/hyle-team/zano_ui.git +[submodule "contrib/tor-connect"] + path = contrib/tor-connect + url = git@github.com:hyle-team/tor-connect.git diff --git a/contrib/tor-connect b/contrib/tor-connect new file mode 160000 index 00000000..c8781e5b --- /dev/null +++ b/contrib/tor-connect @@ -0,0 +1 @@ +Subproject commit c8781e5b498e23860e6b7bd589a1ac0f16df4a14 From 70780ec62d2b77eb45d6319ba162fa7d5c5452a1 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 6 Jan 2022 20:38:22 +0100 Subject: [PATCH 0068/1271] upgraded tor-connect --- contrib/CMakeLists.txt | 2 ++ contrib/tor-connect | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 779876a1..23ceb7a1 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -4,6 +4,7 @@ set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Build test executables") add_subdirectory(zlib) add_subdirectory(db) add_subdirectory(ethereum) +add_subdirectory(tor-connect) if(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "Android") @@ -17,6 +18,7 @@ set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") set_property(TARGET mdbx PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") +set_property(TARGET tor-connect PROPERTY FOLDER "contrib") set_property(TARGET upnpc-static mdbx_chk mdbx_copy mdbx_dump mdbx_load mdbx_stat PROPERTY FOLDER "unused") if(MSVC) diff --git a/contrib/tor-connect b/contrib/tor-connect index c8781e5b..08eefb81 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit c8781e5b498e23860e6b7bd589a1ac0f16df4a14 +Subproject commit 08eefb8102c72f15766f5bedf39dfc41cb0d7a0b From 1a55d65e85f48754f25e024c41dc520d0664dae2 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 8 Jan 2022 16:36:33 +0100 Subject: [PATCH 0069/1271] first implementations for tor: relaying transactions over p2p network protocol --- .../currency_protocol_handler.inl | 5 ++++- src/wallet/wallet2.cpp | 22 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index 03b93b67..62e66448 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -375,8 +375,11 @@ namespace currency if (m_debug_ip_address != 0 && context.m_remote_ip == m_debug_ip_address) return 1; - if(context.m_state != currency_connection_context::state_normal) + //if(context.m_state != currency_connection_context::state_normal) + // return 1; + if (!this->is_synchronized()) return 1; + uint64_t inital_tx_count = arg.txs.size(); TIME_MEASURE_START_MS(new_transactions_handle_time); for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end();) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index cbceb80b..466a707e 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -13,6 +13,7 @@ #include #include #include "include_base_utils.h" +#include "net/levin_client.h" using namespace epee; #include "string_coding.h" @@ -32,6 +33,8 @@ using namespace epee; #include "crypto/bitcoin/sha256_helper.h" using namespace currency; + + #define MINIMUM_REQUIRED_WALLET_FREE_SPACE_BYTES (100*1024*1024) // 100 MB #undef LOG_DEFAULT_CHANNEL @@ -4585,6 +4588,23 @@ uint64_t wallet2::get_needed_money(uint64_t fee, const std::vector& destinations, const std::vector& selected_transfers) From 229cc86f5a3243ff7305712e7dc30b19a482df8a Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 8 Jan 2022 16:55:09 +0100 Subject: [PATCH 0070/1271] changed url of tor-connect to http --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 09642600..21ad13a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ url = https://github.com/hyle-team/zano_ui.git [submodule "contrib/tor-connect"] path = contrib/tor-connect - url = git@github.com:hyle-team/tor-connect.git + url = https://github.com/hyle-team/tor-connect.git From a4ec8748444bfa4e4951da26c5f0cb7f00bf41b6 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 8 Jan 2022 17:10:59 +0100 Subject: [PATCH 0071/1271] moved to a new commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 08eefb81..0d8e07d0 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 08eefb8102c72f15766f5bedf39dfc41cb0d7a0b +Subproject commit 0d8e07d064ad38fd573e89566d60429826cb5c3b From 6cadc8a79e236f117dabe1869689dfbb6cb968eb Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 8 Jan 2022 18:43:09 +0100 Subject: [PATCH 0072/1271] send transaction over p2p protocol as groundwork --- contrib/epee/include/net/levin_client.h | 17 +- contrib/epee/include/net/levin_client.inl | 323 +++++++++++----------- src/wallet/wallet2.cpp | 2 +- 3 files changed, 172 insertions(+), 170 deletions(-) diff --git a/contrib/epee/include/net/levin_client.h b/contrib/epee/include/net/levin_client.h index 335f6ba0..e2fb58b7 100644 --- a/contrib/epee/include/net/levin_client.h +++ b/contrib/epee/include/net/levin_client.h @@ -46,11 +46,12 @@ namespace levin /************************************************************************/ /* */ /************************************************************************/ - class levin_client_impl + template + class levin_client_impl_t { public: - levin_client_impl(); - virtual ~levin_client_impl(); + levin_client_impl_t(); + virtual ~levin_client_impl_t(); bool connect(u_long ip, int port, unsigned int timeout, const std::string& bind_ip = "0.0.0.0"); bool connect(const std::string& addr, int port, unsigned int timeout, const std::string& bind_ip = "0.0.0.0"); @@ -61,14 +62,16 @@ namespace levin virtual int notify(int command, const std::string& in_buff); protected: - net_utils::blocked_mode_client m_transport; + transport_t m_transport; + //net_utils::blocked_mode_client m_transport; }; /************************************************************************/ /* */ /************************************************************************/ - class levin_client_impl2: public levin_client_impl + template + class levin_client_impl2: public levin_client_impl_t { public: @@ -79,8 +82,8 @@ namespace levin } namespace net_utils { - typedef levin::levin_client_impl levin_client; - typedef levin::levin_client_impl2 levin_client2; + typedef levin::levin_client_impl_t levin_client; + typedef levin::levin_client_impl2 levin_client2; } } diff --git a/contrib/epee/include/net/levin_client.inl b/contrib/epee/include/net/levin_client.inl index 6f7c473e..8d69a939 100644 --- a/contrib/epee/include/net/levin_client.inl +++ b/contrib/epee/include/net/levin_client.inl @@ -32,175 +32,174 @@ #include "string_tools.h" namespace epee { -namespace levin -{ -inline -bool levin_client_impl::connect(u_long ip, int port, unsigned int timeout, const std::string& bind_ip) -{ - return m_transport.connect(string_tools::get_ip_string_from_int32(ip), port, timeout, timeout, bind_ip); -} -//------------------------------------------------------------------------------ -inline - bool levin_client_impl::connect(const std::string& addr, int port, unsigned int timeout, const std::string& bind_ip) -{ - return m_transport.connect(addr, port, timeout, timeout, bind_ip); -} -//------------------------------------------------------------------------------ -inline -bool levin_client_impl::is_connected() -{ - return m_transport.is_connected(); -} -//------------------------------------------------------------------------------ -inline -bool levin_client_impl::disconnect() -{ - return m_transport.disconnect(); -} -//------------------------------------------------------------------------------ -inline -levin_client_impl::levin_client_impl() -{ -} -//------------------------------------------------------------------------------ -inline -levin_client_impl::~levin_client_impl() -{ - disconnect(); -} -//------------------------------------------------------------------------------ -inline -int levin_client_impl::invoke(int command, const std::string& in_buff, std::string& buff_out) -{ - if(!is_connected()) - return -1; - - bucket_head head = {0}; - head.m_signature = LEVIN_SIGNATURE; - head.m_cb = in_buff.size(); - head.m_have_to_return_data = true; - head.m_command = command; - if(!m_transport.send(&head, sizeof(head))) - return -1; - - if(!m_transport.send(in_buff)) - return -1; - - std::string local_buff; - if(!m_transport.recv_n(local_buff, sizeof(bucket_head))) - return -1; - - head = *(bucket_head*)local_buff.data(); - - - if(head.m_signature!=LEVIN_SIGNATURE) - { - LOG_PRINT_L0("Signature missmatch in response"); - return -1; - } - - if(!m_transport.recv_n(buff_out, head.m_cb)) - return -1; - - return head.m_return_code; -} -//------------------------------------------------------------------------------ -inline -int levin_client_impl::notify(int command, const std::string& in_buff) -{ - if(!is_connected()) - return -1; - - bucket_head head = {0}; - head.m_signature = LEVIN_SIGNATURE; - head.m_cb = in_buff.size(); - head.m_have_to_return_data = false; - head.m_command = command; - - if(!m_transport.send((const char*)&head, sizeof(head))) - return -1; - - if(!m_transport.send(in_buff)) - return -1; - - return 1; -} - -//------------------------------------------------------------------------------ -//------------------------------------------------------------------------------ -inline - int levin_client_impl2::invoke(int command, const std::string& in_buff, std::string& buff_out) -{ - if(!is_connected()) - return -1; - - bucket_head2 head = {0}; - head.m_signature = LEVIN_SIGNATURE; - head.m_cb = in_buff.size(); - head.m_have_to_return_data = true; - head.m_command = static_cast(command); - head.m_protocol_version = LEVIN_PROTOCOL_VER_1; - head.m_flags = LEVIN_PACKET_REQUEST; - if(!m_transport.send(&head, sizeof(head))) - return -1; - - if(!m_transport.send(in_buff)) - return -1; - - //Since other side of connection could be running by async server, - //we can receive some unexpected notify(forwarded broadcast notifications for example). - //let's ignore every notify in the channel until we get invoke response - std::string local_buff; - - while (true) + namespace levin { - if (!m_transport.recv_n(local_buff, sizeof(bucket_head2))) - return LEVIN_ERROR_NET_ERROR; - head = *(bucket_head2*)local_buff.data(); - if (head.m_signature != LEVIN_SIGNATURE) + template + bool levin_client_impl_t::connect(u_long ip, int port, unsigned int timeout, const std::string& bind_ip) { - LOG_PRINT_L0("Signature missmatch in response"); - return LEVIN_ERROR_SIGNATURE_MISMATCH; + return m_transport.connect(string_tools::get_ip_string_from_int32(ip), port, timeout, timeout, bind_ip); } - if (!m_transport.recv_n(buff_out, head.m_cb)) - return LEVIN_ERROR_NET_ERROR; - - //now check if this is response to invoke (and extra validate if it's response to this(!) invoke) - if (head.m_flags&LEVIN_PACKET_RESPONSE) + //------------------------------------------------------------------------------ + template + bool levin_client_impl_t::connect(const std::string& addr, int port, unsigned int timeout, const std::string& bind_ip) { - //we got response, extra validate if its response to our request - CHECK_AND_ASSERT_MES(head.m_command == static_cast(command), LEVIN_ERROR_PROTOCOL_INCONSISTENT, "command id missmatch in response: " << head.m_command << ", expected: " << command); + return m_transport.connect(addr, port, timeout, timeout, bind_ip); + } + //------------------------------------------------------------------------------ + template + bool levin_client_impl_t::is_connected() + { + return m_transport.is_connected(); + } + //------------------------------------------------------------------------------ + template + bool levin_client_impl_t::disconnect() + { + return m_transport.disconnect(); + } + //------------------------------------------------------------------------------ + template + levin_client_impl_t::levin_client_impl_t() + { + } + //------------------------------------------------------------------------------ + template + levin_client_impl_t::~levin_client_impl_t() + { + disconnect(); + } + //------------------------------------------------------------------------------ + template + int levin_client_impl_t::invoke(int command, const std::string& in_buff, std::string& buff_out) + { + if (!is_connected()) + return -1; + + bucket_head head = { 0 }; + head.m_signature = LEVIN_SIGNATURE; + head.m_cb = in_buff.size(); + head.m_have_to_return_data = true; + head.m_command = command; + if (!m_transport.send(&head, sizeof(head))) + return -1; + + if (!m_transport.send(in_buff)) + return -1; + + std::string local_buff; + if (!m_transport.recv_n(local_buff, sizeof(bucket_head))) + return -1; + + head = *(bucket_head*)local_buff.data(); + + + if (head.m_signature != LEVIN_SIGNATURE) + { + LOG_PRINT_L0("Signature missmatch in response"); + return -1; + } + + if (!m_transport.recv_n(buff_out, head.m_cb)) + return -1; + return head.m_return_code; } + //------------------------------------------------------------------------------ + template + int levin_client_impl_t::notify(int command, const std::string& in_buff) + { + if (!is_connected()) + return -1; + + bucket_head head = { 0 }; + head.m_signature = LEVIN_SIGNATURE; + head.m_cb = in_buff.size(); + head.m_have_to_return_data = false; + head.m_command = command; + + if (!m_transport.send((const char*)&head, sizeof(head))) + return -1; + + if (!m_transport.send(in_buff)) + return -1; + + return 1; + } + //------------------------------------------------------------------------------ + template + int levin_client_impl2::invoke(int command, const std::string& in_buff, std::string& buff_out) + { + if (!is_connected()) + return -1; + + bucket_head2 head = { 0 }; + head.m_signature = LEVIN_SIGNATURE; + head.m_cb = in_buff.size(); + head.m_have_to_return_data = true; + head.m_command = static_cast(command); + head.m_protocol_version = LEVIN_PROTOCOL_VER_1; + head.m_flags = LEVIN_PACKET_REQUEST; + if (!m_transport.send(&head, sizeof(head))) + return -1; + + if (!m_transport.send(in_buff)) + return -1; + + //Since other side of connection could be running by async server, + //we can receive some unexpected notify(forwarded broadcast notifications for example). + //let's ignore every notify in the channel until we get invoke response + std::string local_buff; + + while (true) + { + if (!m_transport.recv_n(local_buff, sizeof(bucket_head2))) + return LEVIN_ERROR_NET_ERROR; + + head = *(bucket_head2*)local_buff.data(); + if (head.m_signature != LEVIN_SIGNATURE) + { + LOG_PRINT_L0("Signature missmatch in response"); + return LEVIN_ERROR_SIGNATURE_MISMATCH; + } + if (!m_transport.recv_n(buff_out, head.m_cb)) + return LEVIN_ERROR_NET_ERROR; + + //now check if this is response to invoke (and extra validate if it's response to this(!) invoke) + if (head.m_flags&LEVIN_PACKET_RESPONSE) + { + //we got response, extra validate if its response to our request + CHECK_AND_ASSERT_MES(head.m_command == static_cast(command), LEVIN_ERROR_PROTOCOL_INCONSISTENT, "command id missmatch in response: " << head.m_command << ", expected: " << command); + return head.m_return_code; + } + } + //never comes here + return LEVIN_ERROR_INTERNAL; + } + //------------------------------------------------------------------------------ + template + int levin_client_impl2::notify(int command, const std::string& in_buff) + { + if (!is_connected()) + return -1; + + bucket_head2 head = { 0 }; + head.m_signature = LEVIN_SIGNATURE; + head.m_cb = in_buff.size(); + head.m_have_to_return_data = false; + head.m_command = command; + head.m_protocol_version = LEVIN_PROTOCOL_VER_1; + head.m_flags = LEVIN_PACKET_REQUEST; + + if (!m_transport.send((const char*)&head, sizeof(head))) + return -1; + + if (!m_transport.send(in_buff)) + return -1; + + return 1; + } + } - //never comes here - return LEVIN_ERROR_INTERNAL; -} -//------------------------------------------------------------------------------ -inline - int levin_client_impl2::notify(int command, const std::string& in_buff) -{ - if(!is_connected()) - return -1; - - bucket_head2 head = {0}; - head.m_signature = LEVIN_SIGNATURE; - head.m_cb = in_buff.size(); - head.m_have_to_return_data = false; - head.m_command = command; - head.m_protocol_version = LEVIN_PROTOCOL_VER_1; - head.m_flags = LEVIN_PACKET_REQUEST; - - if(!m_transport.send((const char*)&head, sizeof(head))) - return -1; - - if(!m_transport.send(in_buff)) - return -1; - - return 1; -} - -} } //------------------------------------------------------------------------------ \ No newline at end of file diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 466a707e..b43f9608 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4591,7 +4591,7 @@ void wallet2::send_transaction_to_network(const transaction& tx) #define ENABLE_TOR_RELAY #ifdef ENABLE_TOR_RELAY //TODO check that core synchronized - epee::levin::levin_client_impl p2p_client; + epee::net_utils::levin_client2 p2p_client; if (!p2p_client.connect("127.0.0.1", P2P_DEFAULT_PORT, 100000)) { THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); From 5446ccf7d37351236276484388e10ad4f6c9315a Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 9 Jan 2022 00:07:26 +0100 Subject: [PATCH 0073/1271] integrated tor into wallet in a draft mode --- contrib/tor-connect | 2 +- src/CMakeLists.txt | 7 +++---- src/common/tor_helper.h | 17 +++++++++++++++++ src/wallet/wallet2.cpp | 6 ++++-- 4 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 src/common/tor_helper.h diff --git a/contrib/tor-connect b/contrib/tor-connect index 0d8e07d0..2282af37 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 0d8e07d064ad38fd573e89566d60429826cb5c3b +Subproject commit 2282af37e1dcc0d50a4d31ef88b4737f54df4fd0 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a044fab8..063a651b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -121,12 +121,11 @@ add_library(currency_core ${CURRENCY_CORE}) add_dependencies(currency_core version ${PCH_LIB_NAME}) ENABLE_SHARED_PCH(currency_core CURRENCY_CORE) +add_library(wallet ${WALLET}) if(CMAKE_SYSTEM_NAME STREQUAL "Android" ) - add_library(wallet ${WALLET}) add_dependencies(wallet version ${PCH_LIB_NAME}) target_link_libraries(wallet currency_core crypto common zlibstatic ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES} android log) else() - add_library(wallet ${WALLET}) add_dependencies(wallet version ${PCH_LIB_NAME}) ENABLE_SHARED_PCH(wallet WALLET) endif() @@ -170,8 +169,8 @@ ENABLE_SHARED_PCH(connectivity_tool CONN_TOOL) ENABLE_SHARED_PCH_EXECUTABLE(connectivity_tool) add_executable(simplewallet ${SIMPLEWALLET}) -add_dependencies(simplewallet version) -target_link_libraries(simplewallet wallet rpc currency_core crypto common zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) +add_dependencies(simplewallet version) +target_link_libraries(simplewallet wallet rpc currency_core crypto common zlibstatic ethash tor-connect ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) ENABLE_SHARED_PCH(simplewallet SIMPLEWALLET) ENABLE_SHARED_PCH_EXECUTABLE(simplewallet) diff --git a/src/common/tor_helper.h b/src/common/tor_helper.h new file mode 100644 index 00000000..48cabba1 --- /dev/null +++ b/src/common/tor_helper.h @@ -0,0 +1,17 @@ +// Copyright (c) 2014-2018 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include "tor-connect/torlib/tor_wrapper.h" + + + +namespace tools +{ + typedef epee::levin::levin_client_impl2 levin_over_tor_client; + +} + + diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index b43f9608..d3db53e3 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -31,6 +31,7 @@ using namespace epee; #include "version.h" #include "common/encryption_filter.h" #include "crypto/bitcoin/sha256_helper.h" +#include "common/tor_helper.h" using namespace currency; @@ -4591,8 +4592,9 @@ void wallet2::send_transaction_to_network(const transaction& tx) #define ENABLE_TOR_RELAY #ifdef ENABLE_TOR_RELAY //TODO check that core synchronized - epee::net_utils::levin_client2 p2p_client; - if (!p2p_client.connect("127.0.0.1", P2P_DEFAULT_PORT, 100000)) + //epee::net_utils::levin_client2 p2p_client; + tools::levin_over_tor_client p2p_client; + if (!p2p_client.connect("144.76.183.143", P2P_DEFAULT_PORT, 100000)) { THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); } From bd797f6514a47bef2c4abad9881bd3a8dd24d4b4 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 13 Jan 2022 14:08:21 +0100 Subject: [PATCH 0074/1271] current state stored before branch switch --- src/wallet/wallet2.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index d3db53e3..c3c3f95b 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4592,9 +4592,9 @@ void wallet2::send_transaction_to_network(const transaction& tx) #define ENABLE_TOR_RELAY #ifdef ENABLE_TOR_RELAY //TODO check that core synchronized - //epee::net_utils::levin_client2 p2p_client; - tools::levin_over_tor_client p2p_client; - if (!p2p_client.connect("144.76.183.143", P2P_DEFAULT_PORT, 100000)) + epee::net_utils::levin_client2 p2p_client; + //tools::levin_over_tor_client p2p_client; + if (!p2p_client.connect("144.76.183.143", 2121, 100000)) { THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); } From ceb30464d3839e2ed1c7921733a9a861b71f44d4 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 13 Jan 2022 14:10:53 +0100 Subject: [PATCH 0075/1271] added missing command line argument for deeplinks under windows/linux --- utils/Zano.sh | 2 +- utils/setup_64.iss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/Zano.sh b/utils/Zano.sh index e0eac8cc..6e4d22d8 100755 --- a/utils/Zano.sh +++ b/utils/Zano.sh @@ -29,7 +29,7 @@ create_desktop_icon() echo GenericName=Zano | tee -a $target_file_name > /dev/null echo Comment=Privacy blockchain | tee -a $target_file_name > /dev/null echo Icon=$script_dir/html/files/desktop_linux_icon.png | tee -a $target_file_name > /dev/null - echo Exec=$script_dir/Zano.sh %u | tee -a $target_file_name > /dev/null + echo Exec=$script_dir/Zano.sh --deeplink-params=%u | tee -a $target_file_name > /dev/null echo Terminal=true | tee -a $target_file_name > /dev/null echo Type=Application | tee -a $target_file_name > /dev/null echo "Categories=Qt;Utility;" | tee -a $target_file_name > /dev/null diff --git a/utils/setup_64.iss b/utils/setup_64.iss index adee27a8..364a9767 100644 --- a/utils/setup_64.iss +++ b/utils/setup_64.iss @@ -53,7 +53,7 @@ Root: HKCR; Subkey: "ZanoWalletDataFile\DefaultIcon"; ValueType: string; ValueNa Root: HKCR; Subkey: "ZanoWalletDataKyesFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\Zano.exe,0" Root: HKCR; Subkey: "Zano"; ValueType: string; ValueName: "URL Protocol"; ValueData: "" -Root: HKCR; Subkey: "Zano\shell\open\command"; ValueType: string; ValueName: ""; ValueData: "{app}\Zano.exe %1" +Root: HKCR; Subkey: "Zano\shell\open\command"; ValueType: string; ValueName: ""; ValueData: "{app}\Zano.exe --deeplink-params=%1" [Files] From cd644cc8513f0a347c776d1740b80d1f7304ae86 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 13 Jan 2022 18:56:49 +0100 Subject: [PATCH 0076/1271] added preventive removal of ipc channel --- src/gui/qt-daemon/application/mainwindow.cpp | 19 +++++++++++++++++-- src/gui/qt-daemon/application/mainwindow.h | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 0c0ce687..445acb10 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -744,11 +744,25 @@ void qt_log_message_handler(QtMsgType type, const QMessageLogContext &context, c } } +bool MainWindow::remove_ipc() +{ + try { + boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + } + catch (...) + { + } + return true; +} bool MainWindow::init_ipc_server() { + //in case previous instance wasn't close graceful, ipc channel will remain open and new creation will fail, so we + //trying to close it anyway before open, to make sure there are no dead channels. If there are another running instance, it wom't + //let channel to close, so it will fail later on creating channel + remove_ipc(); #define GUI_IPC_BUFFER_SIZE 10000 try { //Create a message queue. @@ -775,18 +789,19 @@ bool MainWindow::init_ipc_server() handle_ipc_event(buff);//todo process token } } - boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + remove_ipc(); LOG_PRINT_L0("IPC Handling thread finished"); } catch (const std::exception& ex) { + remove_ipc(); boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); LOG_ERROR("Failed to receive IPC que: " << ex.what()); } catch (...) { - boost::interprocess::message_queue::remove(GUI_IPC_MESSAGE_CHANNEL_NAME); + remove_ipc(); LOG_ERROR("Failed to receive IPC que: unknown exception"); } }); diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 974b0bea..4f7af71d 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -240,6 +240,7 @@ private: bool load_app_config(); bool init_window(); bool init_ipc_server(); + bool remove_ipc(); std::string get_wallet_log_prefix(size_t wallet_id) const { return m_backend.get_wallet_log_prefix(wallet_id); } From 50392b9e312ec187784439c652995056d34f3f7e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 18 Jan 2022 18:19:18 +0100 Subject: [PATCH 0077/1271] fixed bug with crashing message box --- src/gui/qt-daemon/application/mainwindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 445acb10..5c00dbfc 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -825,8 +825,8 @@ bool MainWindow::init_ipc_server() bool MainWindow::handle_ipc_event(const std::string& arguments) { - std::string zzz = "Received IPC: " + arguments; - message_box(zzz.c_str()); + std::string zzz = "Received IPC: " + arguments.c_str(); + std::cout << zzz;//message_box(zzz.c_str()); handle_deeplink_click(arguments.c_str()); From b7a42895a1f55aebb17dc9429c9426aeeb6130ae Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 18 Jan 2022 18:48:36 +0100 Subject: [PATCH 0078/1271] fixed fixing fix --- src/gui/qt-daemon/application/mainwindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 5c00dbfc..d760c2d1 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -825,7 +825,7 @@ bool MainWindow::init_ipc_server() bool MainWindow::handle_ipc_event(const std::string& arguments) { - std::string zzz = "Received IPC: " + arguments.c_str(); + std::string zzz = std::string("Received IPC: ") + arguments.c_str(); std::cout << zzz;//message_box(zzz.c_str()); handle_deeplink_click(arguments.c_str()); From 9ef3c81f659688643ff6d9fbdb181f4331eec01c Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 26 Jan 2022 15:24:31 +0100 Subject: [PATCH 0079/1271] moved ui to latest commit --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 93802cb8..412a89eb 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 93802cb8fe1c79daf320aa4b72ba454d27da31d7 +Subproject commit 412a89eb100e3fa81c0e45e3a4f6270992eded38 From d423f465a1a2f44890161aae1bcd9f7989043fa0 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 27 Jan 2022 15:30:50 +0100 Subject: [PATCH 0080/1271] Fixed WRONG_PASSWORD error in UI --- src/gui/qt-daemon/application/mainwindow.cpp | 1 + src/gui/qt-daemon/layout | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index d760c2d1..df242aff 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -786,6 +786,7 @@ bool MainWindow::init_ipc_server() bool data_received = pmq->timed_receive((void*)buff.data(), GUI_IPC_BUFFER_SIZE, recvd_size, priority, boost::posix_time::ptime(boost::posix_time::microsec_clock::universal_time()) + boost::posix_time::milliseconds(1000)); if (data_received && recvd_size != 0) { + buff.resize(recvd_size, '*'); handle_ipc_event(buff);//todo process token } } diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 412a89eb..02e277cb 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 412a89eb100e3fa81c0e45e3a4f6270992eded38 +Subproject commit 02e277cbda5c9b40f8bb445fb2f6b87a3a780431 From 53dbc93ff0056d7327df3d6a9a9f8294bd03a199 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 31 Jan 2022 15:09:00 +0100 Subject: [PATCH 0081/1271] moved to a new commit UI and tor --- contrib/tor-connect | 2 +- src/gui/qt-daemon/layout | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 2282af37..75ecfd8a 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 2282af37e1dcc0d50a4d31ef88b4737f54df4fd0 +Subproject commit 75ecfd8a99c4dc622b56607e5aa12760420312dc diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 93802cb8..02e277cb 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 93802cb8fe1c79daf320aa4b72ba454d27da31d7 +Subproject commit 02e277cbda5c9b40f8bb445fb2f6b87a3a780431 From 6bfa257cfccba132dee8353e9905563341c23582 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 31 Jan 2022 15:10:05 +0100 Subject: [PATCH 0082/1271] Moved UI to latest commit --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 02e277cb..b42ef6a6 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 02e277cbda5c9b40f8bb445fb2f6b87a3a780431 +Subproject commit b42ef6a638616872c00f27780937e8a01a4d3539 From ad4b652e93030de85eea07126cb7443c34710649 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 2 Feb 2022 14:48:35 +0100 Subject: [PATCH 0083/1271] moved UI to latest commit with fixes --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index b42ef6a6..fdc3e748 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit b42ef6a638616872c00f27780937e8a01a4d3539 +Subproject commit fdc3e748d4e0014e5652031c7603ec00b45803fa From be318d6ed4f4db048a2c58c41c66a78789e17f27 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 4 Feb 2022 22:15:59 +0100 Subject: [PATCH 0084/1271] threads pool: inital code + unit tests dummy --- src/common/threads_pool.h | 102 +++++++++++++++++++ tests/performance_tests/main.cpp | 30 +++--- tests/performance_tests/threads_pool_tests.h | 15 +++ 3 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 src/common/threads_pool.h create mode 100644 tests/performance_tests/threads_pool_tests.h diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h new file mode 100644 index 00000000..b30e7b57 --- /dev/null +++ b/src/common/threads_pool.h @@ -0,0 +1,102 @@ +// Copyright (c) 2014-2019 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include +#include + +namespace utils +{ + + struct call_executor_base + { + virtual void execute()=0; + }; + + template + struct call_executor_t : public call_executor_base + { + call_executor_t(t_executor_func f) :m_func(f) + {} + t_executor_func m_func; + virtual void execute() + { + m_func(); + } + }; + + template + std::shared_ptr build_call_executor(t_executor_func func) + { + std::shared_ptr res = new call_executor_t(func); + return res; + } + + class threads_pool + { + void init() + { + m_is_stop = false; + int num_threads = std::thread::hardware_concurrency(); + + for (int i = 0; i < num_threads; i++) + { + m_threads.push_back(std::thread(worker_func)); + } + } + + threads_pool(): m_is_stop(false), m_threads_counter(0) + {} + + template + bool add_job(t_executor_func func) + { + { + std::unique_lock lock(m_queue_mutex); + m_jobs_que.push_back(build_call_executor(func)); + } + m_condition.notify_one(); + return true; + } + + private: + void worker_func() + { + LOG_PRINT_L0("Worker thread is started"); + while (true) + { + std::shared_ptr job; + { + std::unique_lock lock(m_queue_mutex); + + m_condition.wait(lock, [this]() + { + return !m_jobs_que.empty() || m_is_stop; + }); + if (m_is_stop) + { + LOG_PRINT_L0("Worker thread is finished"); + return; + } + + job = m_jobs_que.front(); + m_jobs_que.pop_front(); + } + + job->execute(); + } + } + + + + std::list> m_jobs_que; + std::condition_variable m_condition; + std::mutex m_queue_mutex; + std::vector m_threads; + atomic m_is_stop; + std::atomic m_threads_counter; + }; +} \ No newline at end of file diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp index c3f0a77a..e8d7a2ed 100644 --- a/tests/performance_tests/main.cpp +++ b/tests/performance_tests/main.cpp @@ -23,6 +23,8 @@ #include "print_struct_to_json.h" #include "free_space_check.h" #include "htlc_hash_tests.h" +#include "threads_pool_tests.h" + int main(int argc, char** argv) { @@ -33,21 +35,23 @@ int main(int argc, char** argv) epee::log_space::log_singletone::get_default_log_file().c_str(), epee::log_space::log_singletone::get_default_log_folder().c_str()); - - std::string buf1 = tools::get_varint_data(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX); - std::string buf2 = tools::get_varint_data(CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX); - std::string buf3 = tools::get_varint_data(CURRENCY_PUBLIC_INTEG_ADDRESS_V2_BASE58_PREFIX); - std::string buf4 = tools::get_varint_data(CURRENCY_PUBLIC_AUDITABLE_ADDRESS_BASE58_PREFIX); - std::string buf5 = tools::get_varint_data(CURRENCY_PUBLIC_AUDITABLE_INTEG_ADDRESS_BASE58_PREFIX); - std::cout << "Buf1: " << epee::string_tools::buff_to_hex_nodelimer(buf1) << ENDL; - std::cout << "Buf2: " << epee::string_tools::buff_to_hex_nodelimer(buf2) << ENDL; - std::cout << "Buf3: " << epee::string_tools::buff_to_hex_nodelimer(buf3) << ENDL; - std::cout << "Buf4: " << epee::string_tools::buff_to_hex_nodelimer(buf4) << ENDL; - std::cout << "Buf5: " << epee::string_tools::buff_to_hex_nodelimer(buf5) << ENDL; + thread_pool_tests(); - - do_htlc_hash_tests(); +// std::string buf1 = tools::get_varint_data(CURRENCY_PUBLIC_ADDRESS_BASE58_PREFIX); +// std::string buf2 = tools::get_varint_data(CURRENCY_PUBLIC_INTEG_ADDRESS_BASE58_PREFIX); +// std::string buf3 = tools::get_varint_data(CURRENCY_PUBLIC_INTEG_ADDRESS_V2_BASE58_PREFIX); +// std::string buf4 = tools::get_varint_data(CURRENCY_PUBLIC_AUDITABLE_ADDRESS_BASE58_PREFIX); +// std::string buf5 = tools::get_varint_data(CURRENCY_PUBLIC_AUDITABLE_INTEG_ADDRESS_BASE58_PREFIX); +// +// std::cout << "Buf1: " << epee::string_tools::buff_to_hex_nodelimer(buf1) << ENDL; +// std::cout << "Buf2: " << epee::string_tools::buff_to_hex_nodelimer(buf2) << ENDL; +// std::cout << "Buf3: " << epee::string_tools::buff_to_hex_nodelimer(buf3) << ENDL; +// std::cout << "Buf4: " << epee::string_tools::buff_to_hex_nodelimer(buf4) << ENDL; +// std::cout << "Buf5: " << epee::string_tools::buff_to_hex_nodelimer(buf5) << ENDL; +// +// + //do_htlc_hash_tests(); //run_serialization_performance_test(); //return 1; //run_core_market_performance_tests(100000); diff --git a/tests/performance_tests/threads_pool_tests.h b/tests/performance_tests/threads_pool_tests.h new file mode 100644 index 00000000..2494dae8 --- /dev/null +++ b/tests/performance_tests/threads_pool_tests.h @@ -0,0 +1,15 @@ +// Copyright (c) 2019 The Zano developers +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include +#include "include_base_utils.h" +#include "threads_pool.h" + + +inline +void thread_pool_tests() +{ +} \ No newline at end of file From e2bb37e5cb941d2f8700cf2f62f890e7b3916860 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 8 Feb 2022 13:59:09 +0100 Subject: [PATCH 0085/1271] added tests for threads pool --- src/common/threads_pool.h | 23 +++++++++++++++----- tests/performance_tests/threads_pool_tests.h | 19 +++++++++++++++- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h index b30e7b57..b1d4aebc 100644 --- a/src/common/threads_pool.h +++ b/src/common/threads_pool.h @@ -16,7 +16,7 @@ namespace utils virtual void execute()=0; }; - template + template struct call_executor_t : public call_executor_base { call_executor_t(t_executor_func f) :m_func(f) @@ -28,15 +28,16 @@ namespace utils } }; - template + template std::shared_ptr build_call_executor(t_executor_func func) { - std::shared_ptr res = new call_executor_t(func); + std::shared_ptr res(static_cast(new call_executor_t(func))); return res; } class threads_pool { + public: void init() { m_is_stop = false; @@ -44,14 +45,14 @@ namespace utils for (int i = 0; i < num_threads; i++) { - m_threads.push_back(std::thread(worker_func)); + m_threads.push_back(std::thread([this](){this->worker_func(); })); } } threads_pool(): m_is_stop(false), m_threads_counter(0) {} - template + template bool add_job(t_executor_func func) { { @@ -62,6 +63,16 @@ namespace utils return true; } + ~threads_pool() + { + m_is_stop = true; + m_condition.notify_all(); + for (auto& th : m_threads) + { + th.join(); + } + } + private: void worker_func() { @@ -96,7 +107,7 @@ namespace utils std::condition_variable m_condition; std::mutex m_queue_mutex; std::vector m_threads; - atomic m_is_stop; + std::atomic m_is_stop; std::atomic m_threads_counter; }; } \ No newline at end of file diff --git a/tests/performance_tests/threads_pool_tests.h b/tests/performance_tests/threads_pool_tests.h index 2494dae8..a1bf2042 100644 --- a/tests/performance_tests/threads_pool_tests.h +++ b/tests/performance_tests/threads_pool_tests.h @@ -6,10 +6,27 @@ #include #include "include_base_utils.h" -#include "threads_pool.h" +#include "common/threads_pool.h" inline void thread_pool_tests() { + { + utils::threads_pool pool; + pool.init(); + std::atomic count_jobs_finished = 0; + size_t i = 0; + for (; i != 10; i++) + { + pool.add_job([&, i]() {LOG_PRINT_L0("Job " << i << " started"); epee::misc_utils::sleep_no_w(10000); ++count_jobs_finished; LOG_PRINT_L0("Job " << i << " finished"); }); + } + while (count_jobs_finished != i) + { + epee::misc_utils::sleep_no_w(500); + } + LOG_PRINT_L0("All jobs finished"); + } + LOG_PRINT_L0("Scope left"); + } \ No newline at end of file From 761b75ad728f0bd1c9a0bad4954defc3fd7d4815 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 8 Feb 2022 14:51:22 +0100 Subject: [PATCH 0086/1271] added batch jobs to threads pool --- src/common/threads_pool.h | 40 +++++++++++++++++++- tests/performance_tests/threads_pool_tests.h | 27 ++++++++++++- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h index b1d4aebc..235e48ec 100644 --- a/src/common/threads_pool.h +++ b/src/common/threads_pool.h @@ -37,7 +37,15 @@ namespace utils class threads_pool { - public: + public: + typedef std::list> jobs_container; + + template + static void add_job_to_container(jobs_container& cntr, t_executor_func func) + { + cntr.push_back(std::shared_ptr(static_cast(new call_executor_t(func)))); + } + void init() { m_is_stop = false; @@ -63,6 +71,34 @@ namespace utils return true; } + void add_batch_and_wait(const jobs_container& cntr) + { + std::condition_variable batch_condition; + std::mutex batch_mutex; + + + std::atomic cnt = 0; + for (const auto& jb : cntr) + { + call_executor_base* pjob = jb.get(); + add_job([&, pjob]() { + pjob->execute(); + { + std::lock_guard lock(batch_mutex); + cnt++; + } + batch_condition.notify_one(); + }); + } + + std::unique_lock lock(batch_mutex); + batch_condition.wait(lock, [&]() + { + return cnt == cntr.size(); + }); + LOG_PRINT_L0("All jobs finiahed"); + } + ~threads_pool() { m_is_stop = true; @@ -103,7 +139,7 @@ namespace utils - std::list> m_jobs_que; + jobs_container m_jobs_que; std::condition_variable m_condition; std::mutex m_queue_mutex; std::vector m_threads; diff --git a/tests/performance_tests/threads_pool_tests.h b/tests/performance_tests/threads_pool_tests.h index a1bf2042..6999ac30 100644 --- a/tests/performance_tests/threads_pool_tests.h +++ b/tests/performance_tests/threads_pool_tests.h @@ -10,7 +10,7 @@ inline -void thread_pool_tests() +void thread_pool_tests_simple() { { utils::threads_pool pool; @@ -28,5 +28,30 @@ void thread_pool_tests() LOG_PRINT_L0("All jobs finished"); } LOG_PRINT_L0("Scope left"); +} +inline +void thread_pool_tests() +{ + { + utils::threads_pool pool; + pool.init(); + std::atomic count_jobs_finished = 0; + + utils::threads_pool::jobs_container jobs; + size_t i = 0; + for (; i != 10; i++) + { + utils::threads_pool::add_job_to_container(jobs, [&, i]() {LOG_PRINT_L0("Job " << i << " started"); epee::misc_utils::sleep_no_w(10000); ++count_jobs_finished; LOG_PRINT_L0("Job " << i << " finished"); }); + } + + pool.add_batch_and_wait(jobs); + if (count_jobs_finished != i) + { + LOG_ERROR("Test failed"); + return; + } + LOG_PRINT_L0("All jobs finished"); + } + LOG_PRINT_L0("Scope left"); } \ No newline at end of file From 5d808ad39ee250ccaaac60aa6cc4d79d6ab860f7 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 8 Feb 2022 14:55:35 +0100 Subject: [PATCH 0087/1271] added option for manual specifying number of threads --- src/common/threads_pool.h | 74 +++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h index 235e48ec..4e4e3905 100644 --- a/src/common/threads_pool.h +++ b/src/common/threads_pool.h @@ -13,7 +13,7 @@ namespace utils struct call_executor_base { - virtual void execute()=0; + virtual void execute() = 0; }; template @@ -28,7 +28,7 @@ namespace utils } }; - template + template std::shared_ptr build_call_executor(t_executor_func func) { std::shared_ptr res(static_cast(new call_executor_t(func))); @@ -40,26 +40,30 @@ namespace utils public: typedef std::list> jobs_container; - template - static void add_job_to_container(jobs_container& cntr, t_executor_func func) + template + static void add_job_to_container(jobs_container& cntr, t_executor_func func) { cntr.push_back(std::shared_ptr(static_cast(new call_executor_t(func)))); } void init() { - m_is_stop = false; int num_threads = std::thread::hardware_concurrency(); - + this->init(num_threads); + } + void init(unsigned int num_threads) + { + m_is_stop = false; + for (int i = 0; i < num_threads; i++) { - m_threads.push_back(std::thread([this](){this->worker_func(); })); + m_threads.push_back(std::thread([this]() {this->worker_func(); })); } } - threads_pool(): m_is_stop(false), m_threads_counter(0) + threads_pool() : m_is_stop(false), m_threads_counter(0) {} - + template bool add_job(t_executor_func func) { @@ -109,41 +113,41 @@ namespace utils } } - private: - void worker_func() + private: + void worker_func() + { + LOG_PRINT_L0("Worker thread is started"); + while (true) { - LOG_PRINT_L0("Worker thread is started"); - while (true) + std::shared_ptr job; { - std::shared_ptr job; - { - std::unique_lock lock(m_queue_mutex); + std::unique_lock lock(m_queue_mutex); - m_condition.wait(lock, [this]() - { - return !m_jobs_que.empty() || m_is_stop; - }); - if (m_is_stop) - { - LOG_PRINT_L0("Worker thread is finished"); - return; - } - - job = m_jobs_que.front(); - m_jobs_que.pop_front(); + m_condition.wait(lock, [this]() + { + return !m_jobs_que.empty() || m_is_stop; + }); + if (m_is_stop) + { + LOG_PRINT_L0("Worker thread is finished"); + return; } - job->execute(); + job = m_jobs_que.front(); + m_jobs_que.pop_front(); } + + job->execute(); } + } - jobs_container m_jobs_que; - std::condition_variable m_condition; - std::mutex m_queue_mutex; - std::vector m_threads; - std::atomic m_is_stop; - std::atomic m_threads_counter; + jobs_container m_jobs_que; + std::condition_variable m_condition; + std::mutex m_queue_mutex; + std::vector m_threads; + std::atomic m_is_stop; + std::atomic m_threads_counter; }; } \ No newline at end of file From 8b4c24cdf93c3253f405d9ded496b96161d9c676 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 15:27:59 +0100 Subject: [PATCH 0088/1271] moved to latest UI: fixes in deeplinks, tabs refactoring --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index fdc3e748..22bed634 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit fdc3e748d4e0014e5652031c7603ec00b45803fa +Subproject commit 22bed634e5f3c1da0b4d4b1262d57a2445dba08b From 35b189b9b570904172237ab1d1405314a016ebf8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 19:27:11 +0100 Subject: [PATCH 0089/1271] moved tor library to latest commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 75ecfd8a..2dd2b2f4 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 75ecfd8a99c4dc622b56607e5aa12760420312dc +Subproject commit 2dd2b2f4cd204ba27dd9eeb17021f5c4f3341566 From 6720efb5db9b696b6d1abcd998fba0fec6d3c227 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 19:32:20 +0100 Subject: [PATCH 0090/1271] changed cmake target type for tor --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 2dd2b2f4..f884157b 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 2dd2b2f4cd204ba27dd9eeb17021f5c4f3341566 +Subproject commit f884157bca80475c579808a8bdc4e8a7bb311ce2 From c9b3d0b20a41fb2efcf4f30b813190fe96ae4314 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 19:57:57 +0100 Subject: [PATCH 0091/1271] moved tor lib to latest commit with fixed missing header --- contrib/tor-connect | 2 +- src/wallet/wallet2.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index f884157b..ce35f526 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit f884157bca80475c579808a8bdc4e8a7bb311ce2 +Subproject commit ce35f526dad04e0e72d75261b21d8c61ca2a725a diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c3c3f95b..633099c5 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4592,8 +4592,8 @@ void wallet2::send_transaction_to_network(const transaction& tx) #define ENABLE_TOR_RELAY #ifdef ENABLE_TOR_RELAY //TODO check that core synchronized - epee::net_utils::levin_client2 p2p_client; - //tools::levin_over_tor_client p2p_client; + //epee::net_utils::levin_client2 p2p_client; + tools::levin_over_tor_client p2p_client; if (!p2p_client.connect("144.76.183.143", 2121, 100000)) { THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); From 4340e4f688bd3d8f93fcdefbee6b034d3bc625bc Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 20:05:22 +0100 Subject: [PATCH 0092/1271] moved tor lib to lates commit: ssl path --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index ce35f526..f66050fb 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit ce35f526dad04e0e72d75261b21d8c61ca2a725a +Subproject commit f66050fb8a009e79f219d3d681532c49bd0a5124 From 09edf4a539bd2a790b1a2243f962158d0c12672e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 20:12:06 +0100 Subject: [PATCH 0093/1271] moved tor lib to lates commit: removed ssl dependency --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index f66050fb..8e9db3cf 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit f66050fb8a009e79f219d3d681532c49bd0a5124 +Subproject commit 8e9db3cf6d4b0c59904bc4979f30592b287abce4 From 72ad0e0fe0aceea2d754d6f2ee25513ff1f2b9fc Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 9 Feb 2022 21:26:58 +0100 Subject: [PATCH 0094/1271] moved to latest commit: added more logs --- contrib/tor-connect | 2 +- src/wallet/wallet2.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 8e9db3cf..6c1506ac 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 8e9db3cf6d4b0c59904bc4979f30592b287abce4 +Subproject commit 6c1506ace94e488333e75208c46a6c00ae85f4d3 diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 633099c5..84d7847b 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4594,7 +4594,7 @@ void wallet2::send_transaction_to_network(const transaction& tx) //TODO check that core synchronized //epee::net_utils::levin_client2 p2p_client; tools::levin_over_tor_client p2p_client; - if (!p2p_client.connect("144.76.183.143", 2121, 100000)) + if (!p2p_client.connect("144.76.183.143", 2121, 10000)) { THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); } From dba8cd74b9d86b897f711d4cf2ceb814ba09b1df Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 11 Feb 2022 17:25:34 +0300 Subject: [PATCH 0095/1271] pulled lat changes from tor submodule --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 6c1506ac..a68ab97d 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 6c1506ace94e488333e75208c46a6c00ae85f4d3 +Subproject commit a68ab97d6ce05c78cbe1ea43e499ad38ddfd7341 From f708f454d90ee6dd6587d4bc6bb854a5ff6c42b1 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 12 Feb 2022 22:38:12 +0300 Subject: [PATCH 0096/1271] fixes related to tor --- contrib/epee/include/misc_language.h | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/contrib/epee/include/misc_language.h b/contrib/epee/include/misc_language.h index 276dfe02..1389ba03 100644 --- a/contrib/epee/include/misc_language.h +++ b/contrib/epee/include/misc_language.h @@ -31,6 +31,8 @@ #include #include #include +#include +#include #include #include "include_base_utils.h" #include "auto_val_init.h" @@ -111,17 +113,6 @@ namespace misc_utils return (std::numeric_limits::max)(); } - // TEMPLATE STRUCT less - template - struct less_as_pod - : public std::binary_function<_Ty, _Ty, bool> - { // functor for operator< - bool operator()(const _Ty& _Left, const _Ty& _Right) const - { // apply operator< to operands - return memcmp(&_Left, &_Right, sizeof(_Left)) < 0; - } - }; - template bool is_less_as_pod(const _Ty& _Left, const _Ty& _Right) { // apply operator< to operands From 2014487c8888472aef2b8451074e1f8a52619612 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 13 Feb 2022 18:31:36 +0300 Subject: [PATCH 0097/1271] oved UI to latest version(fixes on deeplinks) --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 22bed634..85caecba 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 22bed634e5f3c1da0b4d4b1262d57a2445dba08b +Subproject commit 85caecba5d0411dfc53881b4d6f959f0fc8f6a22 From c7604f7c2aa6f9c897c3dc1a87ca5775985ec654 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 13 Feb 2022 18:34:18 +0300 Subject: [PATCH 0098/1271] moved UI to latest commits --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 22bed634..85caecba 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 22bed634e5f3c1da0b4d4b1262d57a2445dba08b +Subproject commit 85caecba5d0411dfc53881b4d6f959f0fc8f6a22 From 42e70b134a1f5547670edb5405d67a7fe9a604c5 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 14 Feb 2022 18:43:23 +0300 Subject: [PATCH 0099/1271] removed message box on command line arg --- src/gui/qt-daemon/application/mainwindow.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index df242aff..646e4d3b 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -852,12 +852,6 @@ bool MainWindow::handle_deeplink_params_in_commandline() bool MainWindow::init_backend(int argc, char* argv[]) { - if (argc > 1) - { - std::string msg = "Command_line 1: "; - msg += argv[1]; - message_box(msg.c_str()); - } TRY_ENTRY(); std::string command_line_fail_details; From 4d51ca307b3b806a7bfafa3fec8674d78d334bf5 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 17 Feb 2022 16:21:19 +0300 Subject: [PATCH 0100/1271] Moved tor lib to latest commit(fixed sent payload len in tor packet) --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index a68ab97d..4d614c0a 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit a68ab97d6ce05c78cbe1ea43e499ad38ddfd7341 +Subproject commit 4d614c0a6fcca5dcf646706e396025031fa5a3ce From 70a991d6f7d4b28267e0dd8f726f97e7511945fd Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 17 Feb 2022 17:44:21 +0300 Subject: [PATCH 0101/1271] Moved tor lib to latest commit(added sending data bigger then 117 bytes) --- contrib/epee/include/net/levin_client.h | 2 ++ contrib/tor-connect | 2 +- src/wallet/wallet2.cpp | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/contrib/epee/include/net/levin_client.h b/contrib/epee/include/net/levin_client.h index e2fb58b7..cc656e9d 100644 --- a/contrib/epee/include/net/levin_client.h +++ b/contrib/epee/include/net/levin_client.h @@ -77,6 +77,8 @@ namespace levin int invoke(int command, const std::string& in_buff, std::string& buff_out); int notify(int command, const std::string& in_buff); + + transport_t& get_transport() {return this->m_transport;} }; } diff --git a/contrib/tor-connect b/contrib/tor-connect index 4d614c0a..c980970b 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 4d614c0a6fcca5dcf646706e396025031fa5a3ce +Subproject commit c980970bece66ef469ac8931ffa5ccac9264437d diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 84d7847b..d3b89433 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4595,6 +4595,7 @@ void wallet2::send_transaction_to_network(const transaction& tx) //epee::net_utils::levin_client2 p2p_client; tools::levin_over_tor_client p2p_client; if (!p2p_client.connect("144.76.183.143", 2121, 10000)) +// if (!p2p_client.connect("144.76.183.143", 1001, 10000)) { THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); } From bba9931ff69e6f98ba5e3ac7f09c5de273584075 Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 10 Mar 2022 16:36:02 +0300 Subject: [PATCH 0102/1271] checkpoints: set to main @ 1480000 --- src/currency_core/checkpoints_create.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/currency_core/checkpoints_create.h b/src/currency_core/checkpoints_create.h index 059d190e..e0152685 100644 --- a/src/currency_core/checkpoints_create.h +++ b/src/currency_core/checkpoints_create.h @@ -25,6 +25,7 @@ namespace currency ADD_CHECKPOINT(600000, "d9fe316086e1aaea07d94082973ec764eff5fc5a05ed6e1eca273cee59daeeb4"); ADD_CHECKPOINT(900000, "2205b73cd79d4937b087b02a8b001171b73c34464bc4a952834eaf7c2bd63e86"); ADD_CHECKPOINT(1161000, "96990d851b484e30190678756ba2a4d3a2f92b987e2470728ac1e38b2bf35908"); + ADD_CHECKPOINT(1480000, "5dd3381eec35e8b4eba4518bfd8eec682a4292761d92218fd59b9f0ffedad3fe"); #endif return true; From 01689994cf510cd7961b895b99a44babe6d62140 Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 10 Mar 2022 16:47:23 +0300 Subject: [PATCH 0103/1271] version bump: 1.4.0.139 -> 1.4.1.140 --- src/version.h.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index a0f77b84..6a30d673 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -5,9 +5,9 @@ #define PROJECT_MAJOR_VERSION "1" #define PROJECT_MINOR_VERSION "4" -#define PROJECT_REVISION "0" +#define PROJECT_REVISION "1" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 139 +#define PROJECT_VERSION_BUILD_NO 140 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From cadf64c3ca60e02409f7fdc813aca21d810aa623 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 10 Mar 2022 16:00:30 +0100 Subject: [PATCH 0104/1271] updated UI --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 85caecba..709a1e6a 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 85caecba5d0411dfc53881b4d6f959f0fc8f6a22 +Subproject commit 709a1e6a3eb5ae7af48bbadf6b4c512b637b75b3 From c46e2fb51ffb2c4a372b24d80112333ae8444bc1 Mon Sep 17 00:00:00 2001 From: zano build machine Date: Thu, 10 Mar 2022 19:18:55 +0300 Subject: [PATCH 0105/1271] === build number: 140 -> 141 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 6a30d673..9c911483 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "1" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 140 +#define PROJECT_VERSION_BUILD_NO 141 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From a43035746681461f6a57c0e7c73f13cdc8c76551 Mon Sep 17 00:00:00 2001 From: dev Date: Fri, 11 Mar 2022 04:24:25 +0300 Subject: [PATCH 0106/1271] bootstrap predownload files were updated to height 1480000 --- src/common/pre_download.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/pre_download.h b/src/common/pre_download.h index 5feec36b..b6d4a74d 100644 --- a/src/common/pre_download.h +++ b/src/common/pre_download.h @@ -21,8 +21,8 @@ namespace tools }; #ifndef TESTNET - static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_1161000.pak", "26660ffcdaf80a43a586e64a1a6da042dcb9ff3b58e14ce1ec9a775b995dc146", 1330022593, 2684313600 }; - static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_1161000.pak", "9dd03f08dea396fe32e6483a8221b292be35fa41c29748f119f11c3275956cdc", 1787475468, 2600247296 }; + static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_1480000.pak", "2b664de02450cc0082efb6c75824d33ffe694b9b17b21fc7966bcb9be9ac31f7", 1570460883, 3221176320 }; + static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_1480000.pak", "67770faa7db22dfe97982611d7471ba5673145589e81e02ed99832644ae328f6", 2042582638, 2968252416 }; #else static constexpr pre_download_entry c_pre_download_mdbx = { "", "", 0, 0 }; static constexpr pre_download_entry c_pre_download_lmdb = { "", "", 0, 0 }; From 12a854834ba6abe9ccfe7e9d327eb17f0ff8f8ee Mon Sep 17 00:00:00 2001 From: zano build machine Date: Fri, 11 Mar 2022 04:26:34 +0300 Subject: [PATCH 0107/1271] === build number: 141 -> 142 === --- src/version.h.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/version.h.in b/src/version.h.in index 9c911483..a9c7f0d9 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -8,6 +8,6 @@ #define PROJECT_REVISION "1" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 141 +#define PROJECT_VERSION_BUILD_NO 142 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 60adbfad022b5546de625854cd57e645b836869e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 15 Mar 2022 21:12:51 +0200 Subject: [PATCH 0108/1271] fixed wallet history export feature --- src/wallet/wallet2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index cbceb80b..a2fa2813 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -3275,7 +3275,7 @@ void wallet2::wti_to_csv_entry(std::ostream& ss, const wallet_public::wallet_tra ss << (wti.is_income ? "in" : "out") << ","; ss << (wti.is_service ? "[SERVICE]" : "") << (wti.is_mixing ? "[MIXINS]" : "") << (wti.is_mining ? "[MINING]" : "") << ","; ss << wti.tx_type << ","; - ss << wti.fee << ENDL; + ss << print_money(wti.fee) << ENDL; }; void wallet2::wti_to_txt_line(std::ostream& ss, const wallet_public::wallet_transfer_info& wti, size_t index) @@ -3316,7 +3316,7 @@ void wallet2::export_transaction_history(std::ostream& ss, const std::string& fo else { //csv by default - ss << "N, Date, Amount, Comment, Address, ID, Height, Unlock timestamp, Tx size, Alias, In/Out, Flags, Type, Fee" << ENDL; + ss << "N, Date, Amount, Comment, Address, ID, Height, Unlock timestamp, Tx size, Alias, PaymentID, In/Out, Flags, Type, Fee" << ENDL; } @@ -3326,6 +3326,7 @@ void wallet2::export_transaction_history(std::ostream& ss, const std::string& fo if (currency::is_coinbase(wti.tx)) return true; } + wti.fee = currency::get_tx_fee(wti.tx); cb(ss, wti, index); return true; }); From 3ca1485646a83aa711fc73222db0ed2876baa1d7 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 17 Mar 2022 19:24:56 +0200 Subject: [PATCH 0109/1271] moved tor-client to latest commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index c980970b..d935b589 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit c980970bece66ef469ac8931ffa5ccac9264437d +Subproject commit d935b5893c182f8a1fc47213fdef89540e85602f From 7f82a13e627340fd6c010525357ec4a4211f8436 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 19 Mar 2022 04:12:13 +0200 Subject: [PATCH 0110/1271] changes in protocol for relayin over tor --- .../currency_protocol_defs.h | 28 ++++++++ src/wallet/wallet2.cpp | 72 ++++++++++++------- src/wallet/wallet2.h | 10 ++- 3 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/currency_protocol/currency_protocol_defs.h b/src/currency_protocol/currency_protocol_defs.h index 48f09e21..7b7d24f0 100644 --- a/src/currency_protocol/currency_protocol_defs.h +++ b/src/currency_protocol/currency_protocol_defs.h @@ -174,6 +174,34 @@ namespace currency }; }; + + /************************************************************************/ + /* */ + /************************************************************************/ + struct INVOKE_NEW_TRANSACTION + { + const static int ID = BC_COMMANDS_POOL_BASE + 8; + + struct request + { + blobdata tx; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(tx) + END_KV_SERIALIZE_MAP() + }; + + struct response + { + std::string code; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(code) + END_KV_SERIALIZE_MAP() + }; + + }; + } #include "currency_protocol_defs_print.h" diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 6735b1d9..587c1b44 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -57,7 +57,8 @@ namespace tools m_minimum_height(WALLET_MINIMUM_HEIGHT_UNSET_CONST), m_pos_mint_packing_size(WALLET_DEFAULT_POS_MINT_PACKING_SIZE), m_current_wallet_file_size(0), - m_use_deffered_global_outputs(false) + m_use_deffered_global_outputs(false), + m_disable_tor_relay(false) { m_core_runtime_config = currency::get_default_core_runtime_config(); } @@ -4586,39 +4587,60 @@ uint64_t wallet2::get_needed_money(uint64_t fee, const std::vectoron_tor_status_change(state_code); +} //---------------------------------------------------------------------------------------------------------------- void wallet2::send_transaction_to_network(const transaction& tx) { #define ENABLE_TOR_RELAY #ifdef ENABLE_TOR_RELAY - //TODO check that core synchronized - //epee::net_utils::levin_client2 p2p_client; - tools::levin_over_tor_client p2p_client; - if (!p2p_client.connect("144.76.183.143", 2121, 10000)) -// if (!p2p_client.connect("144.76.183.143", 1001, 10000)) + if (!m_disable_tor_relay) { - THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); + //TODO check that core synchronized + //epee::net_utils::levin_client2 p2p_client; + + //make few attempts + tools::levin_over_tor_client p2p_client; + p2p_client.get_transport().set_notifier(this); + for (size_t i = 0; i != 3; i++) + { + if (!p2p_client.connect("144.76.183.143", 2121, 10000)) + { + continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); + } + currency::NOTIFY_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); + p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); + std::string blob; + epee::serialization::store_t_to_binary(p2p_req, blob); + p2p_client.notify(NOTIFY_NEW_TRANSACTIONS::ID, blob); + p2p_client.disconnect(); + + //checking if transaction got relayed + //return; + } } - currency::NOTIFY_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); - p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); - std::string blob; - epee::serialization::store_t_to_binary(p2p_req, blob); + else + { + COMMAND_RPC_SEND_RAW_TX::request req; + req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); + COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; + bool r = m_core_proxy->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); + THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, error::daemon_busy, "sendrawtransaction"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, error::no_connection_to_daemon, "Transfer attempt while daemon offline"); + THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, error::tx_rejected, tx, daemon_send_resp.status); - p2p_client.notify(NOTIFY_NEW_TRANSACTIONS::ID, blob); - p2p_client.disconnect(); - return; + WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); + } #endif // - COMMAND_RPC_SEND_RAW_TX::request req; - req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); - COMMAND_RPC_SEND_RAW_TX::response daemon_send_resp; - bool r = m_core_proxy->call_COMMAND_RPC_SEND_RAW_TX(req, daemon_send_resp); - THROW_IF_TRUE_WALLET_EX(!r, error::no_connection_to_daemon, "sendrawtransaction"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_BUSY, error::daemon_busy, "sendrawtransaction"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status == API_RETURN_CODE_DISCONNECTED, error::no_connection_to_daemon, "Transfer attempt while daemon offline"); - THROW_IF_TRUE_WALLET_EX(daemon_send_resp.status != API_RETURN_CODE_OK, error::tx_rejected, tx, daemon_send_resp.status); - - WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); } //---------------------------------------------------------------------------------------------------------------- void wallet2::add_sent_tx_detailed_info(const transaction& tx, diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index fdf2afad..f4f4736d 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -121,6 +121,7 @@ namespace tools virtual void on_sync_progress(const uint64_t& /*percents*/) {} virtual void on_transfer_canceled(const wallet_public::wallet_transfer_info& wti) {} virtual void on_message(message_severity /*severity*/, const std::string& /*m*/) {} + virtual void on_tor_status_change(const std::string& state) {} }; struct tx_dust_policy @@ -338,7 +339,7 @@ namespace tools // END_SERIALIZE() // }; - class wallet2 + class wallet2: public tools::tor::t_transport_state_notifier { wallet2(const wallet2&) = delete; public: @@ -861,6 +862,7 @@ namespace tools uint64_t get_wallet_file_size()const; void set_use_deffered_global_outputs(bool use); construct_tx_param get_default_construct_tx_param_inital(); + void set_disable_tor_relay(bool disable); void export_transaction_history(std::ostream& ss, const std::string& format, bool include_pos_transactions = true); @@ -876,6 +878,11 @@ namespace tools bool check_htlc_redeemed(const crypto::hash& htlc_tx_id, std::string& origin, crypto::hash& redeem_tx_id); private: + // -------- t_transport_state_notifier ------------------------------------------------ + virtual void notify_state_change(const std::string& state_code, const std::string& details = std::string()); + // ------------------------------------------------------------------------------------ + + 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); void load_keys(const std::string& keys_file_name, const std::string& password, uint64_t file_signature, keys_file_data& kf_data); @@ -1060,6 +1067,7 @@ private: mutable uint64_t m_current_wallet_file_size; bool m_use_deffered_global_outputs; + bool m_disable_tor_relay; //this needed to access wallets state in coretests, for creating abnormal blocks and tranmsactions friend class test_generator; From 63fe35d5c511dfc94613ddd333dff81cf651a55e Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 20 Mar 2022 03:09:28 +0300 Subject: [PATCH 0111/1271] warning fix in threads pool --- src/common/threads_pool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h index 4e4e3905..b23c15b5 100644 --- a/src/common/threads_pool.h +++ b/src/common/threads_pool.h @@ -55,7 +55,7 @@ namespace utils { m_is_stop = false; - for (int i = 0; i < num_threads; i++) + for (unsigned int i = 0; i < num_threads; i++) { m_threads.push_back(std::thread([this]() {this->worker_func(); })); } From b6fca25a1026a27743a140d193421e6664ca3519 Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 20 Mar 2022 03:22:46 +0300 Subject: [PATCH 0112/1271] boost placeholders fix --- tests/core_tests/chaingen.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index 7cbf4822..e6b9fb02 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -997,10 +997,10 @@ void append_vector_by_another_vector(U& dst, const V& src) #define REGISTER_CALLBACK(CB_NAME, CLBACK) \ - register_callback(CB_NAME, boost::bind(&CLBACK, this, _1, _2, _3)); + register_callback(CB_NAME, boost::bind(&CLBACK, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3)) #define REGISTER_CALLBACK_METHOD(CLASS, METHOD) \ - register_callback(#METHOD, boost::bind(&CLASS::METHOD, this, _1, _2, _3)); + register_callback(#METHOD, boost::bind(&CLASS::METHOD, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3)) #define MAKE_GENESIS_BLOCK(VEC_EVENTS, BLK_NAME, MINER_ACC, TS) \ test_generator generator; \ From 02544f718657b314df856f972207b391fa58b13b Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 20 Mar 2022 04:05:15 +0300 Subject: [PATCH 0113/1271] boost placeholders fix (2) --- .../epee/include/net/abstract_tcp_server2.inl | 2 +- .../include/storages/levin_abstract_invoke2.h | 8 +- src/common/ntp.cpp | 2 +- src/daemon/daemon_commands_handler.h | 87 ++++++++++--------- src/simplewallet/simplewallet.cpp | 77 ++++++++-------- 5 files changed, 90 insertions(+), 86 deletions(-) diff --git a/contrib/epee/include/net/abstract_tcp_server2.inl b/contrib/epee/include/net/abstract_tcp_server2.inl index 2bb45f69..6829b645 100644 --- a/contrib/epee/include/net/abstract_tcp_server2.inl +++ b/contrib/epee/include/net/abstract_tcp_server2.inl @@ -693,7 +693,7 @@ bool boosted_tcp_server::connect(const std::string& adr, con shared_context->cond.notify_one(); }; - sock_.async_connect(remote_endpoint, boost::bind(connect_callback, _1, local_shared_context)); + sock_.async_connect(remote_endpoint, boost::bind(connect_callback, boost::placeholders::_1, local_shared_context)); while(local_shared_context->ec == boost::asio::error::would_block) { bool r = false; try { diff --git a/contrib/epee/include/storages/levin_abstract_invoke2.h b/contrib/epee/include/storages/levin_abstract_invoke2.h index 1b2c74a4..4c1c5569 100644 --- a/contrib/epee/include/storages/levin_abstract_invoke2.h +++ b/contrib/epee/include/storages/levin_abstract_invoke2.h @@ -267,20 +267,20 @@ namespace epee #define HANDLE_INVOKE2(command_id, func, type_name_in, typename_out) \ if(!is_notify && command_id == command) \ - {handled=true;return epee::net_utils::buff_to_t_adapter(this, command, in_buff, buff_out, boost::bind(func, this, _1, _2, _3, _4), context);} + {handled=true;return epee::net_utils::buff_to_t_adapter(this, command, in_buff, buff_out, boost::bind(func, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3, boost::placeholders::_4), context);} #define HANDLE_INVOKE_T2(COMMAND, func) \ if(!is_notify && COMMAND::ID == command) \ - {handled=true;return epee::net_utils::buff_to_t_adapter(command, in_buff, buff_out, boost::bind(func, this, _1, _2, _3, _4), context);} + {handled=true;return epee::net_utils::buff_to_t_adapter(command, in_buff, buff_out, boost::bind(func, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3, boost::placeholders::_4), context);} #define HANDLE_NOTIFY2(command_id, func, type_name_in) \ if(is_notify && command_id == command) \ - {handled=true;return epee::net_utils::buff_to_t_adapter(this, command, in_buff, boost::bind(func, this, _1, _2, _3), context);} + {handled=true;return epee::net_utils::buff_to_t_adapter(this, command, in_buff, boost::bind(func, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3), context);} #define HANDLE_NOTIFY_T2(NOTIFY, func) \ if(is_notify && NOTIFY::ID == command) \ - {handled=true;return epee::net_utils::buff_to_t_adapter(this, command, in_buff, boost::bind(func, this, _1, _2, _3), context);} + {handled=true;return epee::net_utils::buff_to_t_adapter(this, command, in_buff, boost::bind(func, this, boost::placeholders::_1, boost::placeholders::_2, boost::placeholders::_3), context);} #define CHAIN_INVOKE_MAP2(func) \ diff --git a/src/common/ntp.cpp b/src/common/ntp.cpp index fdbc2a5b..b7f8a076 100644 --- a/src/common/ntp.cpp +++ b/src/common/ntp.cpp @@ -94,7 +94,7 @@ public: // Start the asynchronous operation itself. The handle_receive function // used as a callback will update the ec and length variables. socket_.async_receive(boost::asio::buffer(buffer), - boost::bind(&udp_blocking_client::handle_receive, _1, _2, &ec, &length)); + boost::bind(&udp_blocking_client::handle_receive, boost::placeholders::_1, boost::placeholders::_2, &ec, &length)); // Block until the asynchronous operation has completed. do io_service_.run_one(); while (ec == boost::asio::error::would_block); diff --git a/src/daemon/daemon_commands_handler.h b/src/daemon/daemon_commands_handler.h index 4573b806..78d2ffef 100644 --- a/src/daemon/daemon_commands_handler.h +++ b/src/daemon/daemon_commands_handler.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include "console_handler.h" #include "p2p/net_node.h" @@ -18,6 +19,8 @@ #include "serialization/binary_utils.h" #include "simplewallet/password_container.h" +namespace ph = boost::placeholders; + PUSH_VS_WARNINGS DISABLE_VS_WARNINGS(4100) @@ -31,49 +34,49 @@ class daemon_commands_handler public: daemon_commands_handler(nodetool::node_server >& srv, currency::core_rpc_server& rpc) :m_srv(srv), m_rpc(rpc) { - m_cmd_binder.set_handler("help", boost::bind(&console_handlers_binder::help, &m_cmd_binder, _1), "Show this help"); - m_cmd_binder.set_handler("print_pl", boost::bind(&daemon_commands_handler::print_pl, this, _1), "Print peer list"); - m_cmd_binder.set_handler("print_cn", boost::bind(&daemon_commands_handler::print_cn, this, _1), "Print connections"); - m_cmd_binder.set_handler("print_bc", boost::bind(&daemon_commands_handler::print_bc, this, _1), "Print blockchain info in a given blocks range, print_bc []"); - m_cmd_binder.set_handler("print_bc_tx", boost::bind(&daemon_commands_handler::print_bc_tx, this, _1), "Print blockchain info with trnsactions in a given blocks range, print_bc []"); - //m_cmd_binder.set_handler("print_bci", boost::bind(&daemon_commands_handler::print_bci, this, _1)); - m_cmd_binder.set_handler("print_bc_outs", boost::bind(&daemon_commands_handler::print_bc_outs, this, _1)); - m_cmd_binder.set_handler("print_market", boost::bind(&daemon_commands_handler::print_market, this, _1)); - m_cmd_binder.set_handler("print_bc_outs_stats", boost::bind(&daemon_commands_handler::print_bc_outs_stats, this, _1)); - m_cmd_binder.set_handler("print_block", boost::bind(&daemon_commands_handler::print_block, this, _1), "Print block, print_block | "); - m_cmd_binder.set_handler("print_block_info", boost::bind(&daemon_commands_handler::print_block_info, this, _1), "Print block info, print_block | "); - m_cmd_binder.set_handler("print_tx_prun_info", boost::bind(&daemon_commands_handler::print_tx_prun_info, this, _1), "Print tx prunning info"); - m_cmd_binder.set_handler("print_tx", boost::bind(&daemon_commands_handler::print_tx, this, _1), "Print transaction, print_tx "); - m_cmd_binder.set_handler("start_mining", boost::bind(&daemon_commands_handler::start_mining, this, _1), "Start mining for specified address, start_mining [threads=1]"); - m_cmd_binder.set_handler("stop_mining", boost::bind(&daemon_commands_handler::stop_mining, this, _1), "Stop mining"); - m_cmd_binder.set_handler("print_pool", boost::bind(&daemon_commands_handler::print_pool, this, _1), "Print transaction pool (long format)"); - m_cmd_binder.set_handler("print_pool_sh", boost::bind(&daemon_commands_handler::print_pool_sh, this, _1), "Print transaction pool (short format)"); - m_cmd_binder.set_handler("show_hr", boost::bind(&daemon_commands_handler::show_hr, this, _1), "Start showing hash rate"); - m_cmd_binder.set_handler("hide_hr", boost::bind(&daemon_commands_handler::hide_hr, this, _1), "Stop showing hash rate"); - m_cmd_binder.set_handler("save", boost::bind(&daemon_commands_handler::save, this, _1), "Save blockchain"); - m_cmd_binder.set_handler("print_daemon_stat", boost::bind(&daemon_commands_handler::print_daemon_stat, this, _1), "Print daemon stat"); - m_cmd_binder.set_handler("print_debug_stat", boost::bind(&daemon_commands_handler::print_debug_stat, this, _1), "Print debug stat info"); - m_cmd_binder.set_handler("get_transactions_statics", boost::bind(&daemon_commands_handler::get_transactions_statistics, this, _1), "Calculates transactions statistics"); - m_cmd_binder.set_handler("force_relay_tx_pool", boost::bind(&daemon_commands_handler::force_relay_tx_pool, this, _1), "re-relay all transactions from pool"); - m_cmd_binder.set_handler("enable_channel", boost::bind(&daemon_commands_handler::enable_channel, this, _1), "Enable specified log channel"); - m_cmd_binder.set_handler("disable_channel", boost::bind(&daemon_commands_handler::disable_channel, this, _1), "Enable specified log channel"); - m_cmd_binder.set_handler("clear_cache", boost::bind(&daemon_commands_handler::clear_cache, this, _1), "Clear blockchain storage cache"); - m_cmd_binder.set_handler("clear_altblocks", boost::bind(&daemon_commands_handler::clear_altblocks, this, _1), "Clear blockchain storage cache"); - m_cmd_binder.set_handler("truncate_bc", boost::bind(&daemon_commands_handler::truncate_bc, this, _1), "Truncate blockchain to specified height"); - m_cmd_binder.set_handler("inspect_block_index", boost::bind(&daemon_commands_handler::inspect_block_index, this, _1), "Inspects block index for internal errors"); - m_cmd_binder.set_handler("print_db_performance_data", boost::bind(&daemon_commands_handler::print_db_performance_data, this, _1), "Dumps all db containers performance counters"); - m_cmd_binder.set_handler("search_by_id", boost::bind(&daemon_commands_handler::search_by_id, this, _1), "Search all possible elemets by given id"); - m_cmd_binder.set_handler("find_key_image", boost::bind(&daemon_commands_handler::find_key_image, this, _1), "Try to find tx related to key_image"); - m_cmd_binder.set_handler("rescan_aliases", boost::bind(&daemon_commands_handler::rescan_aliases, this, _1), "Debug function"); - m_cmd_binder.set_handler("forecast_difficulty", boost::bind(&daemon_commands_handler::forecast_difficulty, this, _1), "Prints PoW and PoS difficulties for as many future blocks as possible based on current conditions"); - m_cmd_binder.set_handler("print_deadlock_guard", boost::bind(&daemon_commands_handler::print_deadlock_guard, this, _1), "Print all threads which is blocked or involved in mutex ownership"); - m_cmd_binder.set_handler("print_block_from_hex_blob", boost::bind(&daemon_commands_handler::print_block_from_hex_blob, this, _1), "Unserialize block from hex binary data to json-like representation"); - m_cmd_binder.set_handler("print_tx_from_hex_blob", boost::bind(&daemon_commands_handler::print_tx_from_hex_blob, this, _1), "Unserialize transaction from hex binary data to json-like representation"); - m_cmd_binder.set_handler("print_tx_outputs_usage", boost::bind(&daemon_commands_handler::print_tx_outputs_usage, this, _1), "Analyse if tx outputs for involved in subsequent transactions"); - m_cmd_binder.set_handler("print_difficulties_of_last_n_blocks", boost::bind(&daemon_commands_handler::print_difficulties_of_last_n_blocks, this, _1), "Print difficulties of last n blocks"); - m_cmd_binder.set_handler("debug_remote_node_mode", boost::bind(&daemon_commands_handler::debug_remote_node_mode, this, _1), " - If node got connected put node into 'debug mode' i.e. no sync process of other communication except ping responses, maintenance secrete key will be requested"); + m_cmd_binder.set_handler("help", boost::bind(&console_handlers_binder::help, &m_cmd_binder, ph::_1), "Show this help"); + m_cmd_binder.set_handler("print_pl", boost::bind(&daemon_commands_handler::print_pl, this, ph::_1), "Print peer list"); + m_cmd_binder.set_handler("print_cn", boost::bind(&daemon_commands_handler::print_cn, this, ph::_1), "Print connections"); + m_cmd_binder.set_handler("print_bc", boost::bind(&daemon_commands_handler::print_bc, this, ph::_1), "Print blockchain info in a given blocks range, print_bc []"); + m_cmd_binder.set_handler("print_bc_tx", boost::bind(&daemon_commands_handler::print_bc_tx, this, ph::_1), "Print blockchain info with trnsactions in a given blocks range, print_bc []"); + //m_cmd_binder.set_handler("print_bci", boost::bind(&daemon_commands_handler::print_bci, this, ph::_1)); + m_cmd_binder.set_handler("print_bc_outs", boost::bind(&daemon_commands_handler::print_bc_outs, this, ph::_1)); + m_cmd_binder.set_handler("print_market", boost::bind(&daemon_commands_handler::print_market, this, ph::_1)); + m_cmd_binder.set_handler("print_bc_outs_stats", boost::bind(&daemon_commands_handler::print_bc_outs_stats, this, ph::_1)); + m_cmd_binder.set_handler("print_block", boost::bind(&daemon_commands_handler::print_block, this, ph::_1), "Print block, print_block | "); + m_cmd_binder.set_handler("print_block_info", boost::bind(&daemon_commands_handler::print_block_info, this, ph::_1), "Print block info, print_block | "); + m_cmd_binder.set_handler("print_tx_prun_info", boost::bind(&daemon_commands_handler::print_tx_prun_info, this, ph::_1), "Print tx prunning info"); + m_cmd_binder.set_handler("print_tx", boost::bind(&daemon_commands_handler::print_tx, this, ph::_1), "Print transaction, print_tx "); + m_cmd_binder.set_handler("start_mining", boost::bind(&daemon_commands_handler::start_mining, this, ph::_1), "Start mining for specified address, start_mining [threads=1]"); + m_cmd_binder.set_handler("stop_mining", boost::bind(&daemon_commands_handler::stop_mining, this, ph::_1), "Stop mining"); + m_cmd_binder.set_handler("print_pool", boost::bind(&daemon_commands_handler::print_pool, this, ph::_1), "Print transaction pool (long format)"); + m_cmd_binder.set_handler("print_pool_sh", boost::bind(&daemon_commands_handler::print_pool_sh, this, ph::_1), "Print transaction pool (short format)"); + m_cmd_binder.set_handler("show_hr", boost::bind(&daemon_commands_handler::show_hr, this, ph::_1), "Start showing hash rate"); + m_cmd_binder.set_handler("hide_hr", boost::bind(&daemon_commands_handler::hide_hr, this, ph::_1), "Stop showing hash rate"); + m_cmd_binder.set_handler("save", boost::bind(&daemon_commands_handler::save, this, ph::_1), "Save blockchain"); + m_cmd_binder.set_handler("print_daemon_stat", boost::bind(&daemon_commands_handler::print_daemon_stat, this, ph::_1), "Print daemon stat"); + m_cmd_binder.set_handler("print_debug_stat", boost::bind(&daemon_commands_handler::print_debug_stat, this, ph::_1), "Print debug stat info"); + m_cmd_binder.set_handler("get_transactions_statics", boost::bind(&daemon_commands_handler::get_transactions_statistics, this, ph::_1), "Calculates transactions statistics"); + m_cmd_binder.set_handler("force_relay_tx_pool", boost::bind(&daemon_commands_handler::force_relay_tx_pool, this, ph::_1), "re-relay all transactions from pool"); + m_cmd_binder.set_handler("enable_channel", boost::bind(&daemon_commands_handler::enable_channel, this, ph::_1), "Enable specified log channel"); + m_cmd_binder.set_handler("disable_channel", boost::bind(&daemon_commands_handler::disable_channel, this, ph::_1), "Enable specified log channel"); + m_cmd_binder.set_handler("clear_cache", boost::bind(&daemon_commands_handler::clear_cache, this, ph::_1), "Clear blockchain storage cache"); + m_cmd_binder.set_handler("clear_altblocks", boost::bind(&daemon_commands_handler::clear_altblocks, this, ph::_1), "Clear blockchain storage cache"); + m_cmd_binder.set_handler("truncate_bc", boost::bind(&daemon_commands_handler::truncate_bc, this, ph::_1), "Truncate blockchain to specified height"); + m_cmd_binder.set_handler("inspect_block_index", boost::bind(&daemon_commands_handler::inspect_block_index, this, ph::_1), "Inspects block index for internal errors"); + m_cmd_binder.set_handler("print_db_performance_data", boost::bind(&daemon_commands_handler::print_db_performance_data, this, ph::_1), "Dumps all db containers performance counters"); + m_cmd_binder.set_handler("search_by_id", boost::bind(&daemon_commands_handler::search_by_id, this, ph::_1), "Search all possible elemets by given id"); + m_cmd_binder.set_handler("find_key_image", boost::bind(&daemon_commands_handler::find_key_image, this, ph::_1), "Try to find tx related to key_image"); + m_cmd_binder.set_handler("rescan_aliases", boost::bind(&daemon_commands_handler::rescan_aliases, this, ph::_1), "Debug function"); + m_cmd_binder.set_handler("forecast_difficulty", boost::bind(&daemon_commands_handler::forecast_difficulty, this, ph::_1), "Prints PoW and PoS difficulties for as many future blocks as possible based on current conditions"); + m_cmd_binder.set_handler("print_deadlock_guard", boost::bind(&daemon_commands_handler::print_deadlock_guard, this, ph::_1), "Print all threads which is blocked or involved in mutex ownership"); + m_cmd_binder.set_handler("print_block_from_hex_blob", boost::bind(&daemon_commands_handler::print_block_from_hex_blob, this, ph::_1), "Unserialize block from hex binary data to json-like representation"); + m_cmd_binder.set_handler("print_tx_from_hex_blob", boost::bind(&daemon_commands_handler::print_tx_from_hex_blob, this, ph::_1), "Unserialize transaction from hex binary data to json-like representation"); + m_cmd_binder.set_handler("print_tx_outputs_usage", boost::bind(&daemon_commands_handler::print_tx_outputs_usage, this, ph::_1), "Analyse if tx outputs for involved in subsequent transactions"); + m_cmd_binder.set_handler("print_difficulties_of_last_n_blocks", boost::bind(&daemon_commands_handler::print_difficulties_of_last_n_blocks, this, ph::_1), "Print difficulties of last n blocks"); + m_cmd_binder.set_handler("debug_remote_node_mode", boost::bind(&daemon_commands_handler::debug_remote_node_mode, this, ph::_1), " - If node got connected put node into 'debug mode' i.e. no sync process of other communication except ping responses, maintenance secrete key will be requested"); #ifdef _DEBUG - m_cmd_binder.set_handler("debug_set_time_adj", boost::bind(&daemon_commands_handler::debug_set_time_adj, this, _1), "DEBUG: set core time adjustment"); + m_cmd_binder.set_handler("debug_set_time_adj", boost::bind(&daemon_commands_handler::debug_set_time_adj, this, ph::_1), "DEBUG: set core time adjustment"); #endif } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index cfb48a64..fe277c72 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -33,6 +33,7 @@ using namespace epee; using namespace currency; using boost::lexical_cast; namespace po = boost::program_options; +namespace ph = boost::placeholders; #define EXTENDED_LOGS_FILE "wallet_details.log" @@ -186,49 +187,49 @@ simple_wallet::simple_wallet() m_refresh_progress_reporter(*this), m_offline_mode(false) { - m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, _1), "start_mining - Start mining in daemon"); - m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, _1), "Stop mining in daemon"); - m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, _1), "Resynchronize transactions and balance"); - m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, _1), "Show current wallet balance"); - m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, _1), "incoming_transfers [available|unavailable] - Show incoming transfers - all of them or filter them by availability"); - m_cmd_binder.set_handler("incoming_counts", boost::bind(&simple_wallet::show_incoming_transfers_counts, this, _1), "incoming_transfers counts"); - m_cmd_binder.set_handler("list_recent_transfers", boost::bind(&simple_wallet::list_recent_transfers, this, _1), "list_recent_transfers [offset] [count] - Show recent maximum 1000 transfers, offset default = 0, count default = 100 "); - m_cmd_binder.set_handler("export_recent_transfers", boost::bind(&simple_wallet::export_recent_transfers, this, _1), "list_recent_transfers_tx - Write recent transfer in json to wallet_recent_transfers.txt"); - m_cmd_binder.set_handler("list_outputs", boost::bind(&simple_wallet::list_outputs, this, _1), "list_outputs [spent|unspent] - Lists all the outputs that have ever been sent to this wallet if called without arguments, otherwise it lists only the spent or unspent outputs"); - m_cmd_binder.set_handler("dump_transfers", boost::bind(&simple_wallet::dump_trunsfers, this, _1), "dump_transfers - Write transfers in json to dump_transfers.txt"); - m_cmd_binder.set_handler("dump_keyimages", boost::bind(&simple_wallet::dump_key_images, this, _1), "dump_keyimages - Write key_images in json to dump_key_images.txt"); - m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, _1), "payments [ ... ] - Show payments , ... "); - m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this, _1), "Show blockchain height"); - m_cmd_binder.set_handler("wallet_bc_height", boost::bind(&simple_wallet::show_wallet_bcheight, this, _1), "Show blockchain height"); - m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this, _1), "transfer [ ... ] [payment_id] - Transfer ,... to ,... , respectively. is the number of transactions yours is indistinguishable from (from 0 to maximum available), is an optional HEX-encoded string"); - m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this, _1), "set_log - Change current log detalisation level, is a number 0-4"); - m_cmd_binder.set_handler("enable_console_logger", boost::bind(&simple_wallet::enable_console_logger, this, _1), "Enables console logging"); - m_cmd_binder.set_handler("resync", boost::bind(&simple_wallet::resync_wallet, this, _1), "Causes wallet to reset all transfers and re-synchronize wallet"); - m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this, _1), "Show this help"); - m_cmd_binder.set_handler("get_transfer_info", boost::bind(&simple_wallet::get_transfer_info, this, _1), "displays transfer info by key_image or index"); - m_cmd_binder.set_handler("scan_for_collision", boost::bind(&simple_wallet::scan_for_key_image_collisions, this, _1), "Rescan transfers for key image collisions"); - m_cmd_binder.set_handler("fix_collisions", boost::bind(&simple_wallet::fix_collisions, this, _1), "Rescan transfers for key image collisions"); - m_cmd_binder.set_handler("scan_transfers_for_id", boost::bind(&simple_wallet::scan_transfers_for_id, this, _1), "Rescan transfers for tx_id"); - m_cmd_binder.set_handler("scan_transfers_for_ki", boost::bind(&simple_wallet::scan_transfers_for_ki, this, _1), "Rescan transfers for key image"); - m_cmd_binder.set_handler("print_utxo_distribution", boost::bind(&simple_wallet::print_utxo_distribution, this, _1), "Prints utxo distribution"); - m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this, _1), "sweep_below

[payment_id] - Tries to transfers all coins with amount below the given limit to the given address"); + m_cmd_binder.set_handler("start_mining", boost::bind(&simple_wallet::start_mining, this, ph::_1), "start_mining - Start mining in daemon"); + m_cmd_binder.set_handler("stop_mining", boost::bind(&simple_wallet::stop_mining, this, ph::_1), "Stop mining in daemon"); + m_cmd_binder.set_handler("refresh", boost::bind(&simple_wallet::refresh, this, ph::_1), "Resynchronize transactions and balance"); + m_cmd_binder.set_handler("balance", boost::bind(&simple_wallet::show_balance, this, ph::_1), "Show current wallet balance"); + m_cmd_binder.set_handler("incoming_transfers", boost::bind(&simple_wallet::show_incoming_transfers, this, ph::_1), "incoming_transfers [available|unavailable] - Show incoming transfers - all of them or filter them by availability"); + m_cmd_binder.set_handler("incoming_counts", boost::bind(&simple_wallet::show_incoming_transfers_counts, this, ph::_1), "incoming_transfers counts"); + m_cmd_binder.set_handler("list_recent_transfers", boost::bind(&simple_wallet::list_recent_transfers, this, ph::_1), "list_recent_transfers [offset] [count] - Show recent maximum 1000 transfers, offset default = 0, count default = 100 "); + m_cmd_binder.set_handler("export_recent_transfers", boost::bind(&simple_wallet::export_recent_transfers, this, ph::_1), "list_recent_transfers_tx - Write recent transfer in json to wallet_recent_transfers.txt"); + m_cmd_binder.set_handler("list_outputs", boost::bind(&simple_wallet::list_outputs, this, ph::_1), "list_outputs [spent|unspent] - Lists all the outputs that have ever been sent to this wallet if called without arguments, otherwise it lists only the spent or unspent outputs"); + m_cmd_binder.set_handler("dump_transfers", boost::bind(&simple_wallet::dump_trunsfers, this, ph::_1), "dump_transfers - Write transfers in json to dump_transfers.txt"); + m_cmd_binder.set_handler("dump_keyimages", boost::bind(&simple_wallet::dump_key_images, this, ph::_1), "dump_keyimages - Write key_images in json to dump_key_images.txt"); + m_cmd_binder.set_handler("payments", boost::bind(&simple_wallet::show_payments, this, ph::_1), "payments [ ... ] - Show payments , ... "); + m_cmd_binder.set_handler("bc_height", boost::bind(&simple_wallet::show_blockchain_height, this,ph::_1), "Show blockchain height"); + m_cmd_binder.set_handler("wallet_bc_height", boost::bind(&simple_wallet::show_wallet_bcheight, this,ph::_1), "Show blockchain height"); + m_cmd_binder.set_handler("transfer", boost::bind(&simple_wallet::transfer, this,ph::_1), "transfer [ ... ] [payment_id] - Transfer ,... to ,... , respectively. is the number of transactions yours is indistinguishable from (from 0 to maximum available), is an optional HEX-encoded string"); + m_cmd_binder.set_handler("set_log", boost::bind(&simple_wallet::set_log, this,ph::_1), "set_log - Change current log detalisation level, is a number 0-4"); + m_cmd_binder.set_handler("enable_console_logger", boost::bind(&simple_wallet::enable_console_logger, this,ph::_1), "Enables console logging"); + m_cmd_binder.set_handler("resync", boost::bind(&simple_wallet::resync_wallet, this,ph::_1), "Causes wallet to reset all transfers and re-synchronize wallet"); + m_cmd_binder.set_handler("help", boost::bind(&simple_wallet::help, this,ph::_1), "Show this help"); + m_cmd_binder.set_handler("get_transfer_info", boost::bind(&simple_wallet::get_transfer_info, this,ph::_1), "displays transfer info by key_image or index"); + m_cmd_binder.set_handler("scan_for_collision", boost::bind(&simple_wallet::scan_for_key_image_collisions, this,ph::_1), "Rescan transfers for key image collisions"); + m_cmd_binder.set_handler("fix_collisions", boost::bind(&simple_wallet::fix_collisions, this,ph::_1), "Rescan transfers for key image collisions"); + m_cmd_binder.set_handler("scan_transfers_for_id", boost::bind(&simple_wallet::scan_transfers_for_id, this,ph::_1), "Rescan transfers for tx_id"); + m_cmd_binder.set_handler("scan_transfers_for_ki", boost::bind(&simple_wallet::scan_transfers_for_ki, this,ph::_1), "Rescan transfers for key image"); + m_cmd_binder.set_handler("print_utxo_distribution", boost::bind(&simple_wallet::print_utxo_distribution, this,ph::_1), "Prints utxo distribution"); + m_cmd_binder.set_handler("sweep_below", boost::bind(&simple_wallet::sweep_below, this,ph::_1), "sweep_below
[payment_id] - Tries to transfers all coins with amount below the given limit to the given address"); - m_cmd_binder.set_handler("address", boost::bind(&simple_wallet::print_address, this, _1), "Show current wallet public address"); - m_cmd_binder.set_handler("integrated_address", boost::bind(&simple_wallet::integrated_address, this, _1), "integrated_address [||"); + m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this,ph::_1), "Get transaction one-time secret key (r) for a given "); - m_cmd_binder.set_handler("tracking_seed", boost::bind(&simple_wallet::tracking_seed, this, _1), "For auditable wallets: prints tracking seed for wallet's audit by a third party"); + m_cmd_binder.set_handler("tracking_seed", boost::bind(&simple_wallet::tracking_seed, this,ph::_1), "For auditable wallets: prints tracking seed for wallet's audit by a third party"); - m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this, _1), "Save wallet synchronized data"); - m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this, _1), "save_watch_only - save as watch-only wallet file."); + m_cmd_binder.set_handler("save", boost::bind(&simple_wallet::save, this,ph::_1), "Save wallet synchronized data"); + m_cmd_binder.set_handler("save_watch_only", boost::bind(&simple_wallet::save_watch_only, this,ph::_1), "save_watch_only - save as watch-only wallet file."); - m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), "sign_transfer - sign unsigned tx from a watch-only wallet"); - m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), "submit_transfer - broadcast signed tx"); - m_cmd_binder.set_handler("export_history", boost::bind(&simple_wallet::submit_transfer, this, _1), "Export transaction history in CSV file"); + m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this,ph::_1), "sign_transfer - sign unsigned tx from a watch-only wallet"); + m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this,ph::_1), "submit_transfer - broadcast signed tx"); + m_cmd_binder.set_handler("export_history", boost::bind(&simple_wallet::submit_transfer, this,ph::_1), "Export transaction history in CSV file"); } //---------------------------------------------------------------------------------------------------- simple_wallet::~simple_wallet() From 2246aa8fcec48146d9a9e51f374df69d65d47608 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Mar 2022 01:20:56 +0300 Subject: [PATCH 0114/1271] fix an issue with boost 1.76 --- .../include/serialization/keyvalue_serialization_overloads.h | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 01f9e257..d33d5ad1 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -27,6 +27,7 @@ #pragma once #include +#include #include namespace epee From 72bfd897178ca87ca69e9e52e221a4d3ed697257 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 21 Mar 2022 16:47:11 +0200 Subject: [PATCH 0115/1271] improved p2p protocol for relaying tx over tor --- src/currency_core/currency_config.h | 2 + src/currency_core/tx_pool.cpp | 2 +- .../currency_protocol_defs.h | 40 ++++-------- .../currency_protocol_handler.h | 15 +++-- .../currency_protocol_handler.inl | 61 ++++++++++++++----- .../currency_protocol_handler_common.h | 4 +- src/rpc/core_rpc_server.cpp | 4 +- src/wallet/wallet2.cpp | 7 ++- tests/core_tests/test_core_proxy.h | 2 +- 9 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 29e76ab1..18e38c67 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -250,6 +250,8 @@ + static_assert(CURRENCY_MINER_TX_MAX_OUTS <= CURRENCY_TX_MAX_ALLOWED_OUTS, "Miner tx must obey normal tx max outs limit"); static_assert(PREMINE_AMOUNT / WALLET_MAX_ALLOWED_OUTPUT_AMOUNT < CURRENCY_MINER_TX_MAX_OUTS, "Premine can't be divided into reasonable number of outs"); +#define CURRENCY_RELAY_TXS_MAX_COUNT 5 \ No newline at end of file diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 7a65990e..581dfdb8 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -666,7 +666,7 @@ namespace currency bool tx_memory_pool::force_relay_pool() const { LOG_PRINT_GREEN("Preparing relay message...", LOG_LEVEL_0); - NOTIFY_NEW_TRANSACTIONS::request r = AUTO_VAL_INIT(r); + NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request r = AUTO_VAL_INIT(r); m_db_transactions.enumerate_items([&](uint64_t i, const crypto::hash& k, const tx_details& v) { diff --git a/src/currency_protocol/currency_protocol_defs.h b/src/currency_protocol/currency_protocol_defs.h index 7b7d24f0..2a0eead6 100644 --- a/src/currency_protocol/currency_protocol_defs.h +++ b/src/currency_protocol/currency_protocol_defs.h @@ -71,7 +71,7 @@ namespace currency /************************************************************************/ /* */ /************************************************************************/ - struct NOTIFY_NEW_TRANSACTIONS + struct NOTIFY_OR_INVOKE_NEW_TRANSACTIONS { const static int ID = BC_COMMANDS_POOL_BASE + 2; @@ -83,6 +83,16 @@ namespace currency KV_SERIALIZE(txs) END_KV_SERIALIZE_MAP() }; + + struct response + { + std::string code; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(code) + END_KV_SERIALIZE_MAP() + }; + }; /************************************************************************/ /* */ @@ -174,34 +184,6 @@ namespace currency }; }; - - /************************************************************************/ - /* */ - /************************************************************************/ - struct INVOKE_NEW_TRANSACTION - { - const static int ID = BC_COMMANDS_POOL_BASE + 8; - - struct request - { - blobdata tx; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(tx) - END_KV_SERIALIZE_MAP() - }; - - struct response - { - std::string code; - - BEGIN_KV_SERIALIZE_MAP() - KV_SERIALIZE(code) - END_KV_SERIALIZE_MAP() - }; - - }; - } #include "currency_protocol_defs_print.h" diff --git a/src/currency_protocol/currency_protocol_handler.h b/src/currency_protocol/currency_protocol_handler.h index cae17241..1e420919 100644 --- a/src/currency_protocol/currency_protocol_handler.h +++ b/src/currency_protocol/currency_protocol_handler.h @@ -35,14 +35,15 @@ namespace currency typedef core_stat_info stat_info; typedef t_currency_protocol_handler currency_protocol_handler; typedef CORE_SYNC_DATA payload_type; - typedef std::pair relay_que_entry; + typedef std::pair relay_que_entry; t_currency_protocol_handler(t_core& rcore, nodetool::i_p2p_endpoint* p_net_layout); ~t_currency_protocol_handler(); BEGIN_INVOKE_MAP2(currency_protocol_handler) HANDLE_NOTIFY_T2(NOTIFY_NEW_BLOCK, ¤cy_protocol_handler::handle_notify_new_block) - HANDLE_NOTIFY_T2(NOTIFY_NEW_TRANSACTIONS, ¤cy_protocol_handler::handle_notify_new_transactions) + HANDLE_NOTIFY_T2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS, ¤cy_protocol_handler::handle_notify_new_transactions) + HANDLE_INVOKE_T2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS, ¤cy_protocol_handler::handle_invoke_new_transaction) HANDLE_NOTIFY_T2(NOTIFY_REQUEST_GET_OBJECTS, ¤cy_protocol_handler::handle_request_get_objects) HANDLE_NOTIFY_T2(NOTIFY_RESPONSE_GET_OBJECTS, ¤cy_protocol_handler::handle_response_get_objects) HANDLE_NOTIFY_T2(NOTIFY_REQUEST_CHAIN, ¤cy_protocol_handler::handle_request_chain) @@ -76,16 +77,18 @@ namespace currency private: //----------------- commands handlers ---------------------------------------------- int handle_notify_new_block(int command, NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& context); - int handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, currency_connection_context& context); + int handle_notify_new_transactions(int command, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& context); + int handle_invoke_new_transaction(int command, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& req, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response& rsp, currency_connection_context& context); int handle_request_get_objects(int command, NOTIFY_REQUEST_GET_OBJECTS::request& arg, currency_connection_context& context); int handle_response_get_objects(int command, NOTIFY_RESPONSE_GET_OBJECTS::request& arg, currency_connection_context& context); int handle_request_chain(int command, NOTIFY_REQUEST_CHAIN::request& arg, currency_connection_context& context); int handle_response_chain_entry(int command, NOTIFY_RESPONSE_CHAIN_ENTRY::request& arg, currency_connection_context& context); - + + //----------------- i_bc_protocol_layout --------------------------------------- virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context); - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context); + virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context); //---------------------------------------------------------------------------------- //bool get_payload_sync_data(HANDSHAKE_DATA::request& hshd, currency_connection_context& context); bool request_missing_objects(currency_connection_context& context, bool check_having_blocks); @@ -93,7 +96,7 @@ namespace currency void relay_que_worker(); void process_current_relay_que(const std::list& que); bool check_stop_flag_and_drop_cc(currency_connection_context& context); - + int handle_new_transaction_from_net(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& req, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response& rsp, currency_connection_context& context, bool is_notify); t_core& m_core; nodetool::p2p_endpoint_stub m_p2p_stub; diff --git a/src/currency_protocol/currency_protocol_handler.inl b/src/currency_protocol/currency_protocol_handler.inl index 62e66448..2ec82588 100644 --- a/src/currency_protocol/currency_protocol_handler.inl +++ b/src/currency_protocol/currency_protocol_handler.inl @@ -369,48 +369,79 @@ namespace currency } //------------------------------------------------------------------------------------------------------------------------ template - int t_currency_protocol_handler::handle_notify_new_transactions(int command, NOTIFY_NEW_TRANSACTIONS::request& arg, currency_connection_context& context) + int t_currency_protocol_handler::handle_notify_new_transactions(int command, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& context) + { + NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response rsp_dummy = AUTO_VAL_INIT(rsp_dummy); + return this->handle_new_transaction_from_net(arg, rsp_dummy, context, true); + } + //------------------------------------------------------------------------------------------------------------------------ + template + int t_currency_protocol_handler::handle_invoke_new_transaction(int command, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& req, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response& rsp, currency_connection_context& context) + { + return this->handle_new_transaction_from_net(req, rsp, context, false); + } + //------------------------------------------------------------------------------------------------------------------------ + template + int t_currency_protocol_handler::handle_new_transaction_from_net(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response& rsp, currency_connection_context& context, bool is_notify) { //do not process requests if it comes from node wich is debugged if (m_debug_ip_address != 0 && context.m_remote_ip == m_debug_ip_address) + { + rsp.code = API_RETURN_CODE_ACCESS_DENIED; return 1; + } //if(context.m_state != currency_connection_context::state_normal) // return 1; if (!this->is_synchronized()) + { + rsp.code = API_RETURN_CODE_BUSY; return 1; - + } + + uint64_t inital_tx_count = arg.txs.size(); + + if (inital_tx_count > CURRENCY_RELAY_TXS_MAX_COUNT) + { + LOG_PRINT_L1("NOTIFY_NEW_TRANSACTIONS: To many transactions in NOTIFY_OR_INVOKE_NEW_TRANSACTIONS(" << inital_tx_count << ")"); + rsp.code = API_RETURN_CODE_OVERFLOW; + return 1; + } + TIME_MEASURE_START_MS(new_transactions_handle_time); - for(auto tx_blob_it = arg.txs.begin(); tx_blob_it!=arg.txs.end();) + for (auto tx_blob_it = arg.txs.begin(); tx_blob_it != arg.txs.end();) { currency::tx_verification_context tvc = AUTO_VAL_INIT(tvc); m_core.handle_incoming_tx(*tx_blob_it, tvc, false); - if(tvc.m_verification_failed) + if (tvc.m_verification_failed) { LOG_PRINT_L0("NOTIFY_NEW_TRANSACTIONS: Tx verification failed, dropping connection"); - m_p2p->drop_connection(context); - + if(is_notify) + m_p2p->drop_connection(context); + else + rsp.code = API_RETURN_CODE_FAIL; return 1; } - if(tvc.m_should_be_relayed) + if (tvc.m_should_be_relayed) ++tx_blob_it; else arg.txs.erase(tx_blob_it++); } - if(arg.txs.size()) + if (arg.txs.size()) { //TODO: add announce usage here relay_transactions(arg, context); } TIME_MEASURE_FINISH_MS(new_transactions_handle_time); - LOG_PRINT_L2("NOTIFY_NEW_TRANSACTIONS: " << new_transactions_handle_time << "ms (inital_tx_count: " << inital_tx_count << ", relayed_tx_count: " << arg.txs.size() << ")"); - - return true; + LOG_PRINT_L2("NOTIFY_OR_INVOKE_NEW_TRANSACTIONS(is_notify=" << is_notify <<"): " << new_transactions_handle_time << "ms (inital_tx_count: " << inital_tx_count << ", relayed_tx_count: " << arg.txs.size() << ")"); + rsp.code = API_RETURN_CODE_OK; + return 1; } + //------------------------------------------------------------------------------------------------------------------------ template int t_currency_protocol_handler::handle_request_get_objects(int command, NOTIFY_REQUEST_GET_OBJECTS::request& arg, currency_connection_context& context) @@ -759,7 +790,7 @@ namespace currency m_p2p->get_connections(connections); for (auto& cc : connections) { - NOTIFY_NEW_TRANSACTIONS::request req = AUTO_VAL_INIT(req); + NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request req = AUTO_VAL_INIT(req); for (auto& qe : que) { //exclude relaying to original sender @@ -769,7 +800,7 @@ namespace currency } if (req.txs.size()) { - post_notify(req, cc); + post_notify(req, cc); if (debug_ss.tellp()) debug_ss << ", "; @@ -947,7 +978,7 @@ namespace currency } //------------------------------------------------------------------------------------------------------------------------ template - bool t_currency_protocol_handler::relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context) + bool t_currency_protocol_handler::relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context) { #ifdef ASYNC_RELAY_MODE { @@ -959,7 +990,7 @@ namespace currency //m_relay_que_cv.notify_all(); return true; #else - return relay_post_notify(arg, exclude_context); + return relay_post_notify(arg, exclude_context); #endif } } diff --git a/src/currency_protocol/currency_protocol_handler_common.h b/src/currency_protocol/currency_protocol_handler_common.h index 2bff506c..d07153f5 100644 --- a/src/currency_protocol/currency_protocol_handler_common.h +++ b/src/currency_protocol/currency_protocol_handler_common.h @@ -17,7 +17,7 @@ namespace currency struct i_currency_protocol { virtual bool relay_block(NOTIFY_NEW_BLOCK::request& arg, currency_connection_context& exclude_context)=0; - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context)=0; + virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency_connection_context& exclude_context)=0; //virtual bool request_objects(NOTIFY_REQUEST_GET_OBJECTS::request& arg, currency_connection_context& context)=0; }; @@ -30,7 +30,7 @@ namespace currency { return false; } - virtual bool relay_transactions(NOTIFY_NEW_TRANSACTIONS::request& /*arg*/, currency_connection_context& /*exclude_context*/) + virtual bool relay_transactions(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& /*arg*/, currency_connection_context& /*exclude_context*/) { return false; } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index c16efcac..0f4d866c 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -705,7 +705,7 @@ namespace currency } - NOTIFY_NEW_TRANSACTIONS::request r; + NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request r; r.txs.push_back(tx_blob); m_core.get_protocol()->relay_transactions(r, fake_context); //TODO: make sure that tx has reached other nodes here, probably wait to receive reflections from other nodes @@ -716,7 +716,7 @@ namespace currency bool core_rpc_server::on_force_relaey_raw_txs(const COMMAND_RPC_FORCE_RELAY_RAW_TXS::request& req, COMMAND_RPC_FORCE_RELAY_RAW_TXS::response& res, connection_context& cntx) { CHECK_CORE_READY(); - NOTIFY_NEW_TRANSACTIONS::request r = AUTO_VAL_INIT(r); + NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request r = AUTO_VAL_INIT(r); for (const auto& t : req.txs_as_hex) { diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 587c1b44..c36a8fc6 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4616,14 +4616,15 @@ void wallet2::send_transaction_to_network(const transaction& tx) { continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); } - currency::NOTIFY_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); std::string blob; epee::serialization::store_t_to_binary(p2p_req, blob); - p2p_client.notify(NOTIFY_NEW_TRANSACTIONS::ID, blob); + p2p_client.invoke(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, blob); p2p_client.disconnect(); - //checking if transaction got relayed + //checking if transaction got relayed to other nodes and //return; } } diff --git a/tests/core_tests/test_core_proxy.h b/tests/core_tests/test_core_proxy.h index 83694ecd..29898012 100644 --- a/tests/core_tests/test_core_proxy.h +++ b/tests/core_tests/test_core_proxy.h @@ -51,7 +51,7 @@ public: //----------------- i_currency_protocol --------------------------------------- virtual bool relay_block(currency::NOTIFY_NEW_BLOCK::request& /*arg*/, currency::currency_connection_context& /*exclude_context*/) override { return false; } - virtual bool relay_transactions(currency::NOTIFY_NEW_TRANSACTIONS::request& arg, currency::currency_connection_context& /*exclude_context*/) override + virtual bool relay_transactions(currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request& arg, currency::currency_connection_context& /*exclude_context*/) override { if (m_core_listener) { From 2478dbb677c23d2a8cad404d3c38c0b01497f531 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 21 Mar 2022 19:45:21 +0300 Subject: [PATCH 0116/1271] coretests fixed --- tests/core_tests/chaingen_main.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 3e9cbc80..0765a006 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -667,6 +667,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_options, arg_enable_debug_asserts); command_line::add_arg(desc_options, command_line::arg_data_dir, std::string(".")); command_line::add_arg(desc_options, command_line::arg_stop_after_height); + command_line::add_arg(desc_options, command_line::arg_disable_ntp); currency::core::init_options(desc_options); tools::db::db_backend_selector::init_options(desc_options); From 58931cb77b3d09d4b3ad6b4142fcc9964f3c6227 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 22 Mar 2022 23:38:43 +0200 Subject: [PATCH 0117/1271] added status feedback from wallet\tor layer --- src/simplewallet/simplewallet.cpp | 5 +++++ src/simplewallet/simplewallet.h | 1 + src/wallet/wallet2.cpp | 9 ++++++--- src/wallet/wallet2.h | 1 + 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index cfb48a64..3e1c7504 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -658,6 +658,11 @@ void simple_wallet::on_message(i_wallet2_callback::message_severity severity, co message_writer(color, true, std::string()) << m; } //---------------------------------------------------------------------------------------------------- +void simple_wallet::on_tor_status_change(const std::string& state) +{ + message_writer(epee::log_space::console_color_yellow, false, std::string("TOR")) << state; +} +//---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector& args) { if (m_offline_mode) diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 35f99128..c0c0124d 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -98,6 +98,7 @@ namespace currency virtual void on_new_block(uint64_t height, const currency::block& block) override; virtual void on_transfer2(const tools::wallet_public::wallet_transfer_info& wti, uint64_t balance, uint64_t unlocked_balance, uint64_t total_mined) override; virtual void on_message(i_wallet2_callback::message_severity severity, const std::string& m) override; + virtual void on_tor_status_change(const std::string& state) override; //---------------------------------------------------------- diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index c36a8fc6..62ad07c3 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -32,6 +32,8 @@ using namespace epee; #include "common/encryption_filter.h" #include "crypto/bitcoin/sha256_helper.h" #include "common/tor_helper.h" +#include "storages/levin_abstract_invoke2.h" + using namespace currency; @@ -4619,9 +4621,10 @@ void wallet2::send_transaction_to_network(const transaction& tx) currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); - std::string blob; - epee::serialization::store_t_to_binary(p2p_req, blob); - p2p_client.invoke(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, blob); + //std::string blob; + //epee::serialization::store_t_to_binary(p2p_req, blob); + epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); + //p2p_client.invoke(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, blob,m ); p2p_client.disconnect(); //checking if transaction got relayed to other nodes and diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index f4f4736d..e57ff79a 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -41,6 +41,7 @@ #include "currency_core/bc_escrow_service.h" #include "common/pod_array_file_container.h" #include "wallet_chain_shortener.h" +#include "tor-connect/torlib/tor_lib_iface.h" #define WALLET_DEFAULT_TX_SPENDABLE_AGE 10 From e3936b5e9dc38e07b68f42c841ec558bbc0189bf Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 22 Mar 2022 23:46:29 +0200 Subject: [PATCH 0118/1271] lates tor commits --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index d935b589..1c4e5d9c 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit d935b5893c182f8a1fc47213fdef89540e85602f +Subproject commit 1c4e5d9c0cfe96dccf658de74d3e8294d9f51ad1 From 260e20d91f5ce6f9ab9940df6070259786f34178 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 24 Mar 2022 05:37:58 +0200 Subject: [PATCH 0119/1271] fixed another tor-related bug in simplewallet --- src/wallet/wallet2.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 62ad07c3..2dd9c27b 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4621,12 +4621,12 @@ void wallet2::send_transaction_to_network(const transaction& tx) currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); - //std::string blob; - //epee::serialization::store_t_to_binary(p2p_req, blob); epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); - //p2p_client.invoke(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, blob,m ); p2p_client.disconnect(); - + if (p2p_rsp.code == API_RETURN_CODE_OK) + { + break; + } //checking if transaction got relayed to other nodes and //return; } From 8221d28e61376e7b728ef2e6843167323b467b82 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 29 Mar 2022 02:05:38 +0300 Subject: [PATCH 0120/1271] added human readable tor status messages for simple wallet, disabled technical logs --- contrib/tor-connect | 2 +- src/simplewallet/simplewallet.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 1c4e5d9c..de54ace0 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 1c4e5d9c0cfe96dccf658de74d3e8294d9f51ad1 +Subproject commit de54ace0f0e5178d663b184a85d4805c01b3d6b1 diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 3e1c7504..dbf21477 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -660,7 +660,23 @@ void simple_wallet::on_message(i_wallet2_callback::message_severity severity, co //---------------------------------------------------------------------------------------------------- void simple_wallet::on_tor_status_change(const std::string& state) { - message_writer(epee::log_space::console_color_yellow, false, std::string("TOR")) << state; + std::string human_message; + if (state == TOR_LIB_STATE_INITIALIZING) + human_message = "Initializing..."; + else if (state == TOR_LIB_STATE_DOWNLOADING_CONSENSUS) + human_message = "Downloading consensus..."; + else if (state == TOR_LIB_STATE_MAKING_TUNNEL_A) + human_message = "Building tunnel to A..."; + else if (state == TOR_LIB_STATE_MAKING_TUNNEL_B) + human_message = "Building tunnel to B..."; + else if (state == TOR_LIB_STATE_CREATING_STREAM) + human_message = "Creating stream..."; + else if (state == TOR_LIB_STATE_SUCCESS) + human_message = "Successfully created stream"; + else if (state == TOR_LIB_STATE_FAILED) + human_message = "Failed created stream"; + + message_writer(epee::log_space::console_color_yellow, true, std::string("TOR")) << human_message; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector& args) From f3f2cefde4418e5754fb195c82d0d1b5f6eded50 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 1 Apr 2022 18:14:19 +0300 Subject: [PATCH 0121/1271] fixing tor submodule issue --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 21ad13a0..3f8b8025 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,4 @@ [submodule "contrib/tor-connect"] path = contrib/tor-connect url = https://github.com/hyle-team/tor-connect.git + branch = main From 6a6d72169a795521fc091839149f6e9b1289fcd0 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 1 Apr 2022 18:20:43 +0300 Subject: [PATCH 0122/1271] fixing layout submodule issue --- .gitmodules | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitmodules b/.gitmodules index 3f8b8025..831929a3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,7 @@ [submodule "src/gui/qt-daemon/layout"] path = src/gui/qt-daemon/layout url = https://github.com/hyle-team/zano_ui.git + branch = main [submodule "contrib/tor-connect"] path = contrib/tor-connect url = https://github.com/hyle-team/tor-connect.git From ebd8a63477035aec8c7a436a937531b37a14f02d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 1 Apr 2022 19:49:44 +0300 Subject: [PATCH 0123/1271] updated tor-lib to latest commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index de54ace0..a917f7ef 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit de54ace0f0e5178d663b184a85d4805c01b3d6b1 +Subproject commit a917f7ef44080955eaf09c2cf9e6336758a4dd97 From 80197c2a26f53cd8b6fe844521ce348d40fd3d6d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 2 Apr 2022 00:59:16 +0300 Subject: [PATCH 0124/1271] fixed errors in reporting state --- src/simplewallet/simplewallet.cpp | 9 ++++++++- src/wallet/wallet2.cpp | 3 +++ src/wallet/wallet_errors.h | 6 ++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index dbf21477..1d24c32d 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -675,8 +675,15 @@ void simple_wallet::on_tor_status_change(const std::string& state) human_message = "Successfully created stream"; else if (state == TOR_LIB_STATE_FAILED) human_message = "Failed created stream"; + else if (state == WALLET_LIB_STATE_SENDING) + human_message = "Sending transaction..."; + else if (state == WALLET_LIB_SENT_SUCCESS) + human_message = "Successfully sent!"; + else if (state == WALLET_LIB_SEND_FAILED) + human_message = "Sending failed"; - message_writer(epee::log_space::console_color_yellow, true, std::string("TOR")) << human_message; + + message_writer(epee::log_space::console_color_yellow, true, std::string("[TOR]: ")) << human_message; } //---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh(const std::vector& args) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 2dd9c27b..07a098b7 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4621,12 +4621,15 @@ void wallet2::send_transaction_to_network(const transaction& tx) currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); + this->notify_state_change(WALLET_LIB_STATE_SENDING); epee::net_utils::invoke_remote_command2(NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::ID, p2p_req, p2p_rsp, p2p_client); p2p_client.disconnect(); if (p2p_rsp.code == API_RETURN_CODE_OK) { + this->notify_state_change(WALLET_LIB_SENT_SUCCESS); break; } + this->notify_state_change(WALLET_LIB_SEND_FAILED); //checking if transaction got relayed to other nodes and //return; } diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index c2636d70..f836c8a8 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -16,6 +16,12 @@ // TODO: get rid of all these types, leave only wallet_error with a string message and string/enum error designator + +#define WALLET_LIB_STATE_SENDING "STATE_SENDING" +#define WALLET_LIB_SENT_SUCCESS "STATE_SENT_SUCCESS" +#define WALLET_LIB_SEND_FAILED "STATE_SEND_FAILED" + + namespace tools { namespace error From 46e57bf215bd52554e421bc7577f7541dcbe7a42 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 2 Apr 2022 19:48:36 +0300 Subject: [PATCH 0125/1271] added command line and run-time parameters to endabling/disabling TOR relay in simplewallet --- contrib/tor-connect | 2 +- src/simplewallet/simplewallet.cpp | 40 ++++++++++++++++++++++--------- src/simplewallet/simplewallet.h | 3 +++ src/wallet/wallet2.cpp | 2 ++ 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index a917f7ef..afe293d2 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit a917f7ef44080955eaf09c2cf9e6336758a4dd97 +Subproject commit afe293d266d148a28806a50a3db27601d1a5d718 diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 1d24c32d..53b458b0 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -55,6 +55,7 @@ namespace const command_line::arg_descriptor arg_offline_mode = { "offline-mode", "Don't connect to daemon, work offline (for cold-signing process)", false, true }; const command_line::arg_descriptor arg_scan_for_wallet = { "scan-for-wallet", "", "", true }; const command_line::arg_descriptor arg_addr_to_compare = { "addr-to-compare", "", "", true }; + const command_line::arg_descriptor arg_disable_tor_relay = { "disable-tor-relay", "Do PoS mining", false, false }; const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; @@ -123,17 +124,9 @@ namespace m_flush = false; LOG_PRINT(m_oss.str(), m_log_level) - - if (epee::log_space::console_color_default == m_color) - { - std::cout << m_oss.str(); - } - else - { - epee::log_space::set_console_color(m_color, m_bright); - std::cout << m_oss.str(); - epee::log_space::reset_console_color(); - } + epee::log_space::set_console_color(m_color, m_bright); + std::cout << m_oss.str(); + epee::log_space::reset_console_color(); std::cout << std::endl; } @@ -229,6 +222,8 @@ simple_wallet::simple_wallet() m_cmd_binder.set_handler("sign_transfer", boost::bind(&simple_wallet::sign_transfer, this, _1), "sign_transfer - sign unsigned tx from a watch-only wallet"); m_cmd_binder.set_handler("submit_transfer", boost::bind(&simple_wallet::submit_transfer, this, _1), "submit_transfer - broadcast signed tx"); m_cmd_binder.set_handler("export_history", boost::bind(&simple_wallet::submit_transfer, this, _1), "Export transaction history in CSV file"); + m_cmd_binder.set_handler("tor_enable", boost::bind(&simple_wallet::tor_enable, this, _1), "Enable relaying transactions over TOR network(enabled by default)"); + m_cmd_binder.set_handler("tor_disable", boost::bind(&simple_wallet::tor_disable, this, _1), "Enable relaying transactions over TOR network(enabled by default)"); } //---------------------------------------------------------------------------------------------------- simple_wallet::~simple_wallet() @@ -361,6 +356,11 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) bool r = open_wallet(m_wallet_file, pwd_container.password()); CHECK_AND_ASSERT_MES(r, false, "could not open account"); } + if (m_disable_tor) + { + m_wallet->set_disable_tor_relay(true); + message_writer(epee::log_space::console_color_default, true, std::string(), LOG_LEVEL_0) << "Notice: Relaying transactions over TOR disabled with command line parameter"; + } return true; } @@ -384,6 +384,7 @@ void simple_wallet::handle_command_line(const boost::program_options::variables_ m_do_not_set_date = command_line::get_arg(vm, arg_dont_set_date); m_do_pos_mining = command_line::get_arg(vm, arg_do_pos_mining); m_restore_wallet = command_line::get_arg(vm, arg_restore_wallet); + m_disable_tor = command_line::get_arg(vm, arg_disable_tor_relay); } //---------------------------------------------------------------------------------------------------- bool simple_wallet::try_connect_to_daemon() @@ -1738,6 +1739,21 @@ bool simple_wallet::submit_transfer(const std::vector &args) } return true; } +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::tor_enable(const std::vector &args) +{ + success_msg_writer(true) << "TOR relaying enabled"; + m_wallet->set_disable_tor_relay(false); + return true; +} +//---------------------------------------------------------------------------------------------------- +bool simple_wallet::tor_disable(const std::vector &args) +{ + m_wallet->set_disable_tor_relay(true); + success_msg_writer(true) << "TOR relaying disabled"; + return true; +} + //---------------------------------------------------------------------------------------------------- bool simple_wallet::sweep_below(const std::vector &args) { @@ -1988,6 +2004,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, command_line::arg_log_level); command_line::add_arg(desc_params, arg_scan_for_wallet); command_line::add_arg(desc_params, arg_addr_to_compare); + command_line::add_arg(desc_params, arg_disable_tor_relay); + tools::wallet_rpc_server::init_options(desc_params); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index c0c0124d..f5138544 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -86,6 +86,8 @@ namespace currency bool sign_transfer(const std::vector &args); bool submit_transfer(const std::vector &args); bool sweep_below(const std::vector &args); + bool tor_enable(const std::vector &args); + bool tor_disable(const std::vector &args); bool validate_wrap_status(uint64_t amount); bool get_alias_from_daemon(const std::string& alias_name, currency::extra_alias_entry_base& ai); @@ -167,6 +169,7 @@ namespace currency bool m_do_not_set_date; bool m_do_pos_mining; bool m_offline_mode; + bool m_disable_tor; std::string m_restore_wallet; epee::console_handlers_binder m_cmd_binder; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 07a098b7..272cfcb6 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4618,6 +4618,8 @@ void wallet2::send_transaction_to_network(const transaction& tx) { continue;//THROW_IF_FALSE_WALLET_EX(false, error::no_connection_to_daemon, "Failed to connect to TOR node"); } + + currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::request p2p_req = AUTO_VAL_INIT(p2p_req); currency::NOTIFY_OR_INVOKE_NEW_TRANSACTIONS::response p2p_rsp = AUTO_VAL_INIT(p2p_rsp); p2p_req.txs.push_back(t_serializable_object_to_blob(tx)); From b9ccb10287dd21433ece375ced5b4f3d910a9f50 Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 9 Apr 2022 21:12:44 +0200 Subject: [PATCH 0126/1271] crypto: scalar_t::git_bit + test crypto_sc_get_bit; hash_helper_t extended with hp variants --- src/crypto/crypto-sugar.h | 22 ++++++++++++++ tests/functional_tests/crypto_tests.cpp | 40 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 5542b9fb..e5ce7b9b 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -431,6 +431,13 @@ namespace crypto return result; } + bool get_bit(size_t bit_index) const + { + if (bit_index > 255) + return false; // TODO: consider performace implications + return (m_u64[bit_index >> 6] & (1ull << (bit_index & 63))) != 0; + } + }; // struct scalar_t // @@ -1063,6 +1070,21 @@ namespace crypto ge_bytes_hash_to_ec_32(&result.m_p3, (const unsigned char*)&p); return result; } + + static point_t hp(const scalar_t& s) + { + point_t result; + ge_bytes_hash_to_ec_32(&result.m_p3, s.data()); + return result; + } + + static point_t hp(const void* data, size_t size) + { + point_t result; + ge_bytes_hash_to_ec(&result.m_p3, data, size); + return result; + } + }; // hash_helper_t struct diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 83c94b8b..19c922e2 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -1803,6 +1803,46 @@ TEST(crypto, point_is_zero) } +TEST(crypto, sc_get_bit) +{ + static_assert(sizeof(scalar_t) * 8 == 256, "size missmatch"); + + scalar_t v = 0; // all bits are 0 + for (size_t n = 0; n < 256; ++n) + { + ASSERT_EQ(v.get_bit(n), false); + } + + v = c_scalar_256m1; // all bits are 1 + for (size_t n = 0; n < 256; ++n) + { + ASSERT_EQ(v.get_bit(n), true); + } + + // bits out of the [0; 255] range supposed to be always 0 + for (size_t n = 256; n < 2048; ++n) + { + ASSERT_EQ(v.get_bit(n), false); + } + + // check random value + const scalar_t x = scalar_t::random(); + for (size_t n = 0; n < 64; ++n) + ASSERT_EQ(x.get_bit(n), ((x.m_u64[0] & (1ull << (n - 0))) != 0)); + for (size_t n = 64; n < 128; ++n) + ASSERT_EQ(x.get_bit(n), ((x.m_u64[1] & (1ull << (n - 64))) != 0)); + for (size_t n = 128; n < 192; ++n) + ASSERT_EQ(x.get_bit(n), ((x.m_u64[2] & (1ull << (n - 128))) != 0)); + for (size_t n = 192; n < 256; ++n) + ASSERT_EQ(x.get_bit(n), ((x.m_u64[3] & (1ull << (n - 192))) != 0)); + + // bits out of the [0; 255] range supposed to be always 0 + for (size_t n = 256; n < 2048; ++n) + ASSERT_EQ(x.get_bit(n), false); + + return true; +} + // // test's runner // From 2dfc2862fcd603cf593ab905d59ed28546b68e1e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sat, 9 Apr 2022 22:36:11 +0200 Subject: [PATCH 0127/1271] tor integration in UI --- src/CMakeLists.txt | 2 +- src/gui/qt-daemon/application/mainwindow.cpp | 11 +++++++++++ src/gui/qt-daemon/application/mainwindow.h | 2 ++ src/wallet/view_iface.h | 13 +++++++++++++ src/wallet/wallet_id_adapter.h | 6 ++++++ src/wallet/wallets_manager.cpp | 7 +++++++ src/wallet/wallets_manager.h | 2 ++ 7 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 063a651b..892a8585 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -194,7 +194,7 @@ if(BUILD_GUI) QT5_USE_MODULES(Zano WebEngineWidgets WebChannel) find_package(Qt5PrintSupport REQUIRED) - target_link_libraries(Zano wallet rpc currency_core crypto common zlibstatic ethash Qt5::WebEngineWidgets Qt5::PrintSupport ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) + target_link_libraries(Zano wallet rpc currency_core crypto common zlibstatic ethash tor-connect Qt5::WebEngineWidgets Qt5::PrintSupport ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) if (UNIX AND NOT APPLE) target_link_libraries(Zano rt) endif() diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 646e4d3b..370d06c4 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -947,6 +947,17 @@ bool MainWindow::set_options(const view::gui_options& opt) CATCH_ENTRY2(false); } +bool MainWindow::update_tor_status(const view::current_action_status& opt) +{ + TRY_ENTRY(); + std::string json_str; + epee::serialization::store_t_to_json(opt, json_str, 0, epee::serialization::eol_lf); + LOG_PRINT_L0("SENDING SIGNAL -> [HANDLE_CURRENT_ACTION_STATE]:" << std::endl << json_str); + QMetaObject::invokeMethod(this, "handle_current_action_state", Qt::QueuedConnection, Q_ARG(QString, json_str.c_str())); + return true; + CATCH_ENTRY2(false); +} + bool MainWindow::nativeEventFilter(const QByteArray &eventType, void *message, long *result) { TRY_ENTRY(); diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 4f7af71d..dfe9ae4b 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -197,6 +197,7 @@ signals: void set_options(const QString str); //general function void get_wallet_name(); void handle_deeplink_click(const QString str); + void handle_current_action_state(const QString str); private: //-------------------- i_core_event_handler -------------------- @@ -216,6 +217,7 @@ private: virtual bool init(const std::string& path); virtual bool pos_block_found(const currency::block& block_found); virtual bool set_options(const view::gui_options& opt); + virtual bool update_tor_status(const view::current_action_status& opt); //--------- QAbstractNativeEventFilter --------------------------- virtual bool nativeEventFilter(const QByteArray &eventType, void *message, long *result); //---------------------------------------------- diff --git a/src/wallet/view_iface.h b/src/wallet/view_iface.h index 8c5e52c4..edf78d14 100644 --- a/src/wallet/view_iface.h +++ b/src/wallet/view_iface.h @@ -600,6 +600,18 @@ public: END_KV_SERIALIZE_MAP() }; + struct current_action_status + { + uint64_t wallet_id; + std::string status; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(wallet_id) + KV_SERIALIZE(status) + END_KV_SERIALIZE_MAP() + }; + + struct wallet_sync_status_info { bool is_daemon_connected; @@ -828,6 +840,7 @@ public: virtual bool pos_block_found(const currency::block& block_found){ return true; } virtual bool money_transfer_cancel(const transfer_event_info& wsi){ return true; } virtual bool set_options(const gui_options& opt){ return true; } + virtual bool update_tor_status(const current_action_status & opt) { return true; } }; } diff --git a/src/wallet/wallet_id_adapter.h b/src/wallet/wallet_id_adapter.h index b36b5bac..4a4b0a8c 100644 --- a/src/wallet/wallet_id_adapter.h +++ b/src/wallet/wallet_id_adapter.h @@ -16,6 +16,7 @@ public: virtual void on_pos_block_found(size_t wallet_id, const currency::block& /*block*/) {} virtual void on_sync_progress(size_t wallet_id, const uint64_t& /*percents*/) {} virtual void on_transfer_canceled(size_t wallet_id, const tools::wallet_public::wallet_transfer_info& wti) {} + virtual void on_tor_status_change(size_t wallet_id, const std::string& state) {} }; struct i_wallet_to_i_backend_adapter: public tools::i_wallet2_callback @@ -39,6 +40,11 @@ struct i_wallet_to_i_backend_adapter: public tools::i_wallet2_callback virtual void on_transfer_canceled(const tools::wallet_public::wallet_transfer_info& wti) { m_pbackend->on_transfer_canceled(m_wallet_id, wti); } + virtual void on_tor_status_change(const std::string& state) + { + m_pbackend->on_tor_status_change(m_wallet_id, state); + } + private: i_backend_wallet_callback* m_pbackend; size_t m_wallet_id; diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 9171a27f..075b1bd5 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -1871,6 +1871,13 @@ void wallets_manager::on_transfer_canceled(size_t wallet_id, const tools::wallet } m_pview->money_transfer_cancel(tei); } + +void wallets_manager::on_tor_status_change(size_t wallet_id, const std::string& state) +{ + view::current_action_status tsu = { wallet_id , state }; + m_pview->update_tor_status(tsu); +} + void wallets_manager::wallet_vs_options::worker_func() { LOG_PRINT_GREEN("[WALLET_HANDLER] Wallet handler thread started, addr: " << w->get()->get_account().get_public_address_str(), LOG_LEVEL_0); diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index ac64041c..5b919a0b 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -187,6 +187,8 @@ private: virtual void on_pos_block_found(size_t wallet_id, const currency::block& /*block*/); virtual void on_sync_progress(size_t wallet_id, const uint64_t& /*percents*/); virtual void on_transfer_canceled(size_t wallet_id, const tools::wallet_public::wallet_transfer_info& wti); + virtual void on_tor_status_change(size_t wallet_id, const std::string& state); + std::thread m_main_worker_thread; From 5fc390233caa71bda478ee1e4c76a99899559d77 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 10 Apr 2022 17:12:38 +0200 Subject: [PATCH 0128/1271] fixed compilation issue for gcc --- contrib/epee/include/net/levin_client.inl | 32 +++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/contrib/epee/include/net/levin_client.inl b/contrib/epee/include/net/levin_client.inl index 8d69a939..c55d5ce3 100644 --- a/contrib/epee/include/net/levin_client.inl +++ b/contrib/epee/include/net/levin_client.inl @@ -36,42 +36,42 @@ namespace epee { template - bool levin_client_impl_t::connect(u_long ip, int port, unsigned int timeout, const std::string& bind_ip) + bool levin_client_impl_t::connect(u_long ip, int port, unsigned int timeout, const std::string& bind_ip) { return m_transport.connect(string_tools::get_ip_string_from_int32(ip), port, timeout, timeout, bind_ip); } //------------------------------------------------------------------------------ template - bool levin_client_impl_t::connect(const std::string& addr, int port, unsigned int timeout, const std::string& bind_ip) + bool levin_client_impl_t::connect(const std::string& addr, int port, unsigned int timeout, const std::string& bind_ip) { return m_transport.connect(addr, port, timeout, timeout, bind_ip); } //------------------------------------------------------------------------------ template - bool levin_client_impl_t::is_connected() + bool levin_client_impl_t::is_connected() { return m_transport.is_connected(); } //------------------------------------------------------------------------------ template - bool levin_client_impl_t::disconnect() + bool levin_client_impl_t::disconnect() { return m_transport.disconnect(); } //------------------------------------------------------------------------------ template - levin_client_impl_t::levin_client_impl_t() + levin_client_impl_t::levin_client_impl_t() { } //------------------------------------------------------------------------------ template - levin_client_impl_t::~levin_client_impl_t() + levin_client_impl_t::~levin_client_impl_t() { disconnect(); } //------------------------------------------------------------------------------ template - int levin_client_impl_t::invoke(int command, const std::string& in_buff, std::string& buff_out) + int levin_client_impl_t::invoke(int command, const std::string& in_buff, std::string& buff_out) { if (!is_connected()) return -1; @@ -107,7 +107,7 @@ namespace epee } //------------------------------------------------------------------------------ template - int levin_client_impl_t::notify(int command, const std::string& in_buff) + int levin_client_impl_t::notify(int command, const std::string& in_buff) { if (!is_connected()) return -1; @@ -127,7 +127,7 @@ namespace epee return 1; } //------------------------------------------------------------------------------ - template + template int levin_client_impl2::invoke(int command, const std::string& in_buff, std::string& buff_out) { if (!is_connected()) @@ -140,10 +140,10 @@ namespace epee head.m_command = static_cast(command); head.m_protocol_version = LEVIN_PROTOCOL_VER_1; head.m_flags = LEVIN_PACKET_REQUEST; - if (!m_transport.send(&head, sizeof(head))) + if (!this->m_transport.send(&head, sizeof(head))) return -1; - if (!m_transport.send(in_buff)) + if (!this->m_transport.send(in_buff)) return -1; //Since other side of connection could be running by async server, @@ -153,7 +153,7 @@ namespace epee while (true) { - if (!m_transport.recv_n(local_buff, sizeof(bucket_head2))) + if (!this->m_transport.recv_n(local_buff, sizeof(bucket_head2))) return LEVIN_ERROR_NET_ERROR; head = *(bucket_head2*)local_buff.data(); @@ -162,7 +162,7 @@ namespace epee LOG_PRINT_L0("Signature missmatch in response"); return LEVIN_ERROR_SIGNATURE_MISMATCH; } - if (!m_transport.recv_n(buff_out, head.m_cb)) + if (!this->m_transport.recv_n(buff_out, head.m_cb)) return LEVIN_ERROR_NET_ERROR; //now check if this is response to invoke (and extra validate if it's response to this(!) invoke) @@ -178,7 +178,7 @@ namespace epee } //------------------------------------------------------------------------------ template - int levin_client_impl2::notify(int command, const std::string& in_buff) + int levin_client_impl2::notify(int command, const std::string& in_buff) { if (!is_connected()) return -1; @@ -191,10 +191,10 @@ namespace epee head.m_protocol_version = LEVIN_PROTOCOL_VER_1; head.m_flags = LEVIN_PACKET_REQUEST; - if (!m_transport.send((const char*)&head, sizeof(head))) + if (!this->m_transport.send((const char*)&head, sizeof(head))) return -1; - if (!m_transport.send(in_buff)) + if (!this->m_transport.send(in_buff)) return -1; return 1; From 0fe6d2c5260a4fbf5275fbbcac94a7e0f7d09c48 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 10 Apr 2022 18:47:56 +0200 Subject: [PATCH 0129/1271] tor updated(Scrypt, can you like this too?) --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index afe293d2..93d717ae 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit afe293d266d148a28806a50a3db27601d1a5d718 +Subproject commit 93d717aebc9ef56e1a9eaefc9d137574bda0038c From 319d53f7cf09331f257b525eaa399482124520b1 Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 10 Apr 2022 19:45:45 +0200 Subject: [PATCH 0130/1271] crypto: H2 introduced, functional tests main's clean-up --- src/crypto/crypto-sugar.cpp | 1 + src/crypto/crypto-sugar.h | 1 + tests/functional_tests/main.cpp | 3 --- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/crypto/crypto-sugar.cpp b/src/crypto/crypto-sugar.cpp index 4774e9b7..8b8779ae 100644 --- a/src/crypto/crypto-sugar.cpp +++ b/src/crypto/crypto-sugar.cpp @@ -21,6 +21,7 @@ namespace crypto const scalar_t c_scalar_1div8 = { 0x6106e529e2dc2f79, 0x07d39db37d1cdad0, 0x0, 0x0600000000000000 }; const point_t c_point_H = { 0x05087c1f5b9b32d6, 0x00547595f445c3b5, 0x764df64578552f2a, 0x8a49a651e0e0da45 }; // == Hp(G), this is being checked in bpp_basics + const point_t c_point_H2 = { 0x70c8d1ab9dbf1cc0, 0xc561bb12639a8516, 0x3cfff1def9e5b268, 0xe0936386f3bcce1a }; // == Hp("h2_generator"), cheched in bpp_basics const point_t c_point_0 = point_t(point_t::tag_zero()); } // namespace crypto diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index e5ce7b9b..d1928a92 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -897,6 +897,7 @@ namespace crypto extern const point_g_t c_point_G; extern const point_t c_point_H; + extern const point_t c_point_H2; extern const point_t c_point_0; // diff --git a/tests/functional_tests/main.cpp b/tests/functional_tests/main.cpp index ed53cbe3..fc3da2d0 100644 --- a/tests/functional_tests/main.cpp +++ b/tests/functional_tests/main.cpp @@ -66,9 +66,6 @@ int main(int argc, char* argv[]) TRY_ENTRY(); string_tools::set_module_name_and_folder(argv[0]); - uint64_t reward = 0; - currency::get_block_reward(false, 500000, 589313, 10300000000000000, reward, 11030); - //set up logging options //log_space::get_set_log_detalisation_level(true, LOG_LEVEL_1); //log_space::log_singletone::add_logger(LOGGER_CONSOLE, NULL, NULL, LOG_LEVEL_2); From dc61500a462d99b60baf0e6d0d8da89ca933e2b0 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 10 Apr 2022 19:46:43 +0200 Subject: [PATCH 0131/1271] fixed http state machine, fixed wallet bug with tor connectivity failure --- contrib/epee/include/net/http_client.h | 2 +- contrib/tor-connect | 2 +- src/wallet/wallet2.cpp | 7 +++++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/contrib/epee/include/net/http_client.h b/contrib/epee/include/net/http_client.h index be81ba41..c0b57596 100644 --- a/contrib/epee/include/net/http_client.h +++ b/contrib/epee/include/net/http_client.h @@ -831,7 +831,7 @@ using namespace std; return false; }else { //Apparently there are no signs of the form of transfer, will receive data until the connection is closed - m_state = reciev_machine_state_error; + m_state = reciev_machine_state_body_connection_close; LOG_PRINT("Undefinded transfer type, consider http_body_transfer_connection_close method. header: " << m_header_cache, LOG_LEVEL_2); return false; } diff --git a/contrib/tor-connect b/contrib/tor-connect index 93d717ae..19c9c5d7 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 93d717aebc9ef56e1a9eaefc9d137574bda0038c +Subproject commit 19c9c5d79d40b7bde08598cf994ca0599803f489 diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 272cfcb6..9f4abdc6 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -4612,6 +4612,7 @@ void wallet2::send_transaction_to_network(const transaction& tx) //make few attempts tools::levin_over_tor_client p2p_client; p2p_client.get_transport().set_notifier(this); + bool succeseful_sent = false; for (size_t i = 0; i != 3; i++) { if (!p2p_client.connect("144.76.183.143", 2121, 10000)) @@ -4629,12 +4630,18 @@ void wallet2::send_transaction_to_network(const transaction& tx) if (p2p_rsp.code == API_RETURN_CODE_OK) { this->notify_state_change(WALLET_LIB_SENT_SUCCESS); + succeseful_sent = true; break; } this->notify_state_change(WALLET_LIB_SEND_FAILED); //checking if transaction got relayed to other nodes and //return; } + if (!succeseful_sent) + { + this->notify_state_change(WALLET_LIB_SEND_FAILED); + THROW_IF_FALSE_WALLET_EX(succeseful_sent, error::no_connection_to_daemon, "Faile to build TOR stream"); + } } else { From 10141c3dfca4bd1c354e6ff24bad4813430d0252 Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 10 Apr 2022 19:50:13 +0200 Subject: [PATCH 0132/1271] crypto: range proofs major refactoring, crypto traits refactored, calc_exp_power_of_2_upper_bound changed to constexpr analogs --- src/crypto/range_proof_bpp.h | 698 ++++++++++++++++ src/crypto/range_proofs.cpp | 3 - src/crypto/range_proofs.h | 751 +----------------- tests/functional_tests/crypto_tests.cpp | 2 + .../crypto_tests_range_proofs.h | 182 +++++ 5 files changed, 921 insertions(+), 715 deletions(-) create mode 100644 src/crypto/range_proof_bpp.h create mode 100644 tests/functional_tests/crypto_tests_range_proofs.h diff --git a/src/crypto/range_proof_bpp.h b/src/crypto/range_proof_bpp.h new file mode 100644 index 00000000..a8b68807 --- /dev/null +++ b/src/crypto/range_proof_bpp.h @@ -0,0 +1,698 @@ +// Copyright (c) 2021-2022 Zano Project (https://zano.org/) +// Copyright (c) 2021-2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once + +// +// This file contains the implementation of range proof protocol. +// Namely, Bulletproofs+ https://eprint.iacr.org/2020/735 +// + +namespace crypto +{ + struct bpp_signature + { + std::vector L; // size = ceil( log_2(m * n) ) + std::vector R; + public_key A0; + public_key A; + public_key B; + scalar_t r; + scalar_t s; + scalar_t delta; + }; + +#define DBG_VAL_PRINT(x) std::cout << #x ": " << x << ENDL +#define DBG_PRINT(x) std::cout << x << ENDL + + template + bool bpp_gen(const scalar_vec_t& values, const scalar_vec_t& masks, bpp_signature& sig, std::vector& commitments, uint8_t* p_err = nullptr) + { +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("bpp_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() > 0 && values.size() <= CT::c_bpp_values_max && values.size() == masks.size(), 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(masks.is_reduced(), 3); + + const size_t c_bpp_log2_m = constexpr_ceil_log2(values.size()); + const size_t c_bpp_m = 1ull << c_bpp_log2_m; + const size_t c_bpp_mn = c_bpp_m * CT::c_bpp_n; + const size_t c_bpp_log2_mn = c_bpp_log2_m + CT::c_bpp_log2_n; + + // pre-multiply all output points by c_scalar_1div8 + // in order to enforce these points to be in the prime-order subgroup (after mul by 8 in bpp_verify()) + + // calc commitments vector as commitments[i] = 1/8 * values[i] * G + 1/8 * masks[i] * H + commitments.resize(values.size()); + for (size_t i = 0; i < values.size(); ++i) + CT::calc_pedersen_commitment(values[i] * c_scalar_1div8, masks[i] * c_scalar_1div8, commitments[i]); + + + // s.a. BP+ paper, page 15, eq. 11 + // decompose v into aL and aR: + // v = aL o (1, 2, 2^2, ..., 2^n-1), o - component-wise product aka Hadamard product + // aR = aL - (1, 1, ... 1) + // aR o aL = 0 + + // aLs = (aL_0, aL_1, ..., aL_m-1) -- `bit` matrix of c_bpp_m x c_bpp_n, each element is a scalar + + scalar_mat_t aLs(c_bpp_mn), aRs(c_bpp_mn); + aLs.zero(); + aRs.zero(); + // m >= values.size, first set up [0..values.size-1], then -- [values.size..m-1] (padding area) + for (size_t i = 0; i < values.size(); ++i) + { + const scalar_t& v = values[i]; + for (size_t j = 0; j < CT::c_bpp_n; ++j) + { + if (v.get_bit(j)) + aLs(i, j) = c_scalar_1; // aL = 1, aR = 0 + else + aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1 + } + } + + for (size_t i = values.size(); i < c_bpp_m; ++i) + for (size_t j = 0; j < CT::c_bpp_n; ++j) + aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1 + + + // using e as Fiat-Shamir transcript + scalar_t e = CT::get_initial_transcript(); + DBG_PRINT("initial transcript: " << e); + + hash_helper_t::hs_t hsc; + CT::update_transcript(hsc, e, commitments); + + // BP+ paper, page 15: The prover begins with sending A = g^aL h^aR h^alpha (group element) + // so we calculate A0 = alpha * H + SUM(aL_i * G_i) + SUM(aR_i * H_i) + + scalar_t alpha = scalar_t::random(); + point_t A0 = alpha * CT::bpp_H; + + for (size_t i = 0; i < c_bpp_mn; ++i) + A0 += aLs[i] * CT::get_generator(false, i) + aRs[i] * CT::get_generator(true, i); + + // part of 1/8 defense scheme + A0 *= c_scalar_1div8; + A0.to_public_key(sig.A0); + + DBG_VAL_PRINT(alpha); + DBG_VAL_PRINT(A0); + + // calculate scalar challenges y and z + hsc.add_scalar(e); + hsc.add_pub_key(sig.A0); + scalar_t y = hsc.calc_hash(); + scalar_t z = hash_helper_t::hs(y); + e = z; // transcript for further steps + DBG_VAL_PRINT(y); + DBG_VAL_PRINT(z); + + // Computing vector d for aggregated version of the protocol (BP+ paper, page 17) + // (note: elements is stored column-by-column in memory) + // d = | 1 * z^(2*1), 1 * z^(2*2), 1 * z^(2*3), ..., 1 * z^(2*m) | + // | 2 * z^(2*1), 2 * z^(2*2), 2 * z^(2*3), ..., 2 * z^(2*m) | + // | 4 * z^(2*1), 4 * z^(2*2), 4 * z^(2*3), ..., 4 * z^(2*m) | + // | ....................................................................................... | + // | 2^(n-1) * z^(2*1), 2^(n-1) * z^(2*2), 2^(n-1) * z^(2*3), ..., 2^(n-1) * z^(2*m)) | + // Note: sum(d_i) = (2^n - 1) * ((z^2)^1 + (z^2)^2 + ... (z^2)^m)) = (2^n-1) * sum_of_powers(x^2, log(m)) + + scalar_t z_sq = z * z; + scalar_mat_t d(c_bpp_mn); + d(0, 0) = z_sq; + // first row + for (size_t i = 1; i < c_bpp_m; ++i) + d(i, 0) = d(i - 1, 0) * z_sq; + // all rows + for (size_t j = 1; j < CT::c_bpp_n; ++j) + for (size_t i = 0; i < c_bpp_m; ++i) + d(i, j) = d(i, j - 1) + d(i, j - 1); + + DBG_PRINT("Hs(d): " << d.calc_hs()); + + // calculate extended Vandermonde vector y = (1, y, y^2, ..., y^(mn+1)) (BP+ paper, page 18, Fig. 3) + // (calculate two more elements (1 and y^(mn+1)) for convenience) + scalar_vec_t y_powers(c_bpp_mn + 2); + y_powers[0] = 1; + for (size_t i = 1; i <= c_bpp_mn + 1; ++i) + y_powers[i] = y_powers[i - 1] * y; + + const scalar_t& y_mn_p1 = y_powers[c_bpp_mn + 1]; + + DBG_PRINT("Hs(y_powers): " << y_powers.calc_hs()); + + // aL_hat = aL - 1*z + scalar_vec_t aLs_hat = aLs - z; + // aL_hat = aR + d o y^leftarr + 1*z where y^leftarr = (y^n, y^(n-1), ..., y) (BP+ paper, page 18, Fig. 3) + scalar_vec_t aRs_hat = aRs + z; + for (size_t i = 0; i < c_bpp_mn; ++i) + aRs_hat[i] += d[i] * y_powers[c_bpp_mn - i]; + + DBG_PRINT("Hs(aLs_hat): " << aLs_hat.calc_hs()); + DBG_PRINT("Hs(aRs_hat): " << aRs_hat.calc_hs()); + + // calculate alpha_hat + // alpha_hat = alpha + SUM(z^(2j) * gamma_j * y^(mn+1)) for j = 1..m + // i.e. \hat{\alpha} = \alpha + y^{m n+1} \sum_{j = 1}^{m} z^{2j} \gamma_j + scalar_t alpha_hat = 0; + for (size_t i = 0; i < masks.size(); ++i) + alpha_hat += d(i, 0) * masks[i]; + alpha_hat = alpha + y_mn_p1 * alpha_hat; + + DBG_VAL_PRINT(alpha_hat); + + // calculate y^-1, y^-2, ... + const scalar_t y_inverse = y.reciprocal(); + scalar_vec_t y_inverse_powers(c_bpp_mn / 2 + 1); // the greatest power we need is c_bpp_mn/2 (at the first reduction round) + y_inverse_powers[0] = 1; + for (size_t i = 1, size = y_inverse_powers.size(); i < size; ++i) + y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inverse; + + // prepare generator's vector + std::vector g(c_bpp_mn), h(c_bpp_mn); + for (size_t i = 0; i < c_bpp_mn; ++i) + { + g[i] = CT::get_generator(false, i); + h[i] = CT::get_generator(true, i); + } + + // WIP zk-argument called with zk-WIP(g, h, G, H, A_hat, aL_hat, aR_hat, alpha_hat) + + scalar_vec_t& a = aLs_hat; + scalar_vec_t& b = aRs_hat; + + sig.L.resize(c_bpp_log2_mn); + sig.R.resize(c_bpp_log2_mn); + + // zk-WIP reduction rounds (s.a. the preprint page 13 Fig. 1) + for (size_t n = c_bpp_mn / 2, ni = 0; n >= 1; n /= 2, ++ni) + { + DBG_PRINT(ENDL << "#" << ni); + + // zk-WIP(g, h, G, H, P, a, b, alpha) + + scalar_t dL = scalar_t::random(); + DBG_VAL_PRINT(dL); + scalar_t dR = scalar_t::random(); + DBG_VAL_PRINT(dR); + + // a = (a1, a2), b = (b1, b2) -- vectors of scalars + // cL = -- scalar + scalar_t cL = 0; + for (size_t i = 0; i < n; ++i) + cL += a[i] * y_powers[i + 1] * b[n + i]; + + DBG_VAL_PRINT(cL); + + // cR = * y^n -- scalar + scalar_t cR = 0; + for (size_t i = 0; i < n; ++i) + cR += a[n + i] * y_powers[i + 1] * b[i]; + cR *= y_powers[n]; + + DBG_VAL_PRINT(cR); + + // L = y^-n * a1 * g2 + b2 * h1 + cL * G + dL * H -- point + point_t sum = c_point_0; + for (size_t i = 0; i < n; ++i) + sum += a[i] * g[n + i]; + point_t L; + CT::calc_pedersen_commitment(cL, dL, L); + for (size_t i = 0; i < n; ++i) + L += b[n + i] * h[i]; + L += y_inverse_powers[n] * sum; + L *= c_scalar_1div8; + DBG_VAL_PRINT(L); + + // R = y^n * a2 * g1 + b1 * h2 + cR * G + dR * H -- point + sum.zero(); + for (size_t i = 0; i < n; ++i) + sum += a[n + i] * g[i]; + point_t R; + CT::calc_pedersen_commitment(cR, dR, R); + for (size_t i = 0; i < n; ++i) + R += b[i] * h[n + i]; + R += y_powers[n] * sum; + R *= c_scalar_1div8; + DBG_VAL_PRINT(R); + + // put L, R to the sig + L.to_public_key(sig.L[ni]); + R.to_public_key(sig.R[ni]); + + // update the transcript + hsc.add_scalar(e); + hsc.add_pub_key(sig.L[ni]); + hsc.add_pub_key(sig.R[ni]); + e = hsc.calc_hash(); + DBG_VAL_PRINT(e); + + // recalculate arguments for the next round + scalar_t e_squared = e * e; + scalar_t e_inverse = e.reciprocal(); + scalar_t e_inverse_squared = e_inverse * e_inverse; + scalar_t e_y_inv_n = e * y_inverse_powers[n]; + scalar_t e_inv_y_n = e_inverse * y_powers[n]; + + // g_hat = e^-1 * g1 + (e * y^-n) * g2 -- vector of points + for (size_t i = 0; i < n; ++i) + g[i] = e_inverse * g[i] + e_y_inv_n * g[n + i]; + + // h_hat = e * h1 + e^-1 * h2 -- vector of points + for (size_t i = 0; i < n; ++i) + h[i] = e * h[i] + e_inverse * h[n + i]; + + // P_hat = e^2 * L + P + e^-2 * R -- point + + // a_hat = e * a1 + e^-1 * y^n * a2 -- vector of scalars + for (size_t i = 0; i < n; ++i) + a[i] = e * a[i] + e_inv_y_n * a[n + i]; + + // b_hat = e^-1 * b1 + e * b2 -- vector of scalars + for (size_t i = 0; i < n; ++i) + b[i] = e_inverse * b[i] + e * b[n + i]; + + // alpha_hat = e^2 * dL + alpha + e^-2 * dR -- scalar + alpha_hat += e_squared * dL + e_inverse_squared * dR; + + // run next iteraton zk-WIP(g_hat, h_hat, G, H, P_hat, a_hat, b_hat, alpha_hat) + } + DBG_PRINT(""); + + // zk-WIP last round + scalar_t r = scalar_t::random(); + scalar_t s = scalar_t::random(); + scalar_t delta = scalar_t::random(); + scalar_t eta = scalar_t::random(); + DBG_VAL_PRINT(r); + DBG_VAL_PRINT(s); + DBG_VAL_PRINT(delta); + DBG_VAL_PRINT(eta); + + // A = r * g + s * h + (r y b + s y a) * G + delta * H -- point + point_t A = c_point_0; + CT::calc_pedersen_commitment(y * (r * b[0] + s * a[0]), delta, A); + A += r * g[0] + s * h[0]; + A *= c_scalar_1div8; + A.to_public_key(sig.A); + DBG_VAL_PRINT(A); + + // B = (r * y * s) * G + eta * H + point_t B = c_point_0; + CT::calc_pedersen_commitment(r * y * s, eta, B); + B *= c_scalar_1div8; + B.to_public_key(sig.B); + DBG_VAL_PRINT(B); + + // update the transcript + hsc.add_scalar(e); + hsc.add_pub_key(sig.A); + hsc.add_pub_key(sig.B); + e = hsc.calc_hash(); + DBG_VAL_PRINT(e); + + // finalize the signature + sig.r = r + e * a[0]; + sig.s = s + e * b[0]; + sig.delta = eta + e * delta + e * e * alpha_hat; + DBG_VAL_PRINT(sig.r); + DBG_VAL_PRINT(sig.s); + DBG_VAL_PRINT(sig.delta); + + return true; +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + } // bpp_gen() + + + struct bpp_sig_commit_ref_t + { + bpp_sig_commit_ref_t(const bpp_signature& sig, const std::vector& commitments) + : sig(sig) + , commitments(commitments) + {} + const bpp_signature& sig; + const std::vector& commitments; + }; + + + template + bool bpp_verify(const std::vector& sigs, uint8_t* p_err = nullptr) + { +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("bpp_verify: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + DBG_PRINT(ENDL << " . . . . bpp_verify() . . . . "); + + const size_t kn = sigs.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(kn > 0, 1); + + struct intermediate_element_t + { + scalar_t y; + scalar_t z; + scalar_t z_sq; + scalar_vec_t e; + scalar_vec_t e_sq; + scalar_t e_final; + scalar_t e_final_sq; + size_t inv_e_offset; // offset in batch_for_inverse + size_t inv_y_offset; // offset in batch_for_inverse + size_t c_bpp_log2_m; + size_t c_bpp_m; + size_t c_bpp_mn; + point_t A; + point_t A0; + point_t B; + std::vector L; + std::vector R; + }; + std::vector interms(kn); + + size_t c_bpp_log2_m_max = 0; + for (size_t k = 0; k < kn; ++k) + { + const bpp_sig_commit_ref_t& bsc = sigs[k]; + const bpp_signature& sig = bsc.sig; + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bsc.commitments.size() > 0, 2); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() > 0 && sig.L.size() == sig.R.size(), 3); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.r.is_reduced() && sig.s.is_reduced() && sig.delta.is_reduced(), 4); + + intermediate_element_t& interm = interms[k]; + interm.c_bpp_log2_m = constexpr_ceil_log2(bsc.commitments.size()); + if (c_bpp_log2_m_max < interm.c_bpp_log2_m) + c_bpp_log2_m_max = interm.c_bpp_log2_m; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() == interm.c_bpp_log2_m + CT::c_bpp_log2_n, 5); + + interm.c_bpp_m = 1ull << interm.c_bpp_log2_m; + interm.c_bpp_mn = interm.c_bpp_m * CT::c_bpp_n; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A0.from_public_key(sig.A0), 6); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A.from_public_key(sig.A), 7); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.B.from_public_key(sig.B), 8); + interm.L.resize(sig.L.size()); + interm.R.resize(sig.R.size()); + for (size_t i = 0; i < interm.L.size(); ++i) + { + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.L[i].from_public_key(sig.L[i]), 9); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.R[i].from_public_key(sig.R[i]), 10); + } + } + const size_t c_bpp_m_max = 1ull << c_bpp_log2_m_max; + const size_t c_bpp_mn_max = c_bpp_m_max * CT::c_bpp_n; + const size_t c_bpp_LR_size_max = c_bpp_log2_m_max + CT::c_bpp_log2_n; + + + // + // prepare stuff + // + /* + std::vector g(c_bpp_mn_max), h(c_bpp_mn_max); + for (size_t i = 0; i < c_bpp_mn_max; ++i) + { + g[i] = CT::get_generator(false, i); + h[i] = CT::get_generator(true, i); + } + */ + + scalar_vec_t batch_for_inverse; + batch_for_inverse.reserve(kn + kn * c_bpp_LR_size_max); + + + for (size_t k = 0; k < kn; ++k) + { + DBG_PRINT(ENDL << "SIG #" << k); + const bpp_sig_commit_ref_t& bsc = sigs[k]; + const bpp_signature& sig = bsc.sig; + intermediate_element_t& interm = interms[k]; + + // restore y and z + // using e as Fiat-Shamir transcript + scalar_t e = CT::get_initial_transcript(); + DBG_PRINT("initial transcript: " << e); + hash_helper_t::hs_t hsc; + CT::update_transcript(hsc, e, bsc.commitments); + // calculate scalar challenges y and z + hsc.add_scalar(e); + hsc.add_pub_key(sig.A0); + hsc.assign_calc_hash(interm.y); + interm.z = hash_helper_t::hs(interm.y); + interm.z_sq = interm.z * interm.z; + DBG_VAL_PRINT(interm.y); + DBG_VAL_PRINT(interm.z); + e = interm.z; // transcript for further steps + + interm.inv_y_offset = batch_for_inverse.size(); + batch_for_inverse.push_back(interm.y); + interm.inv_e_offset = batch_for_inverse.size(); + + interm.e.resize(sig.L.size()); + interm.e_sq.resize(sig.L.size()); + + for (size_t i = 0; i < sig.L.size(); ++i) + { + hsc.add_scalar(e); + hsc.add_pub_key(sig.L[i]); + hsc.add_pub_key(sig.R[i]); + hsc.assign_calc_hash(e); + interm.e[i] = e; + interm.e_sq[i] = e * e; + DBG_PRINT("e[" << i << "]: " << e); + batch_for_inverse.push_back(e); + } + + hsc.add_scalar(e); + hsc.add_pub_key(sig.A); + hsc.add_pub_key(sig.B); + hsc.assign_calc_hash(interm.e_final); + interm.e_final_sq = interm.e_final * interm.e_final; + DBG_VAL_PRINT(interm.e_final); + } + + batch_for_inverse.invert(); + + // Notation: + // 1_vec ^ n = (1, 1, 1, ..., 1) + // 2_vec ^ n = (2^0, 2^1, 2^2, ..., 2^(n-1)) + // -1_vec ^ n = ((-1)^0, (-1)^1, (-1)^2, ... (-1)^(n-1)) = (1, -1, 1, -1, ...) + // y<^n = (y^n, y^(n-1), ..., y^1) + // y>^n = (y^1, y^2, ..., y^n) + + // from page 13, Fig 1: + // Verifier outputs Accept IFF the following equality holds (single proof): + // P^e^2 * A^e * B == g ^ (r' e) * h ^ (s' e) * G ^ (r' y s') * H ^ delta' + // (where g and h are calculated in each round) + // The same equation in additive notation: + // e^2 * P + e * A + B == (r' * e) * g + (s' * e) * h + (r' y s') * G + delta' * H + // <=> + // (r' * e) * g + (s' * e) * h + (r' y s') * G + delta' * H - e^2 * P - e * A - B == 0 (*) + // where A, B, r', s', delta' is taken from the signature + // and P_{k+1} = e^2 * L_k + P_k + e^-2 * R_k for all rounds + // + // from page 18, Fig 3: + // P and V computes: + // A_hat = A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h + + // + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) + + // + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G + // (calculated once) + // + // As suggested in Section 6.1 "Practical Optimizations": + // 1) g and h exponentianions can be optimized in order not to be calculated at each round as the following (page 20): + // + // (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta' * H - + // - e^2 * A_hat + // - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j) + // - e * A - B = 0 (**) + // + // where: + // g, h - vector of fixed generators + // s_vec_i = y^(1-i) * PROD{j=1..log(n)}(e_j ^ b(i,j)) + // s'_vec_i = PROD{j=1..log(n)}(e_j ^ -b(i,j)) + // b(i, j) = { 2 * ((1<<(j-1)) & (i-1)) - 1) (counting both from 1) (page 20) + // b(i, j) = { 2 * ((1< + + // (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta' * H - + // - e^2 * (A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h + + // + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) + + // + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G + // ) + // - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j) + // - e * A - B = 0 + + // => + + // (for single signature) + // + // (r' * e * s_vec - e^2 * (- 1_vec^(mn) * z)) * g | these are + // + (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) * h | fixed generators + // + (r' y s' - e^2 * ((z - z^2)*SUM(y^>mn) - z*y^(mn+1)*SUM(d)) * G | across all + // + delta' * H | the signatures + // + // - e^2 * A0 + // - e^2 * y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j)) + // - e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j) + // - e * A - B = 0 (***) + // + // All (***) will be muptiplied by random weightning factor and then summed up. + + // Calculate cummulative sclalar multiplicand for fixed generators across all the sigs. + scalar_vec_t g_scalars; + g_scalars.resize(c_bpp_mn_max, 0); + scalar_vec_t h_scalars; + h_scalars.resize(c_bpp_mn_max, 0); + scalar_t G_scalar = 0; + scalar_t H_scalar = 0; + point_t summand = c_point_0; + + for (size_t k = 0; k < kn; ++k) + { + DBG_PRINT(ENDL << "SIG #" << k); + const bpp_sig_commit_ref_t& bsc = sigs[k]; + const bpp_signature& sig = bsc.sig; + intermediate_element_t& interm = interms[k]; + + // random weightning factor for speed-optimized batch verification (preprint page 20) + const scalar_t rwf = scalar_t::random(); + DBG_PRINT("rwf: " << rwf); + + // prepare d vector (see also d structure description in proof function) + scalar_mat_t d(interm.c_bpp_mn); + d(0, 0) = interm.z_sq; + // first row + for (size_t i = 1; i < interm.c_bpp_m; ++i) + d(i, 0) = d(i - 1, 0) * interm.z_sq; + // all rows + for (size_t j = 1; j < CT::c_bpp_n; ++j) + for (size_t i = 0; i < interm.c_bpp_m; ++i) + d(i, j) = d(i, j - 1) + d(i, j - 1); + // sum(d) (see also note in proof function for this) + static const scalar_t c_scalar_2_power_n_minus_1 = { 0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000 }; + const scalar_t sum_d = c_scalar_2_power_n_minus_1 * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); + + DBG_PRINT("Hs(d): " << d.calc_hs()); + DBG_PRINT("sum(d): " << sum_d); + + const scalar_t& y_inv = batch_for_inverse[interm.inv_y_offset]; + auto get_e_inv = [&](size_t i) { return batch_for_inverse[interm.inv_e_offset + i]; }; // i belongs to [0; L.size()-1] + + // prepare s_vec (unlike the paper here we moved y-component out of s_vec for convenience, so s_vec'[x] = s_vec[~x & (MN-1)]) + // complexity (sc_mul's): MN+2*log2(MN)-2 + // the idea is the following: + // s_vec[00000b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^-1 * (e_1)^-1 * (e_0)^-1 + // s_vec[00101b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^+1 * (e_1)^-1 * (e_0)^+1 + const size_t log2_mn = sig.L.size(); // at the beginning we made sure that sig.L.size() == c_bpp_log2_m + c_bpp_log2_n + scalar_vec_t s_vec(interm.c_bpp_mn); + s_vec[0] = get_e_inv(0); + for (size_t i = 1; i < log2_mn; ++i) + s_vec[0] *= get_e_inv(i); // s_vec[0] = (e_0)^-1 * (e_1)^-1 * .. (e_{log2_mn-1})^-1 + DBG_PRINT("[0] " << s_vec[0]); + for (size_t i = 1; i < interm.c_bpp_mn; ++i) + { + size_t base_el_index = i & (i - 1); // base element index: 0, 0, 2, 0, 4, 4, 6, 0, 8, 8, 10... base element differs in one bit (0) from the current one (1) + size_t bit_index = log2_mn - calc_lsb_32((uint32_t)i) - 1; // the bit index where current element has the difference with the base + s_vec[i] = s_vec[base_el_index] * interm.e_sq[bit_index]; // (e_j)^-1 * (e_j)^2 = (e_j)^+1 + DBG_PRINT("[" << i << "] " << " " << base_el_index << ", " << bit_index << " : " << s_vec[i]); + } + + // prepare y_inv vector + scalar_vec_t y_inverse_powers(interm.c_bpp_mn); + y_inverse_powers[0] = 1; + for (size_t i = 1; i < interm.c_bpp_mn; ++i) + y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inv; + + // y^(mn+1) + scalar_t y_power_mnp1 = interm.y; + for (size_t i = 0; i < log2_mn; ++i) + y_power_mnp1 *= y_power_mnp1; + y_power_mnp1 *= interm.y; + DBG_VAL_PRINT(y_power_mnp1); + + // now calculate all multiplicands for common generators + + // g vector multiplicands: + // rwf * (r' * e * (1, y^-1, y^-2, ...) o s_vec + e^2 * z) = + // rwf * r' * e * ((1, y^-1, ...) o s_vec) + rwf * e^2 * z * (1, 1, ...) + scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z; + scalar_t rwf_r_e = rwf * interm.e_final * sig.r; + for (size_t i = 0; i < interm.c_bpp_mn; ++i) + g_scalars[i] += rwf_r_e * y_inverse_powers[i] * s_vec[i] + rwf_e_sq_z; + + DBG_PRINT("Hs(g_scalars): " << g_scalars.calc_hs()); + + // h vector multiplicands: + // rwf * (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) + // rwf * s' * e * s'_vec - rwf * e^2 * z * (1, 1...) - rwf * e^2 * (d o y<^(mn)) + //scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z; + scalar_t rwf_s_e = rwf * sig.s * interm.e_final; + scalar_t rwf_e_sq_y = rwf * interm.e_final_sq * interm.y; + for (size_t i = interm.c_bpp_mn - 1; i != SIZE_MAX; --i) + { + h_scalars[i] += rwf_s_e * s_vec[interm.c_bpp_mn - 1 - i] - rwf_e_sq_z - rwf_e_sq_y * d[i]; + rwf_e_sq_y *= interm.y; + } + + DBG_PRINT("Hs(h_scalars): " << h_scalars.calc_hs()); + + // G point multiplicands: + // rwf * (r' y s' - e ^ 2 * ((z - z ^ 2)*SUM(y^>mn) - z * y^(mn+1) * SUM(d)) = + // = rwf * r' y s' - rwf * e^2 * (z - z ^ 2)*SUM(y^>mn) + rwf * e^2 * z * y^(mn+1) * SUM(d) + G_scalar += rwf * sig.r * interm.y * sig.s + rwf_e_sq_y * sum_d * interm.z; + G_scalar -= rwf * interm.e_final_sq * (interm.z - interm.z_sq) * sum_of_powers(interm.y, log2_mn); + DBG_PRINT("sum_y: " << sum_of_powers(interm.y, log2_mn)); + DBG_PRINT("G_scalar: " << G_scalar); + + // H point multiplicands: + // rwf * delta + H_scalar += rwf * sig.delta; + DBG_PRINT("H_scalar: " << H_scalar); + + // uncommon generators' multiplicands + point_t summand_8 = c_point_0; // this summand to be multiplied by 8 before adding to the main summand + // - rwf * e^2 * A0 + summand_8 -= rwf * interm.e_final_sq * interm.A0; + DBG_PRINT("A0_scalar: " << c_scalar_Lm1 * interm.e_final_sq * rwf); + + // - rwf * e^2 * y^(mn+1) * (SUM{j=1..m} (z^2)^j * V_j)) + scalar_t e_sq_y_mn1_z_sq_power = rwf * interm.e_final_sq * y_power_mnp1; + for (size_t j = 0; j < bsc.commitments.size(); ++j) + { + e_sq_y_mn1_z_sq_power *= interm.z_sq; + summand_8 -= e_sq_y_mn1_z_sq_power * bsc.commitments[j]; + DBG_PRINT("V_scalar[" << j << "]: " << c_scalar_Lm1 * e_sq_y_mn1_z_sq_power); + } + + // - rwf * e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j) + scalar_t rwf_e_sq = rwf * interm.e_final_sq; + for (size_t j = 0; j < log2_mn; ++j) + { + summand_8 -= rwf_e_sq * (interm.e_sq[j] * interm.L[j] + get_e_inv(j) * get_e_inv(j) * interm.R[j]); + DBG_PRINT("L_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * interm.e_sq[j]); + DBG_PRINT("R_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * get_e_inv(j) * get_e_inv(j)); + } + + // - rwf * e * A - rwf * B = 0 + summand_8 -= rwf * interm.e_final * interm.A + rwf * interm.B; + DBG_PRINT("A_scalar: " << c_scalar_Lm1 * rwf * interm.e_final); + DBG_PRINT("B_scalar: " << c_scalar_Lm1 * rwf); + + summand_8.modify_mul8(); + summand += summand_8; + } + + point_t GH_exponents = c_point_0; + CT::calc_pedersen_commitment(G_scalar, H_scalar, GH_exponents); + bool result = multiexp_and_check_being_zero(g_scalars, h_scalars, summand + GH_exponents); + if (result) + DBG_PRINT(ENDL << " . . . . bpp_verify() -- SUCCEEDED!!!" << ENDL); + return result; +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + } + +} // namespace crypto diff --git a/src/crypto/range_proofs.cpp b/src/crypto/range_proofs.cpp index b6750a09..783a0c2b 100644 --- a/src/crypto/range_proofs.cpp +++ b/src/crypto/range_proofs.cpp @@ -6,7 +6,4 @@ namespace crypto { - const point_t& bpp_crypto_trait_zano::bpp_H = c_point_H; - - } diff --git a/src/crypto/range_proofs.h b/src/crypto/range_proofs.h index b3d04d68..623c5af0 100644 --- a/src/crypto/range_proofs.h +++ b/src/crypto/range_proofs.h @@ -1,14 +1,9 @@ -// Copyright (c) 2021 Zano Project (https://zano.org/) -// Copyright (c) 2021 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2021-2022 Zano Project (https://zano.org/) +// Copyright (c) 2021-2022 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #pragma once -// -// This file contains the implementation of range proof protocol. -// Namely, Bulletproofs+ https://eprint.iacr.org/2020/735.pdf -// - #include "epee/include/misc_log_ex.h" #include "crypto-sugar.h" @@ -28,27 +23,23 @@ namespace crypto return result; } - constexpr size_t c_bpp_log2_n = 6; - constexpr size_t c_bpp_n = 64; // 2^64 is the upper bound for the witness's range - constexpr size_t c_bpp_values_max = 16; // maximum number of elements in BP+ proof, i.e. max allowed BP+ outputs - constexpr size_t c_bpp_mn_max = c_bpp_n * c_bpp_values_max; + + // returns greatest k, s.t. 2**k <= v + // tests in crypto_tests_range_proofs.h + constexpr size_t constexpr_floor_log2(size_t v) + { + return v <= 1 ? 0 : constexpr_floor_log2(v >> 1) + 1; + } // returns smallest k, s.t. v <= 2**k - inline size_t calc_exp_power_of_2_upper_bound(size_t v) + // tests in crypto_tests_range_proofs.h + constexpr size_t constexpr_ceil_log2(size_t v) { - constexpr size_t max_v = (SIZE_MAX >> 1) + 1; - //if (v > max_v) - // return 0; - - size_t pow = 1, result = 0; - while (v > pow) - { - pow <<= 1; - ++result; - } - return result; + return v <= 1 ? 0 : constexpr_floor_log2(v - 1) + 1; } + + // returns least significant bit uing de Bruijn sequence // http://graphics.stanford.edu/~seander/bithacks.html inline uint8_t calc_lsb_32(uint32_t v) @@ -61,17 +52,28 @@ namespace crypto return multiply_de_bruijn_bit_position[((uint32_t)((v & -(int32_t)v) * 0x077CB531U)) >> 27]; } - + //////////////////////////////////////// // crypto trait for Zano //////////////////////////////////////// + template struct bpp_crypto_trait_zano { + static constexpr size_t c_bpp_n = N; // the upper bound for the witness's range + static constexpr size_t c_bpp_values_max = values_max; // maximum number of elements in BP+ proof, i.e. max allowed BP+ outputs + static constexpr size_t c_bpp_log2_n = constexpr_ceil_log2(c_bpp_n); + static constexpr size_t c_bpp_mn_max = c_bpp_n * c_bpp_values_max; + static void calc_pedersen_commitment(const scalar_t& value, const scalar_t& mask, point_t& commitment) { commitment = value * c_point_G + mask * c_point_H; } + static void calc_pedersen_commitment_2(const scalar_t& value, const scalar_t& mask1, const scalar_t& mask2, point_t& commitment) + { + commitment = value * c_point_G + mask1 * c_point_H + mask2 * c_point_H2; + } + static const scalar_t& get_initial_transcript() { static scalar_t value = hash_helper_t::hs("Zano BP+ initial transcript"); @@ -86,6 +88,7 @@ namespace crypto e = hsc.calc_hash(); } + // TODO: refactor with proper OOB handling static const point_t& get_generator(bool select_H, size_t index) { if (index >= c_bpp_mn_max) @@ -108,332 +111,22 @@ namespace crypto } static const point_t& bpp_H; - }; + static const point_t& bpp_H2; + }; // struct bpp_crypto_trait_zano + + template + const point_t& bpp_crypto_trait_zano::bpp_H = c_point_H; + + template + const point_t& bpp_crypto_trait_zano::bpp_H2 = c_point_H2; - struct bpp_signature - { - std::vector L; // size = log_2(m * n) - std::vector R; - public_key A0; - public_key A; - public_key B; - scalar_t r; - scalar_t s; - scalar_t delta; - }; - -#define DBG_VAL_PRINT(x) std::cout << #x ": " << x << ENDL -#define DBG_PRINT(x) std::cout << x << ENDL - - template - bool bpp_gen(const std::vector& values, const scalar_vec_t& masks, bpp_signature& sig, std::vector& commitments, uint8_t* p_err = nullptr) - { -#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ - if (!(cond)) { LOG_PRINT_RED("bpp_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ - if (p_err) { *p_err = err_code; } return false; } - - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() > 0 && values.size() <= c_bpp_values_max && values.size() == masks.size(), 1); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(masks.is_reduced(), 3); - - const size_t c_bpp_log2_m = calc_exp_power_of_2_upper_bound(values.size()); - const size_t c_bpp_m = 1ull << c_bpp_log2_m; - const size_t c_bpp_mn = c_bpp_m * c_bpp_n; - const size_t c_bpp_log2_mn = c_bpp_log2_m + c_bpp_log2_n; - - // pre-multiply all output points by c_scalar_1div8 - // in order to enforce these points to be in the prime-order subgroup (after mul by 8 in bpp_verify()) - - // calc commitments vector as commitments[i] = 1/8 * values[i] * G + 1/8 * masks[i] * H - commitments.resize(values.size()); - for (size_t i = 0; i < values.size(); ++i) - CT::calc_pedersen_commitment(scalar_t(values[i]) * c_scalar_1div8, masks[i] * c_scalar_1div8, commitments[i]); - - - // s.a. BP+ paper, page 15, eq. 11 - // decompose v into aL and aR: - // v = aL o (1, 2, 2^2, ..., 2^n-1), o - component-wise product aka Hadamard product - // aR = aL - (1, 1, ... 1) - // aR o aL = 0 - - // aLs = (aL_0, aL_1, ..., aL_m-1) -- `bit` matrix of c_bpp_m x c_bpp_n, each element is a scalar - - scalar_mat_t aLs(c_bpp_mn), aRs(c_bpp_mn); - aLs.zero(); - aRs.zero(); - // m >= values.size, first set up [0..values.size-1], then -- [values.size..m-1] (padding area) - for (size_t i = 0; i < values.size(); ++i) - { - uint64_t v = values[i]; - for (size_t j = 0; j < c_bpp_n; ++j) - { - if (v & 1) - aLs(i, j) = c_scalar_1; // aL = 1, aR = 0 - else - aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1 - v >>= 1; - } - } - - for (size_t i = values.size(); i < c_bpp_m; ++i) - for (size_t j = 0; j < c_bpp_n; ++j) - aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1 - - - // using e as Fiat-Shamir transcript - scalar_t e = CT::get_initial_transcript(); - DBG_PRINT("initial transcript: " << e); - - hash_helper_t::hs_t hsc; - CT::update_transcript(hsc, e, commitments); - - // BP+ paper, page 15: The prover begins with sending A = g^aL h^aR h^alpha (group element) - // so we calculate A0 = alpha * H + SUM(aL_i * G_i) + SUM(aR_i * H_i) - - scalar_t alpha = scalar_t::random(); - point_t A0 = alpha * CT::bpp_H; - - for (size_t i = 0; i < c_bpp_mn; ++i) - A0 += aLs[i] * CT::get_generator(false, i) + aRs[i] * CT::get_generator(true, i); - - // part of 1/8 defense scheme - A0 *= c_scalar_1div8; - A0.to_public_key(sig.A0); - - DBG_VAL_PRINT(alpha); - DBG_VAL_PRINT(A0); - - // calculate scalar challenges y and z - hsc.add_scalar(e); - hsc.add_pub_key(sig.A0); - scalar_t y = hsc.calc_hash(); - scalar_t z = hash_helper_t::hs(y); - e = z; // transcript for further steps - DBG_VAL_PRINT(y); - DBG_VAL_PRINT(z); - - // Computing vector d for aggregated version of the protocol (BP+ paper, page 17) - // (note: elements is stored column-by-column in memory) - // d = | 1 * z^(2*1), 1 * z^(2*2), 1 * z^(2*3), ..., 1 * z^(2*m) | - // | 2 * z^(2*1), 2 * z^(2*2), 2 * z^(2*3), ..., 2 * z^(2*m) | - // | 4 * z^(2*1), 4 * z^(2*2), 4 * z^(2*3), ..., 4 * z^(2*m) | - // | ....................................................................................... | - // | 2^(n-1) * z^(2*1), 2^(n-1) * z^(2*2), 2^(n-1) * z^(2*3), ..., 2^(n-1) * z^(2*m)) | - // Note: sum(d_i) = (2^n - 1) * ((z^2)^1 + (z^2)^2 + ... (z^2)^m)) = (2^n-1) * sum_of_powers(x^2, log(m)) - - scalar_t z_sq = z * z; - scalar_mat_t d(c_bpp_mn); - d(0, 0) = z_sq; - // first row - for (size_t i = 1; i < c_bpp_m; ++i) - d(i, 0) = d(i - 1, 0) * z_sq; - // all rows - for (size_t j = 1; j < c_bpp_n; ++j) - for (size_t i = 0; i < c_bpp_m; ++i) - d(i, j) = d(i, j - 1) + d(i, j - 1); - - DBG_PRINT("Hs(d): " << d.calc_hs()); - - // calculate extended Vandermonde vector y = (1, y, y^2, ..., y^(mn+1)) (BP+ paper, page 18, Fig. 3) - // (calculate two more elements (1 and y^(mn+1)) for convenience) - scalar_vec_t y_powers(c_bpp_mn + 2); - y_powers[0] = 1; - for (size_t i = 1; i <= c_bpp_mn + 1; ++i) - y_powers[i] = y_powers[i - 1] * y; - - const scalar_t& y_mn_p1 = y_powers[c_bpp_mn + 1]; - - DBG_PRINT("Hs(y_powers): " << y_powers.calc_hs()); - - // aL_hat = aL - 1*z - scalar_vec_t aLs_hat = aLs - z; - // aL_hat = aR + d o y^leftarr + 1*z where y^leftarr = (y^n, y^(n-1), ..., y) (BP+ paper, page 18, Fig. 3) - scalar_vec_t aRs_hat = aRs + z; - for (size_t i = 0; i < c_bpp_mn; ++i) - aRs_hat[i] += d[i] * y_powers[c_bpp_mn - i]; - - DBG_PRINT("Hs(aLs_hat): " << aLs_hat.calc_hs()); - DBG_PRINT("Hs(aRs_hat): " << aRs_hat.calc_hs()); - - // calculate alpha_hat - // alpha_hat = alpha + SUM(z^(2j) * gamma_j * y^(mn+1)) for j = 1..m - // i.e. \hat{\alpha} = \alpha + y^{m n+1} \sum_{j = 1}^{m} z^{2j} \gamma_j - scalar_t alpha_hat = 0; - for (size_t i = 0; i < masks.size(); ++i) - alpha_hat += d(i, 0) * masks[i]; - alpha_hat = alpha + y_mn_p1 * alpha_hat; - - DBG_VAL_PRINT(alpha_hat); - - // calculate y^-1, y^-2, ... - const scalar_t y_inverse = y.reciprocal(); - scalar_vec_t y_inverse_powers(c_bpp_mn / 2 + 1); // the greatest power we need is c_bpp_mn/2 (at the first reduction round) - y_inverse_powers[0] = 1; - for (size_t i = 1, size = y_inverse_powers.size(); i < size; ++i) - y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inverse; - - // prepare generator's vector - std::vector g(c_bpp_mn), h(c_bpp_mn); - for (size_t i = 0; i < c_bpp_mn; ++i) - { - g[i] = CT::get_generator(false, i); - h[i] = CT::get_generator(true, i); - } - - // WIP zk-argument called with zk-WIP(g, h, G, H, A_hat, aL_hat, aR_hat, alpha_hat) - - scalar_vec_t& a = aLs_hat; - scalar_vec_t& b = aRs_hat; - - sig.L.resize(c_bpp_log2_mn); - sig.R.resize(c_bpp_log2_mn); - - // zk-WIP reduction rounds (s.a. the preprint page 13 Fig. 1) - for (size_t n = c_bpp_mn / 2, ni = 0; n >= 1; n /= 2, ++ni) - { - DBG_PRINT(ENDL << "#" << ni); - - // zk-WIP(g, h, G, H, P, a, b, alpha) - - scalar_t dL = scalar_t::random(); - DBG_VAL_PRINT(dL); - scalar_t dR = scalar_t::random(); - DBG_VAL_PRINT(dR); - - // a = (a1, a2), b = (b1, b2) -- vectors of scalars - // cL = -- scalar - scalar_t cL = 0; - for (size_t i = 0; i < n; ++i) - cL += a[i] * y_powers[i + 1] * b[n + i]; - - DBG_VAL_PRINT(cL); - - // cR = * y^n -- scalar - scalar_t cR = 0; - for (size_t i = 0; i < n; ++i) - cR += a[n + i] * y_powers[i + 1] * b[i]; - cR *= y_powers[n]; - - DBG_VAL_PRINT(cR); - - // L = y^-n * a1 * g2 + b2 * h1 + cL * G + dL * H -- point - point_t sum = c_point_0; - for (size_t i = 0; i < n; ++i) - sum += a[i] * g[n + i]; - point_t L; - CT::calc_pedersen_commitment(cL, dL, L); - for (size_t i = 0; i < n; ++i) - L += b[n + i] * h[i]; - L += y_inverse_powers[n] * sum; - L *= c_scalar_1div8; - DBG_VAL_PRINT(L); - - // R = y^n * a2 * g1 + b1 * h2 + cR * G + dR * H -- point - sum.zero(); - for (size_t i = 0; i < n; ++i) - sum += a[n + i] * g[i]; - point_t R; - CT::calc_pedersen_commitment(cR, dR, R); - for (size_t i = 0; i < n; ++i) - R += b[i] * h[n + i]; - R += y_powers[n] * sum; - R *= c_scalar_1div8; - DBG_VAL_PRINT(R); - - // put L, R to the sig - L.to_public_key(sig.L[ni]); - R.to_public_key(sig.R[ni]); - - // update the transcript - hsc.add_scalar(e); - hsc.add_pub_key(sig.L[ni]); - hsc.add_pub_key(sig.R[ni]); - e = hsc.calc_hash(); - DBG_VAL_PRINT(e); - - // recalculate arguments for the next round - scalar_t e_squared = e * e; - scalar_t e_inverse = e.reciprocal(); - scalar_t e_inverse_squared = e_inverse * e_inverse; - scalar_t e_y_inv_n = e * y_inverse_powers[n]; - scalar_t e_inv_y_n = e_inverse * y_powers[n]; - - // g_hat = e^-1 * g1 + (e * y^-n) * g2 -- vector of points - for (size_t i = 0; i < n; ++i) - g[i] = e_inverse * g[i] + e_y_inv_n * g[n + i]; - - // h_hat = e * h1 + e^-1 * h2 -- vector of points - for (size_t i = 0; i < n; ++i) - h[i] = e * h[i] + e_inverse * h[n + i]; - - // P_hat = e^2 * L + P + e^-2 * R -- point - - // a_hat = e * a1 + e^-1 * y^n * a2 -- vector of scalars - for (size_t i = 0; i < n; ++i) - a[i] = e * a[i] + e_inv_y_n * a[n + i]; - - // b_hat = e^-1 * b1 + e * b2 -- vector of scalars - for (size_t i = 0; i < n; ++i) - b[i] = e_inverse * b[i] + e * b[n + i]; - - // alpha_hat = e^2 * dL + alpha + e^-2 * dR -- scalar - alpha_hat += e_squared * dL + e_inverse_squared * dR; - - // run next iteraton zk-WIP(g_hat, h_hat, G, H, P_hat, a_hat, b_hat, alpha_hat) - } - DBG_PRINT(""); - - // zk-WIP last round - scalar_t r = scalar_t::random(); - scalar_t s = scalar_t::random(); - scalar_t delta = scalar_t::random(); - scalar_t eta = scalar_t::random(); - DBG_VAL_PRINT(r); - DBG_VAL_PRINT(s); - DBG_VAL_PRINT(delta); - DBG_VAL_PRINT(eta); - - // A = r * g + s * h + (r y b + s y a) * G + delta * H -- point - point_t A = c_point_0; - CT::calc_pedersen_commitment(y * (r * b[0] + s * a[0]), delta, A); - A += r * g[0] + s * h[0]; - A *= c_scalar_1div8; - A.to_public_key(sig.A); - DBG_VAL_PRINT(A); - - // B = (r * y * s) * G + eta * H - point_t B = c_point_0; - CT::calc_pedersen_commitment(r * y * s, eta, B); - B *= c_scalar_1div8; - B.to_public_key(sig.B); - DBG_VAL_PRINT(B); - - // update the transcript - hsc.add_scalar(e); - hsc.add_pub_key(sig.A); - hsc.add_pub_key(sig.B); - e = hsc.calc_hash(); - DBG_VAL_PRINT(e); - - // finalize the signature - sig.r = r + e * a[0]; - sig.s = s + e * b[0]; - sig.delta = eta + e * delta + e * e * alpha_hat; - DBG_VAL_PRINT(sig.r); - DBG_VAL_PRINT(sig.s); - DBG_VAL_PRINT(sig.delta); - - return true; -#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE - } // bpp_gen() - - // efficient multiexponentiation (naive stub implementation atm, TODO) template bool multiexp_and_check_being_zero(const scalar_vec_t& g_scalars, const scalar_vec_t& h_scalars, const point_t& summand) { - CHECK_AND_ASSERT_MES(g_scalars.size() < c_bpp_mn_max, false, "g_scalars oversized"); - CHECK_AND_ASSERT_MES(h_scalars.size() < c_bpp_mn_max, false, "h_scalars oversized"); + CHECK_AND_ASSERT_MES(g_scalars.size() < CT::c_bpp_mn_max, false, "g_scalars oversized"); + CHECK_AND_ASSERT_MES(h_scalars.size() < CT::c_bpp_mn_max, false, "h_scalars oversized"); point_t result = summand; @@ -452,372 +145,6 @@ namespace crypto } - struct bpp_sig_commit_ref_t - { - bpp_sig_commit_ref_t(const bpp_signature& sig, const std::vector& commitments) - : sig(sig) - , commitments(commitments) - {} - const bpp_signature& sig; - const std::vector& commitments; - }; - - - template - bool bpp_verify(const std::vector& sigs, uint8_t* p_err = nullptr) - { -#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ - if (!(cond)) { LOG_PRINT_RED("bpp_verify: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ - if (p_err) { *p_err = err_code; } return false; } - - DBG_PRINT(ENDL << " . . . . bpp_verify() . . . . "); - - const size_t kn = sigs.size(); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(kn > 0, 1); - - struct intermediate_element_t - { - scalar_t y; - scalar_t z; - scalar_t z_sq; - scalar_vec_t e; - scalar_vec_t e_sq; - scalar_t e_final; - scalar_t e_final_sq; - size_t inv_e_offset; // offset in batch_for_inverse - size_t inv_y_offset; // offset in batch_for_inverse - size_t c_bpp_log2_m; - size_t c_bpp_m; - size_t c_bpp_mn; - point_t A; - point_t A0; - point_t B; - std::vector L; - std::vector R; - }; - std::vector interms(kn); - - size_t c_bpp_log2_m_max = 0; - for (size_t k = 0; k < kn; ++k) - { - const bpp_sig_commit_ref_t& bsc = sigs[k]; - const bpp_signature& sig = bsc.sig; - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bsc.commitments.size() > 0, 2); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() > 0 && sig.L.size() == sig.R.size(), 3); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.r.is_reduced() && sig.s.is_reduced() && sig.delta.is_reduced(), 4); - - intermediate_element_t& interm = interms[k]; - interm.c_bpp_log2_m = calc_exp_power_of_2_upper_bound(bsc.commitments.size()); - if (c_bpp_log2_m_max < interm.c_bpp_log2_m) - c_bpp_log2_m_max = interm.c_bpp_log2_m; - - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() == interm.c_bpp_log2_m + c_bpp_log2_n, 5); - - interm.c_bpp_m = 1ull << interm.c_bpp_log2_m; - interm.c_bpp_mn = interm.c_bpp_m * c_bpp_n; - - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A0.from_public_key(sig.A0), 6); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A.from_public_key(sig.A), 7); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.B.from_public_key(sig.B), 8); - interm.L.resize(sig.L.size()); - interm.R.resize(sig.R.size()); - for (size_t i = 0; i < interm.L.size(); ++i) - { - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.L[i].from_public_key(sig.L[i]), 9); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.R[i].from_public_key(sig.R[i]), 10); - } - } - const size_t c_bpp_m_max = 1ull << c_bpp_log2_m_max; - const size_t c_bpp_mn_max = c_bpp_m_max * c_bpp_n; - const size_t c_bpp_LR_size_max = c_bpp_log2_m_max + c_bpp_log2_n; - - - // - // prepare stuff - // - /* - std::vector g(c_bpp_mn_max), h(c_bpp_mn_max); - for (size_t i = 0; i < c_bpp_mn_max; ++i) - { - g[i] = CT::get_generator(false, i); - h[i] = CT::get_generator(true, i); - } - */ - - scalar_vec_t batch_for_inverse; - batch_for_inverse.reserve(kn + kn * c_bpp_LR_size_max); - - - for (size_t k = 0; k < kn; ++k) - { - DBG_PRINT(ENDL << "SIG #" << k); - const bpp_sig_commit_ref_t& bsc = sigs[k]; - const bpp_signature& sig = bsc.sig; - intermediate_element_t& interm = interms[k]; - - // restore y and z - // using e as Fiat-Shamir transcript - scalar_t e = CT::get_initial_transcript(); - DBG_PRINT("initial transcript: " << e); - hash_helper_t::hs_t hsc; - CT::update_transcript(hsc, e, bsc.commitments); - // calculate scalar challenges y and z - hsc.add_scalar(e); - hsc.add_pub_key(sig.A0); - hsc.assign_calc_hash(interm.y); - interm.z = hash_helper_t::hs(interm.y); - interm.z_sq = interm.z * interm.z; - DBG_VAL_PRINT(interm.y); - DBG_VAL_PRINT(interm.z); - e = interm.z; // transcript for further steps - - interm.inv_y_offset = batch_for_inverse.size(); - batch_for_inverse.push_back(interm.y); - interm.inv_e_offset = batch_for_inverse.size(); - - interm.e.resize(sig.L.size()); - interm.e_sq.resize(sig.L.size()); - - for (size_t i = 0; i < sig.L.size(); ++i) - { - hsc.add_scalar(e); - hsc.add_pub_key(sig.L[i]); - hsc.add_pub_key(sig.R[i]); - hsc.assign_calc_hash(e); - interm.e[i] = e; - interm.e_sq[i] = e * e; - DBG_PRINT("e[" << i << "]: " << e); - batch_for_inverse.push_back(e); - } - - hsc.add_scalar(e); - hsc.add_pub_key(sig.A); - hsc.add_pub_key(sig.B); - hsc.assign_calc_hash(interm.e_final); - interm.e_final_sq = interm.e_final * interm.e_final; - DBG_VAL_PRINT(interm.e_final); - } - - batch_for_inverse.invert(); - - // Notation: - // 1_vec ^ n = (1, 1, 1, ..., 1) - // 2_vec ^ n = (2^0, 2^1, 2^2, ..., 2^(n-1)) - // -1_vec ^ n = ((-1)^0, (-1)^1, (-1)^2, ... (-1)^(n-1)) = (1, -1, 1, -1, ...) - // y<^n = (y^n, y^(n-1), ..., y^1) - // y>^n = (y^1, y^2, ..., y^n) - - // from page 13, Fig 1: - // Verifier outputs Accept IFF the following equality holds (single proof): - // P^e^2 * A^e * B == g ^ (r' e) * h ^ (s' e) * G ^ (r' y s') * H ^ delta' - // (where g and h are calculated in each round) - // The same equation in additive notation: - // e^2 * P + e * A + B == (r' * e) * g + (s' * e) * h + (r' y s') * G + delta' * H - // <=> - // (r' * e) * g + (s' * e) * h + (r' y s') * G + delta' * H - e^2 * P - e * A - B == 0 (*) - // where A, B, r', s', delta' is taken from the signature - // and P_{k+1} = e^2 * L_k + P_k + e^-2 * R_k for all rounds - // - // from page 18, Fig 3: - // P and V computes: - // A_hat = A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h + - // + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) + - // + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G - // (calculated once) - // - // As suggested in Section 6.1 "Practical Optimizations": - // 1) g and h exponentianions can be optimized in order not to be calculated at each round as the following (page 20): - // - // (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta' * H - - // - e^2 * A_hat - // - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j) - // - e * A - B = 0 (**) - // - // where: - // g, h - vector of fixed generators - // s_vec_i = y^(1-i) * PROD{j=1..log(n)}(e_j ^ b(i,j)) - // s'_vec_i = PROD{j=1..log(n)}(e_j ^ -b(i,j)) - // b(i, j) = { 2 * ((1<<(j-1)) & (i-1)) - 1) (counting both from 1) (page 20) - // b(i, j) = { 2 * ((1< - - // (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta' * H - - // - e^2 * (A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h + - // + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) + - // + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G - // ) - // - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j) - // - e * A - B = 0 - - // => - - // (for single signature) - // - // (r' * e * s_vec - e^2 * (- 1_vec^(mn) * z)) * g | these are - // + (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) * h | fixed generators - // + (r' y s' - e^2 * ((z - z^2)*SUM(y^>mn) - z*y^(mn+1)*SUM(d)) * G | across all - // + delta' * H | the signatures - // - // - e^2 * A0 - // - e^2 * y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j)) - // - e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j) - // - e * A - B = 0 (***) - // - // All (***) will be muptiplied by random weightning factor and then summed up. - - // Calculate cummulative sclalar multiplicand for fixed generators across all the sigs. - scalar_vec_t g_scalars; - g_scalars.resize(c_bpp_mn_max, 0); - scalar_vec_t h_scalars; - h_scalars.resize(c_bpp_mn_max, 0); - scalar_t G_scalar = 0; - scalar_t H_scalar = 0; - point_t summand = c_point_0; - - for (size_t k = 0; k < kn; ++k) - { - DBG_PRINT(ENDL << "SIG #" << k); - const bpp_sig_commit_ref_t& bsc = sigs[k]; - const bpp_signature& sig = bsc.sig; - intermediate_element_t& interm = interms[k]; - - // random weightning factor for speed-optimized batch verification (preprint page 20) - const scalar_t rwf = scalar_t::random(); - DBG_PRINT("rwf: " << rwf); - - // prepare d vector (see also d structure description in proof function) - scalar_mat_t d(interm.c_bpp_mn); - d(0, 0) = interm.z_sq; - // first row - for (size_t i = 1; i < interm.c_bpp_m; ++i) - d(i, 0) = d(i - 1, 0) * interm.z_sq; - // all rows - for (size_t j = 1; j < c_bpp_n; ++j) - for (size_t i = 0; i < interm.c_bpp_m; ++i) - d(i, j) = d(i, j - 1) + d(i, j - 1); - // sum(d) (see also note in proof function for this) - static const scalar_t c_scalar_2_power_n_minus_1 = { 0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000 }; - const scalar_t sum_d = c_scalar_2_power_n_minus_1 * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); - - DBG_PRINT("Hs(d): " << d.calc_hs()); - DBG_PRINT("sum(d): " << sum_d); - - const scalar_t& y_inv = batch_for_inverse[interm.inv_y_offset]; - auto get_e_inv = [&](size_t i) { return batch_for_inverse[interm.inv_e_offset + i]; }; // i belongs to [0; L.size()-1] - - // prepare s_vec (unlike the paper here we moved y-component out of s_vec for convenience, so s_vec'[x] = s_vec[~x & (MN-1)]) - // complexity (sc_mul's): MN+2*log2(MN)-2 - // the idea is the following: - // s_vec[00000b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^-1 * (e_1)^-1 * (e_0)^-1 - // s_vec[00101b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^+1 * (e_1)^-1 * (e_0)^+1 - const size_t log2_mn = sig.L.size(); // at the beginning we made sure that sig.L.size() == c_bpp_log2_m + c_bpp_log2_n - scalar_vec_t s_vec(interm.c_bpp_mn); - s_vec[0] = get_e_inv(0); - for (size_t i = 1; i < log2_mn; ++i) - s_vec[0] *= get_e_inv(i); // s_vec[0] = (e_0)^-1 * (e_1)^-1 * .. (e_{log2_mn-1})^-1 - DBG_PRINT("[0] " << s_vec[0]); - for (size_t i = 1; i < interm.c_bpp_mn; ++i) - { - size_t base_el_index = i & (i - 1); // base element index: 0, 0, 2, 0, 4, 4, 6, 0, 8, 8, 10... base element differs in one bit (0) from the current one (1) - size_t bit_index = log2_mn - calc_lsb_32((uint32_t)i) - 1; // the bit index where current element has the difference with the base - s_vec[i] = s_vec[base_el_index] * interm.e_sq[bit_index]; // (e_j)^-1 * (e_j)^2 = (e_j)^+1 - DBG_PRINT("[" << i << "] " << " " << base_el_index << ", " << bit_index << " : " << s_vec[i]); - } - - // prepare y_inv vector - scalar_vec_t y_inverse_powers(interm.c_bpp_mn); - y_inverse_powers[0] = 1; - for (size_t i = 1; i < interm.c_bpp_mn; ++i) - y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inv; - - // y^(mn+1) - scalar_t y_power_mnp1 = interm.y; - for (size_t i = 0; i < log2_mn; ++i) - y_power_mnp1 *= y_power_mnp1; - y_power_mnp1 *= interm.y; - DBG_VAL_PRINT(y_power_mnp1); - - // now calculate all multiplicands for common generators - - // g vector multiplicands: - // rwf * (r' * e * (1, y^-1, y^-2, ...) o s_vec + e^2 * z) = - // rwf * r' * e * ((1, y^-1, ...) o s_vec) + rwf * e^2 * z * (1, 1, ...) - scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z; - scalar_t rwf_r_e = rwf * interm.e_final * sig.r; - for (size_t i = 0; i < interm.c_bpp_mn; ++i) - g_scalars[i] += rwf_r_e * y_inverse_powers[i] * s_vec[i] + rwf_e_sq_z; - - DBG_PRINT("Hs(g_scalars): " << g_scalars.calc_hs()); - - // h vector multiplicands: - // rwf * (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) - // rwf * s' * e * s'_vec - rwf * e^2 * z * (1, 1...) - rwf * e^2 * (d o y<^(mn)) - //scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z; - scalar_t rwf_s_e = rwf * sig.s * interm.e_final; - scalar_t rwf_e_sq_y = rwf * interm.e_final_sq * interm.y; - for (size_t i = interm.c_bpp_mn - 1; i != SIZE_MAX; --i) - { - h_scalars[i] += rwf_s_e * s_vec[interm.c_bpp_mn - 1 - i] - rwf_e_sq_z - rwf_e_sq_y * d[i]; - rwf_e_sq_y *= interm.y; - } - - DBG_PRINT("Hs(h_scalars): " << h_scalars.calc_hs()); - - // G point multiplicands: - // rwf * (r' y s' - e ^ 2 * ((z - z ^ 2)*SUM(y^>mn) - z * y^(mn+1) * SUM(d)) = - // = rwf * r' y s' - rwf * e^2 * (z - z ^ 2)*SUM(y^>mn) + rwf * e^2 * z * y^(mn+1) * SUM(d) - G_scalar += rwf * sig.r * interm.y * sig.s + rwf_e_sq_y * sum_d * interm.z; - G_scalar -= rwf * interm.e_final_sq * (interm.z - interm.z_sq) * sum_of_powers(interm.y, log2_mn); - DBG_PRINT("sum_y: " << sum_of_powers(interm.y, log2_mn)); - DBG_PRINT("G_scalar: " << G_scalar); - - // H point multiplicands: - // rwf * delta - H_scalar += rwf * sig.delta; - DBG_PRINT("H_scalar: " << H_scalar); - - // uncommon generators' multiplicands - point_t summand_8 = c_point_0; // this summand to be multiplied by 8 before adding to the main summand - // - rwf * e^2 * A0 - summand_8 -= rwf * interm.e_final_sq * interm.A0; - DBG_PRINT("A0_scalar: " << c_scalar_Lm1 * interm.e_final_sq * rwf); - - // - rwf * e^2 * y^(mn+1) * (SUM{j=1..m} (z^2)^j * V_j)) - scalar_t e_sq_y_mn1_z_sq_power = rwf * interm.e_final_sq * y_power_mnp1; - for (size_t j = 0; j < bsc.commitments.size(); ++j) - { - e_sq_y_mn1_z_sq_power *= interm.z_sq; - summand_8 -= e_sq_y_mn1_z_sq_power * bsc.commitments[j]; - DBG_PRINT("V_scalar[" << j << "]: " << c_scalar_Lm1 * e_sq_y_mn1_z_sq_power); - } - - // - rwf * e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j) - scalar_t rwf_e_sq = rwf * interm.e_final_sq; - for (size_t j = 0; j < log2_mn; ++j) - { - summand_8 -= rwf_e_sq * (interm.e_sq[j] * interm.L[j] + get_e_inv(j) * get_e_inv(j) * interm.R[j]); - DBG_PRINT("L_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * interm.e_sq[j]); - DBG_PRINT("R_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * get_e_inv(j) * get_e_inv(j)); - } - - // - rwf * e * A - rwf * B = 0 - summand_8 -= rwf * interm.e_final * interm.A + rwf * interm.B; - DBG_PRINT("A_scalar: " << c_scalar_Lm1 * rwf * interm.e_final); - DBG_PRINT("B_scalar: " << c_scalar_Lm1 * rwf); - - summand_8.modify_mul8(); - summand += summand_8; - } - - point_t GH_exponents = c_point_0; - CT::calc_pedersen_commitment(G_scalar, H_scalar, GH_exponents); - bool result = multiexp_and_check_being_zero(g_scalars, h_scalars, summand + GH_exponents); - if (result) - DBG_PRINT(ENDL << " . . . . bpp_verify() -- SUCCEEDED!!!" << ENDL); - return result; -#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE - } - } // namespace crypto + +#include "range_proof_bpp.h" diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 19c922e2..68350097 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -487,6 +487,7 @@ struct test_keeper_t //////////////////////////////////////////////////////////////////////////////// #include "L2S.h" +#include "crypto_tests_range_proofs.h" //////////////////////////////////////////////////////////////////////////////// @@ -1843,6 +1844,7 @@ TEST(crypto, sc_get_bit) return true; } + // // test's runner // diff --git a/tests/functional_tests/crypto_tests_range_proofs.h b/tests/functional_tests/crypto_tests_range_proofs.h new file mode 100644 index 00000000..94f4db0b --- /dev/null +++ b/tests/functional_tests/crypto_tests_range_proofs.h @@ -0,0 +1,182 @@ +// Copyright (c) 2021 Zano Project (https://zano.org/) +// Copyright (c) 2021 sowle (val@zano.org, crypto.sowle@gmail.com) +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once + +// calc weighted inner pruduct of av and bv w.r.t. Vandermonde vector (y, y^2, y^3, ..., y^n) +// = (<> -- standard inner product, o - componen-wise) +// s.a. BP+ paper, pages 3-4 +bool wip_vandermonde(const scalar_vec_t& av, const scalar_vec_t& bv, const scalar_t& y, scalar_t& result) +{ + result = 0; + size_t n = av.size(); + if (n != bv.size()) + return false; + + scalar_t y_powered = 1; + for (size_t i = 0; i < n; ++i) + { + y_powered *= y; + result.assign_muladd(av[i] * bv[i], y_powered, result); // result.a += av[i] * bv[i] * y_powered; + } + + return true; +} + + +static_assert(constexpr_floor_log2(0) == 0, ""); +static_assert(constexpr_floor_log2(1) == 0, ""); +static_assert(constexpr_floor_log2(2) == 1, ""); +static_assert(constexpr_floor_log2(3) == 1, ""); +static_assert(constexpr_floor_log2(4) == 2, ""); +static_assert(constexpr_floor_log2(5) == 2, ""); +static_assert(constexpr_floor_log2(64) == 6, ""); +static_assert(constexpr_floor_log2(100) == 6, ""); +static_assert(constexpr_floor_log2(100000000) == 26, ""); +static_assert(constexpr_floor_log2(0x7fffffffffffffff) == 62, ""); +static_assert(constexpr_floor_log2(SIZE_MAX) == 63, ""); + +static_assert(constexpr_ceil_log2(0) == 0, ""); +static_assert(constexpr_ceil_log2(1) == 0, ""); +static_assert(constexpr_ceil_log2(2) == 1, ""); +static_assert(constexpr_ceil_log2(3) == 2, ""); +static_assert(constexpr_ceil_log2(4) == 2, ""); +static_assert(constexpr_ceil_log2(5) == 3, ""); +static_assert(constexpr_ceil_log2(64) == 6, ""); +static_assert(constexpr_ceil_log2(100) == 7, ""); +static_assert(constexpr_ceil_log2(100000000) == 27, ""); +static_assert(constexpr_ceil_log2(0x7fffffffffffffff) == 63, ""); +static_assert(constexpr_ceil_log2(SIZE_MAX) == 64, ""); + + +TEST(bpp, basics) +{ + /* + srand(0); + for (size_t i = 0; i < 10; ++i) + std::cout << scalar_t::random().to_string_as_secret_key() << ENDL; + */ + + point_t H = hash_helper_t::hp(c_point_G); + ASSERT_EQ(H, c_point_H); + std::string h2_hash_str("h2_generator"); + point_t H2 = hash_helper_t::hp(h2_hash_str.c_str(), h2_hash_str.size()); + ASSERT_EQ(H2, c_point_H2); + LOG_PRINT_L0("c_point_0 = " << c_point_0 << " = { " << c_point_0.to_hex_comma_separated_uint64_str() << " }"); + LOG_PRINT_L0("Zano G = " << c_point_G << " = { " << c_point_G.to_hex_comma_separated_bytes_str() << " }"); + LOG_PRINT_L0("Zano H = " << H << " = { " << H.to_hex_comma_separated_uint64_str() << " }"); + LOG_PRINT_L0("Zano H2 = " << H2 << " = { " << H2.to_hex_comma_separated_uint64_str() << " }"); + + scalar_vec_t values = { 5 }; + scalar_vec_t masks = { 0 }; + bpp_signature bpp_sig; + std::vector commitments; + uint8_t err = 0; + + bool r = bpp_gen>(values, masks, bpp_sig, commitments, &err); + + ASSERT_TRUE(r); + + return true; +} + + +TEST(bpp, two) +{ + std::vector signatures_vector; + signatures_vector.reserve(10); + std::vector> commitments_vector; + commitments_vector.reserve(10); + + std::vector sigs; + uint8_t err = 0; + bool r = false; + + { + signatures_vector.resize(signatures_vector.size() + 1); + bpp_signature &bpp_sig = signatures_vector.back(); + commitments_vector.resize(commitments_vector.size() + 1); + std::vector& commitments = commitments_vector.back(); + + scalar_vec_t values = { 5 }; + scalar_vec_t masks = { scalar_t(77 + 256 * 77) }; + + r = bpp_gen>(values, masks, bpp_sig, commitments, &err); + ASSERT_TRUE(r); + + sigs.emplace_back(bpp_sig, commitments); + } + + { + signatures_vector.resize(signatures_vector.size() + 1); + bpp_signature &bpp_sig = signatures_vector.back(); + commitments_vector.resize(commitments_vector.size() + 1); + std::vector& commitments = commitments_vector.back(); + + scalar_vec_t values = { 5, 700, 8 }; + scalar_vec_t masks = { scalar_t(77 + 256 * 77), scalar_t(255), scalar_t(17) }; + + r = bpp_gen>(values, masks, bpp_sig, commitments, &err); + ASSERT_TRUE(r); + + sigs.emplace_back(bpp_sig, commitments); + } + + r = bpp_verify>(sigs, &err); + ASSERT_TRUE(r); + + + return true; +} + + +TEST(bpp, power_256) +{ + // make sure the BPP implementation supports values up to 2^256 (Zarcanum needs 2^170 since b_a < z * 2^64, where z = 2^106, s.a. Zarcanum preprint, page 21) + std::vector signatures_vector; + signatures_vector.reserve(10); + std::vector> commitments_vector; + commitments_vector.reserve(10); + + std::vector sig_ommit_refs; + uint8_t err = 0; + bool r = false; + + { + signatures_vector.resize(signatures_vector.size() + 1); + bpp_signature &bpp_sig = signatures_vector.back(); + commitments_vector.resize(commitments_vector.size() + 1); + std::vector& commitments = commitments_vector.back(); + + scalar_vec_t values = { 5 }; + scalar_vec_t masks = { scalar_t(77 + 256 * 77) }; + + r = bpp_gen>(values, masks, bpp_sig, commitments, &err); + ASSERT_TRUE(r); + + sig_ommit_refs.emplace_back(bpp_sig, commitments); + } + + { + signatures_vector.resize(signatures_vector.size() + 1); + bpp_signature &bpp_sig = signatures_vector.back(); + commitments_vector.resize(commitments_vector.size() + 1); + std::vector& commitments = commitments_vector.back(); + + scalar_vec_t values = { 5, 700, 8 }; + scalar_vec_t masks = { scalar_t(77 + 256 * 77), scalar_t(255), scalar_t(17) }; + + r = bpp_gen>(values, masks, bpp_sig, commitments, &err); + ASSERT_TRUE(r); + + sig_ommit_refs.emplace_back(bpp_sig, commitments); + } + + r = bpp_verify>(sig_ommit_refs, &err); + ASSERT_TRUE(r); + + + return true; +} + From d9b6e479a8b0ddae2bd06f2c66144e28c172e913 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 10 Apr 2022 19:50:21 +0200 Subject: [PATCH 0133/1271] removed unused files from tor --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 19c9c5d7..63577d17 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 19c9c5d79d40b7bde08598cf994ca0599803f489 +Subproject commit 63577d17cbae659ec6a91ca30410eb7559253485 From ff5a628420be73228ed599c361fe32a37b4aa9fc Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 10 Apr 2022 20:33:31 +0200 Subject: [PATCH 0134/1271] provided exterval var for submodules --- CMakeLists.txt | 3 +++ contrib/tor-connect | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f21fb093..675c14d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,6 +57,9 @@ endif() set(USE_PCH FALSE CACHE BOOL "Use shared precompiled headers") +get_filename_component(EPEE_INCLUDE_PATH_FOR_SUBMODULES contrib/epee/include ABSOLUTE ) +message("Epee absolute path set to: ${EPEE_INCLUDE_PATH_FOR_SUBMODULES}") + include_directories(src contrib/eos_portable_archive contrib contrib/epee/include "${CMAKE_BINARY_DIR}/version" "${CMAKE_BINARY_DIR}/contrib/zlib") add_definitions(-DSTATICLIB) diff --git a/contrib/tor-connect b/contrib/tor-connect index 63577d17..800d1e9b 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 63577d17cbae659ec6a91ca30410eb7559253485 +Subproject commit 800d1e9b05807424ad64f41cdd7af2ddee2e5757 From 00a99e037b8d3854cd70775b9da33c192f2ab5ce Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Sun, 10 Apr 2022 20:39:13 +0200 Subject: [PATCH 0135/1271] updated tor --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 800d1e9b..2029c284 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 800d1e9b05807424ad64f41cdd7af2ddee2e5757 +Subproject commit 2029c2842817f964947b624d3fa9cc0c5324f9e0 From d0a66c379383ff5cb10e25e6d04bd77d9e023ae9 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 11 Apr 2022 20:31:08 +0200 Subject: [PATCH 0136/1271] fixed issues for macos/clang --- contrib/epee/include/net/levin_client.inl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/epee/include/net/levin_client.inl b/contrib/epee/include/net/levin_client.inl index c55d5ce3..62089afa 100644 --- a/contrib/epee/include/net/levin_client.inl +++ b/contrib/epee/include/net/levin_client.inl @@ -109,7 +109,7 @@ namespace epee template int levin_client_impl_t::notify(int command, const std::string& in_buff) { - if (!is_connected()) + if (!this->is_connected()) return -1; bucket_head head = { 0 }; @@ -130,7 +130,7 @@ namespace epee template int levin_client_impl2::invoke(int command, const std::string& in_buff, std::string& buff_out) { - if (!is_connected()) + if (!this->is_connected()) return -1; bucket_head2 head = { 0 }; @@ -180,7 +180,7 @@ namespace epee template int levin_client_impl2::notify(int command, const std::string& in_buff) { - if (!is_connected()) + if (!this->is_connected()) return -1; bucket_head2 head = { 0 }; From 218c6a38cb58d63f0beecd158bf2d215649de07c Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 11 Apr 2022 20:43:43 +0200 Subject: [PATCH 0137/1271] crypto: Bulletproofs+ with double-blinded commitments extension implemented (nicknamed as bppe), basic tests added --- src/crypto/range_proof_bppe.h | 719 ++++++++++++++++++ src/crypto/range_proofs.h | 1 + .../crypto_tests_range_proofs.h | 79 ++ 3 files changed, 799 insertions(+) create mode 100644 src/crypto/range_proof_bppe.h diff --git a/src/crypto/range_proof_bppe.h b/src/crypto/range_proof_bppe.h new file mode 100644 index 00000000..83fc83c2 --- /dev/null +++ b/src/crypto/range_proof_bppe.h @@ -0,0 +1,719 @@ +// Copyright (c) 2022 Zano Project (https://zano.org/) +// Copyright (c) 2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +#pragma once + +// +// This file contains the implementation of range proof protocol. +// Namely, Bulletproofs+ https://eprint.iacr.org/2020/735 +// Double-blinded commitments extension implemented as in Appendix D in the Zarcanum whitepaper: https://eprint.iacr.org/2021/1478 + +namespace crypto +{ + struct bppe_signature + { + std::vector L; // size = log_2(m * n) + std::vector R; + public_key A0; + public_key A; + public_key B; + scalar_t r; + scalar_t s; + scalar_t delta_1; + scalar_t delta_2; + }; + +#define DBG_VAL_PRINT(x) std::cout << #x ": " << x << ENDL +#define DBG_PRINT(x) std::cout << x << ENDL + + template + bool bppe_gen(const scalar_vec_t& values, const scalar_vec_t& masks, const scalar_vec_t& masks2, bppe_signature& sig, std::vector& commitments, uint8_t* p_err = nullptr) + { +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("bppe_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() > 0 && values.size() <= CT::c_bpp_values_max && values.size() == masks.size() && masks.size() == masks2.size(), 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(masks.is_reduced() && masks2.is_reduced(), 3); + + const size_t c_bpp_log2_m = constexpr_ceil_log2(values.size()); + const size_t c_bpp_m = 1ull << c_bpp_log2_m; + const size_t c_bpp_mn = c_bpp_m * CT::c_bpp_n; + const size_t c_bpp_log2_mn = c_bpp_log2_m + CT::c_bpp_log2_n; + + // pre-multiply all output points by c_scalar_1div8 + // in order to enforce these points to be in the prime-order subgroup (after mul by 8 in bpp_verify()) + + // calc commitments vector as commitments[i] = 1/8 * values[i] * G + 1/8 * masks[i] * H + 1/8 * masks2[i] * H2 + commitments.resize(values.size()); + for (size_t i = 0; i < values.size(); ++i) + CT::calc_pedersen_commitment_2(values[i] * c_scalar_1div8, masks[i] * c_scalar_1div8, masks2[i] * c_scalar_1div8, commitments[i]); + + + // s.a. BP+ paper, page 15, eq. 11 + // decompose v into aL and aR: + // v = aL o (1, 2, 2^2, ..., 2^n-1), o - component-wise product aka Hadamard product + // aR = aL - (1, 1, ... 1) + // aR o aL = 0 + + // aLs = (aL_0, aL_1, ..., aL_m-1) -- `bit` matrix of c_bpp_m x c_bpp_n, each element is a scalar + + scalar_mat_t aLs(c_bpp_mn), aRs(c_bpp_mn); + aLs.zero(); + aRs.zero(); + // m >= values.size, first set up [0..values.size-1], then -- [values.size..m-1] (padding area) + for (size_t i = 0; i < values.size(); ++i) + { + const scalar_t& v = values[i]; + for (size_t j = 0; j < CT::c_bpp_n; ++j) + { + if (v.get_bit(j)) + aLs(i, j) = c_scalar_1; // aL = 1, aR = 0 + else + aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1 + } + } + + for (size_t i = values.size(); i < c_bpp_m; ++i) + for (size_t j = 0; j < CT::c_bpp_n; ++j) + aRs(i, j) = c_scalar_Lm1; // aL = 0, aR = -1 + + + // using e as Fiat-Shamir transcript + scalar_t e = CT::get_initial_transcript(); + DBG_PRINT("initial transcript: " << e); + + hash_helper_t::hs_t hsc; + CT::update_transcript(hsc, e, commitments); + + // Zarcanum paper, page 33, Fig. D.3: The prover chooses alpha_1, alpha_2 and computes A = g^aL h^aR h_1^alpha_1 h_2^alpha_2 + // so we calculate A0 = alpha_1 * H + alpha_2 * H_2 + SUM(aL_i * G_i) + SUM(aR_i * H_i) + + scalar_t alpha_1 = scalar_t::random(), alpha_2 = scalar_t::random(); + point_t A0 = alpha_1 * CT::bpp_H + alpha_2 * CT::bpp_H2; + + for (size_t i = 0; i < c_bpp_mn; ++i) + A0 += aLs[i] * CT::get_generator(false, i) + aRs[i] * CT::get_generator(true, i); + + // part of 1/8 defense scheme + A0 *= c_scalar_1div8; + A0.to_public_key(sig.A0); + + DBG_VAL_PRINT(alpha_1); + DBG_VAL_PRINT(alpha_2); + DBG_VAL_PRINT(A0); + + // calculate scalar challenges y and z + hsc.add_scalar(e); + hsc.add_pub_key(sig.A0); + scalar_t y = hsc.calc_hash(); + scalar_t z = hash_helper_t::hs(y); + e = z; // transcript for further steps + DBG_VAL_PRINT(y); + DBG_VAL_PRINT(z); + + // Computing vector d for aggregated version of the protocol (BP+ paper, page 17) + // (note: elements is stored column-by-column in memory) + // d = | 1 * z^(2*1), 1 * z^(2*2), 1 * z^(2*3), ..., 1 * z^(2*m) | + // | 2 * z^(2*1), 2 * z^(2*2), 2 * z^(2*3), ..., 2 * z^(2*m) | + // | 4 * z^(2*1), 4 * z^(2*2), 4 * z^(2*3), ..., 4 * z^(2*m) | + // | ....................................................................................... | + // | 2^(n-1) * z^(2*1), 2^(n-1) * z^(2*2), 2^(n-1) * z^(2*3), ..., 2^(n-1) * z^(2*m)) | + // Note: sum(d_i) = (2^n - 1) * ((z^2)^1 + (z^2)^2 + ... (z^2)^m)) = (2^n-1) * sum_of_powers(x^2, log(m)) + + scalar_t z_sq = z * z; + scalar_mat_t d(c_bpp_mn); + d(0, 0) = z_sq; + // first row + for (size_t i = 1; i < c_bpp_m; ++i) + d(i, 0) = d(i - 1, 0) * z_sq; + // all rows + for (size_t j = 1; j < CT::c_bpp_n; ++j) + for (size_t i = 0; i < c_bpp_m; ++i) + d(i, j) = d(i, j - 1) + d(i, j - 1); + + DBG_PRINT("Hs(d): " << d.calc_hs()); + + // calculate extended Vandermonde vector y = (1, y, y^2, ..., y^(mn+1)) (BP+ paper, page 18, Fig. 3) + // (calculate two more elements (1 and y^(mn+1)) for convenience) + scalar_vec_t y_powers(c_bpp_mn + 2); + y_powers[0] = 1; + for (size_t i = 1; i <= c_bpp_mn + 1; ++i) + y_powers[i] = y_powers[i - 1] * y; + + const scalar_t& y_mn_p1 = y_powers[c_bpp_mn + 1]; + + DBG_PRINT("Hs(y_powers): " << y_powers.calc_hs()); + + // aL_hat = aL - 1*z + scalar_vec_t aLs_hat = aLs - z; + // aL_hat = aR + d o y^leftarr + 1*z where y^leftarr = (y^n, y^(n-1), ..., y) (BP+ paper, page 18, Fig. 3) + scalar_vec_t aRs_hat = aRs + z; + for (size_t i = 0; i < c_bpp_mn; ++i) + aRs_hat[i] += d[i] * y_powers[c_bpp_mn - i]; + + DBG_PRINT("Hs(aLs_hat): " << aLs_hat.calc_hs()); + DBG_PRINT("Hs(aRs_hat): " << aRs_hat.calc_hs()); + + // calculate alpha_hat + // alpha_hat_1 = alpha_1 + SUM(z^(2j) * gamma_1,j * y^(mn+1)) for j = 1..m + // alpha_hat_2 = alpha_2 + SUM(z^(2j) * gamma_2,j * y^(mn+1)) for j = 1..m + // i.e. \hat{\alpha} = \alpha + y^{m n+1} \sum_{j = 1}^{m} z^{2j} \gamma_j + scalar_t alpha_hat_1 = 0, alpha_hat_2 = 0; + for (size_t i = 0; i < masks.size(); ++i) + { + alpha_hat_1 += d(i, 0) * masks[i]; + alpha_hat_2 += d(i, 0) * masks2[i]; + } + alpha_hat_1 = alpha_1 + y_mn_p1 * alpha_hat_1; + alpha_hat_2 = alpha_2 + y_mn_p1 * alpha_hat_2; + + DBG_VAL_PRINT(alpha_hat_1); + DBG_VAL_PRINT(alpha_hat_2); + + // calculate y^-1, y^-2, ... + const scalar_t y_inverse = y.reciprocal(); + scalar_vec_t y_inverse_powers(c_bpp_mn / 2 + 1); // the greatest power we need is c_bpp_mn/2 (at the first reduction round) + y_inverse_powers[0] = 1; + for (size_t i = 1, size = y_inverse_powers.size(); i < size; ++i) + y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inverse; + + // prepare generator's vector + std::vector g(c_bpp_mn), h(c_bpp_mn); + for (size_t i = 0; i < c_bpp_mn; ++i) + { + g[i] = CT::get_generator(false, i); + h[i] = CT::get_generator(true, i); + } + + // WIP zk-argument called with zk-WIP(g, h, G, H, H2, A_hat, aL_hat, aR_hat, alpha_hat_1, alpha_hat_2) + + scalar_vec_t& a = aLs_hat; + scalar_vec_t& b = aRs_hat; + + sig.L.resize(c_bpp_log2_mn); + sig.R.resize(c_bpp_log2_mn); + + // zk-WIP reduction rounds (s.a. Zarcanum preprint page 24 Fig. D.1) + for (size_t n = c_bpp_mn / 2, ni = 0; n >= 1; n /= 2, ++ni) + { + DBG_PRINT(ENDL << "#" << ni); + + // zk-WIP(g, h, G, H, H2, P, a, b, alpha_1, alpha_2) + + scalar_t dL = scalar_t::random(), dL2 = scalar_t::random(); + DBG_VAL_PRINT(dL); DBG_VAL_PRINT(dL2); + scalar_t dR = scalar_t::random(), dR2 = scalar_t::random(); + DBG_VAL_PRINT(dR); DBG_VAL_PRINT(dR2); + + // a = (a1, a2), b = (b1, b2) -- vectors of scalars + // cL = -- scalar + scalar_t cL = 0; + for (size_t i = 0; i < n; ++i) + cL += a[i] * y_powers[i + 1] * b[n + i]; + + DBG_VAL_PRINT(cL); + + // cR = * y^n -- scalar + scalar_t cR = 0; + for (size_t i = 0; i < n; ++i) + cR += a[n + i] * y_powers[i + 1] * b[i]; + cR *= y_powers[n]; + + DBG_VAL_PRINT(cR); + + // L = y^-n * a1 * g2 + b2 * h1 + cL * G + dL * H + dL2 * H2 -- point + point_t sum = c_point_0; + for (size_t i = 0; i < n; ++i) + sum += a[i] * g[n + i]; + point_t L; + CT::calc_pedersen_commitment_2(cL, dL, dL2, L); + for (size_t i = 0; i < n; ++i) + L += b[n + i] * h[i]; + L += y_inverse_powers[n] * sum; + L *= c_scalar_1div8; + DBG_VAL_PRINT(L); + + // R = y^n * a2 * g1 + b1 * h2 + cR * G + dR * H + dR2 * H2 -- point + sum.zero(); + for (size_t i = 0; i < n; ++i) + sum += a[n + i] * g[i]; + point_t R; + CT::calc_pedersen_commitment_2(cR, dR, dR2, R); + for (size_t i = 0; i < n; ++i) + R += b[i] * h[n + i]; + R += y_powers[n] * sum; + R *= c_scalar_1div8; + DBG_VAL_PRINT(R); + + // put L, R to the sig + L.to_public_key(sig.L[ni]); + R.to_public_key(sig.R[ni]); + + // update the transcript + hsc.add_scalar(e); + hsc.add_pub_key(sig.L[ni]); + hsc.add_pub_key(sig.R[ni]); + e = hsc.calc_hash(); + DBG_VAL_PRINT(e); + + // recalculate arguments for the next round + scalar_t e_squared = e * e; + scalar_t e_inverse = e.reciprocal(); + scalar_t e_inverse_squared = e_inverse * e_inverse; + scalar_t e_y_inv_n = e * y_inverse_powers[n]; + scalar_t e_inv_y_n = e_inverse * y_powers[n]; + + // g_hat = e^-1 * g1 + (e * y^-n) * g2 -- vector of points + for (size_t i = 0; i < n; ++i) + g[i] = e_inverse * g[i] + e_y_inv_n * g[n + i]; + + // h_hat = e * h1 + e^-1 * h2 -- vector of points + for (size_t i = 0; i < n; ++i) + h[i] = e * h[i] + e_inverse * h[n + i]; + + // P_hat = e^2 * L + P + e^-2 * R -- point + + // a_hat = e * a1 + e^-1 * y^n * a2 -- vector of scalars + for (size_t i = 0; i < n; ++i) + a[i] = e * a[i] + e_inv_y_n * a[n + i]; + + // b_hat = e^-1 * b1 + e * b2 -- vector of scalars + for (size_t i = 0; i < n; ++i) + b[i] = e_inverse * b[i] + e * b[n + i]; + + // alpha_hat_1 = e^2 * dL + alpha_1 + e^-2 * dR -- scalar + // alpha_hat_2 = e^2 * dL2 + alpha_2 + e^-2 * dR2 -- scalar + alpha_hat_1 += e_squared * dL + e_inverse_squared * dR; + alpha_hat_2 += e_squared * dL2 + e_inverse_squared * dR2; + + // run next iteraton zk-WIP(g_hat, h_hat, G, H, H2, P_hat, a_hat, b_hat, alpha_hat_1, alpha_hat_2) + } + DBG_PRINT(""); + + // zk-WIP last round + scalar_t r = scalar_t::random(); + scalar_t s = scalar_t::random(); + scalar_t delta_1 = scalar_t::random(), delta_2 = scalar_t::random(); + scalar_t eta_1 = scalar_t::random(), eta_2 = scalar_t::random(); + DBG_VAL_PRINT(r); + DBG_VAL_PRINT(s); + DBG_VAL_PRINT(delta_1); DBG_VAL_PRINT(delta_2); + DBG_VAL_PRINT(eta_1); DBG_VAL_PRINT(eta_2); + + // A = r * g + s * h + (r y b + s y a) * G + delta_1 * H + delta_2 * H2 -- point + point_t A = c_point_0; + CT::calc_pedersen_commitment_2(y * (r * b[0] + s * a[0]), delta_1, delta_2, A); + A += r * g[0] + s * h[0]; + A *= c_scalar_1div8; + A.to_public_key(sig.A); + DBG_VAL_PRINT(A); + + // B = (r * y * s) * G + eta_1 * H + eta_2 * H2 + point_t B = c_point_0; + CT::calc_pedersen_commitment_2(r * y * s, eta_1, eta_2, B); + B *= c_scalar_1div8; + B.to_public_key(sig.B); + DBG_VAL_PRINT(B); + + // update the transcript + hsc.add_scalar(e); + hsc.add_pub_key(sig.A); + hsc.add_pub_key(sig.B); + e = hsc.calc_hash(); + DBG_VAL_PRINT(e); + + // finalize the signature + sig.r = r + e * a[0]; + sig.s = s + e * b[0]; + sig.delta_1 = eta_1 + e * delta_1 + e * e * alpha_hat_1; + sig.delta_2 = eta_2 + e * delta_2 + e * e * alpha_hat_2; + DBG_VAL_PRINT(sig.r); + DBG_VAL_PRINT(sig.s); + DBG_VAL_PRINT(sig.delta_1); + DBG_VAL_PRINT(sig.delta_2); + + return true; +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + } // bppe_gen() + + + struct bppe_sig_commit_ref_t + { + bppe_sig_commit_ref_t(const bppe_signature& sig, const std::vector& commitments) + : sig(sig) + , commitments(commitments) + {} + const bppe_signature& sig; + const std::vector& commitments; + }; + + + template + bool bppe_verify(const std::vector& sigs, uint8_t* p_err = nullptr) + { +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("bppe_verify: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + DBG_PRINT(ENDL << " . . . . bppe_verify() . . . . "); + + const size_t kn = sigs.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(kn > 0, 1); + + struct intermediate_element_t + { + scalar_t y; + scalar_t z; + scalar_t z_sq; + scalar_vec_t e; + scalar_vec_t e_sq; + scalar_t e_final; + scalar_t e_final_sq; + size_t inv_e_offset; // offset in batch_for_inverse + size_t inv_y_offset; // offset in batch_for_inverse + size_t c_bpp_log2_m; + size_t c_bpp_m; + size_t c_bpp_mn; + point_t A; + point_t A0; + point_t B; + std::vector L; + std::vector R; + }; + std::vector interms(kn); + + size_t c_bpp_log2_m_max = 0; + for (size_t k = 0; k < kn; ++k) + { + const bppe_sig_commit_ref_t& bsc = sigs[k]; + const bppe_signature& sig = bsc.sig; + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bsc.commitments.size() > 0, 2); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() > 0 && sig.L.size() == sig.R.size(), 3); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.r.is_reduced() && sig.s.is_reduced() && sig.delta_1.is_reduced() && sig.delta_2.is_reduced(), 4); + + intermediate_element_t& interm = interms[k]; + interm.c_bpp_log2_m = constexpr_ceil_log2(bsc.commitments.size()); + if (c_bpp_log2_m_max < interm.c_bpp_log2_m) + c_bpp_log2_m_max = interm.c_bpp_log2_m; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.L.size() == interm.c_bpp_log2_m + CT::c_bpp_log2_n, 5); + + interm.c_bpp_m = 1ull << interm.c_bpp_log2_m; + interm.c_bpp_mn = interm.c_bpp_m * CT::c_bpp_n; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A0.from_public_key(sig.A0), 6); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.A.from_public_key(sig.A), 7); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.B.from_public_key(sig.B), 8); + interm.L.resize(sig.L.size()); + interm.R.resize(sig.R.size()); + for (size_t i = 0; i < interm.L.size(); ++i) + { + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.L[i].from_public_key(sig.L[i]), 9); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(interm.R[i].from_public_key(sig.R[i]), 10); + } + } + const size_t c_bpp_m_max = 1ull << c_bpp_log2_m_max; + const size_t c_bpp_mn_max = c_bpp_m_max * CT::c_bpp_n; + const size_t c_bpp_LR_size_max = c_bpp_log2_m_max + CT::c_bpp_log2_n; + + + // + // prepare stuff + // + /* + std::vector g(c_bpp_mn_max), h(c_bpp_mn_max); + for (size_t i = 0; i < c_bpp_mn_max; ++i) + { + g[i] = CT::get_generator(false, i); + h[i] = CT::get_generator(true, i); + } + */ + + scalar_vec_t batch_for_inverse; + batch_for_inverse.reserve(kn + kn * c_bpp_LR_size_max); + + + for (size_t k = 0; k < kn; ++k) + { + DBG_PRINT(ENDL << "SIG #" << k); + const bppe_sig_commit_ref_t& bsc = sigs[k]; + const bppe_signature& sig = bsc.sig; + intermediate_element_t& interm = interms[k]; + + // restore y and z + // using e as Fiat-Shamir transcript + scalar_t e = CT::get_initial_transcript(); + DBG_PRINT("initial transcript: " << e); + hash_helper_t::hs_t hsc; + CT::update_transcript(hsc, e, bsc.commitments); + // calculate scalar challenges y and z + hsc.add_scalar(e); + hsc.add_pub_key(sig.A0); + hsc.assign_calc_hash(interm.y); + interm.z = hash_helper_t::hs(interm.y); + interm.z_sq = interm.z * interm.z; + DBG_VAL_PRINT(interm.y); + DBG_VAL_PRINT(interm.z); + e = interm.z; // transcript for further steps + + interm.inv_y_offset = batch_for_inverse.size(); + batch_for_inverse.push_back(interm.y); + interm.inv_e_offset = batch_for_inverse.size(); + + interm.e.resize(sig.L.size()); + interm.e_sq.resize(sig.L.size()); + + for (size_t i = 0; i < sig.L.size(); ++i) + { + hsc.add_scalar(e); + hsc.add_pub_key(sig.L[i]); + hsc.add_pub_key(sig.R[i]); + hsc.assign_calc_hash(e); + interm.e[i] = e; + interm.e_sq[i] = e * e; + DBG_PRINT("e[" << i << "]: " << e); + batch_for_inverse.push_back(e); + } + + hsc.add_scalar(e); + hsc.add_pub_key(sig.A); + hsc.add_pub_key(sig.B); + hsc.assign_calc_hash(interm.e_final); + interm.e_final_sq = interm.e_final * interm.e_final; + DBG_VAL_PRINT(interm.e_final); + } + + batch_for_inverse.invert(); + + // Notation: + // 1_vec ^ n = (1, 1, 1, ..., 1) + // 2_vec ^ n = (2^0, 2^1, 2^2, ..., 2^(n-1)) + // -1_vec ^ n = ((-1)^0, (-1)^1, (-1)^2, ... (-1)^(n-1)) = (1, -1, 1, -1, ...) + // y<^n = (y^n, y^(n-1), ..., y^1) + // y>^n = (y^1, y^2, ..., y^n) + + // from Zarcanum page 24, Fig D.1: + // Verifier outputs Accept IFF the following holds: + // P^e^2 * A^e * B == g ^ (r' e) * h ^ (s' e) * G ^ (r' y s') * H ^ delta'_1 * H2 ^ delta'_2 + // (where g and h are calculated in each round) + // The same equation in additive notation: + // e^2 * P + e * A + B == (r' * e) * g + (s' * e) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 + // <=> + // (r' * e) * g + (s' * e) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 - e^2 * P - e * A - B == 0 (*) + // where A, B, r', s', delta'_1, delta'_2 is taken from the signature + // and P_{k+1} = e^2 * L_k + P_k + e^-2 * R_k for all rounds + // + // from Zarcanum preprint page 33, Fig D.3: + // P and V computes: + // A_hat = A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h + + // + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) + + // + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G + // (calculated once) + // + // As suggested in BPP preprint Section 6.1 "Practical Optimizations": + // 1) g and h exponentianions can be optimized in order not to be calculated at each round + // as the following (page 20, with delta'_2 and H2 added): + // + // (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 - + // - e^2 * A_hat + // - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j) + // - e * A - B = 0 (**) + // + // where: + // g, h - vector of fixed generators + // s_vec_i = y^(1-i) * PROD{j=1..log(n)}(e_j ^ b(i,j)) + // s'_vec_i = PROD{j=1..log(n)}(e_j ^ -b(i,j)) + // b(i, j) = { 2 * ((1<<(j-1)) & (i-1)) - 1) (counting both from 1) (page 20) + // b(i, j) = { 2 * ((1< + + // (r' * e * s_vec) * g + (s' * e * s'_vec) * h + (r' y s') * G + delta'_1 * H + delta'_2 * H2 - + // - e^2 * (A0 + (- 1^(mn) * z) * g + (d o y<^(mn) + 1^(mn) * z) * h + + // + y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j) + + // + (z*SUM(y^>mn) - z*y^(mn+1)*SUM(d) - z^2 * SUM(y^>mn)) * G + // ) + // - SUM{j=1..log(n)}(e_final^2 * e_j^2 * L_j + e_final^2 * e_j^-2 * R_j) + // - e * A - B = 0 + + // => + + // (for single signature) + // + // (r' * e * s_vec - e^2 * (- 1_vec^(mn) * z)) * g | these are + // + (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) * h | fixed generators + // + (r' y s' - e^2 * ((z - z^2)*SUM(y^>mn) - z*y^(mn+1)*SUM(d)) * G | across all + // + delta'_1 * H | the signatures + // + delta'_2 * H2 | the signatures + // + // - e^2 * A0 + // - e^2 * y^(mn+1) * (SUM{j=1..m} z^(2j) * V_j)) + // - e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j) + // - e * A - B = 0 (***) + // + // All (***) will be muptiplied by random weightning factor and then summed up. + + // Calculate cummulative sclalar multiplicand for fixed generators across all the sigs. + scalar_vec_t g_scalars; + g_scalars.resize(c_bpp_mn_max, 0); + scalar_vec_t h_scalars; + h_scalars.resize(c_bpp_mn_max, 0); + scalar_t G_scalar = 0; + scalar_t H_scalar = 0; + scalar_t H2_scalar = 0; + point_t summand = c_point_0; + + for (size_t k = 0; k < kn; ++k) + { + DBG_PRINT(ENDL << "SIG #" << k); + const bppe_sig_commit_ref_t& bsc = sigs[k]; + const bppe_signature& sig = bsc.sig; + intermediate_element_t& interm = interms[k]; + + // random weightning factor for speed-optimized batch verification (preprint page 20) + const scalar_t rwf = scalar_t::random(); + DBG_PRINT("rwf: " << rwf); + + // prepare d vector (see also d structure description in proof function) + scalar_mat_t d(interm.c_bpp_mn); + d(0, 0) = interm.z_sq; + // first row + for (size_t i = 1; i < interm.c_bpp_m; ++i) + d(i, 0) = d(i - 1, 0) * interm.z_sq; + // all rows + for (size_t j = 1; j < CT::c_bpp_n; ++j) + for (size_t i = 0; i < interm.c_bpp_m; ++i) + d(i, j) = d(i, j - 1) + d(i, j - 1); + // sum(d) (see also note in proof function for this) + // TODO: check for not 2^64 version + static const scalar_t c_scalar_2_power_n_minus_1 = { 0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000 }; + const scalar_t sum_d = c_scalar_2_power_n_minus_1 * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); + + DBG_PRINT("Hs(d): " << d.calc_hs()); + DBG_PRINT("sum(d): " << sum_d); + + const scalar_t& y_inv = batch_for_inverse[interm.inv_y_offset]; + auto get_e_inv = [&](size_t i) { return batch_for_inverse[interm.inv_e_offset + i]; }; // i belongs to [0; L.size()-1] + + // prepare s_vec (unlike the paper here we moved y-component out of s_vec for convenience, so s_vec'[x] = s_vec[~x & (MN-1)]) + // complexity (sc_mul's): MN+2*log2(MN)-2 + // the idea is the following: + // s_vec[00000b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^-1 * (e_1)^-1 * (e_0)^-1 + // s_vec[00101b] = ... * (e_4)^-1 * (e_3)^-1 * (e_2)^+1 * (e_1)^-1 * (e_0)^+1 + const size_t log2_mn = sig.L.size(); // at the beginning we made sure that sig.L.size() == c_bpp_log2_m + c_bpp_log2_n + scalar_vec_t s_vec(interm.c_bpp_mn); + s_vec[0] = get_e_inv(0); + for (size_t i = 1; i < log2_mn; ++i) + s_vec[0] *= get_e_inv(i); // s_vec[0] = (e_0)^-1 * (e_1)^-1 * .. (e_{log2_mn-1})^-1 + DBG_PRINT("[0] " << s_vec[0]); + for (size_t i = 1; i < interm.c_bpp_mn; ++i) + { + size_t base_el_index = i & (i - 1); // base element index: 0, 0, 2, 0, 4, 4, 6, 0, 8, 8, 10... base element differs in one bit (0) from the current one (1) + size_t bit_index = log2_mn - calc_lsb_32((uint32_t)i) - 1; // the bit index where current element has the difference with the base + s_vec[i] = s_vec[base_el_index] * interm.e_sq[bit_index]; // (e_j)^-1 * (e_j)^2 = (e_j)^+1 + DBG_PRINT("[" << i << "] " << " " << base_el_index << ", " << bit_index << " : " << s_vec[i]); + } + + // prepare y_inv vector + scalar_vec_t y_inverse_powers(interm.c_bpp_mn); + y_inverse_powers[0] = 1; + for (size_t i = 1; i < interm.c_bpp_mn; ++i) + y_inverse_powers[i] = y_inverse_powers[i - 1] * y_inv; + + // y^(mn+1) + scalar_t y_power_mnp1 = interm.y; + for (size_t i = 0; i < log2_mn; ++i) + y_power_mnp1 *= y_power_mnp1; + y_power_mnp1 *= interm.y; + DBG_VAL_PRINT(y_power_mnp1); + + // now calculate all multiplicands for common generators + + // g vector multiplicands: + // rwf * (r' * e * (1, y^-1, y^-2, ...) o s_vec + e^2 * z) = + // rwf * r' * e * ((1, y^-1, ...) o s_vec) + rwf * e^2 * z * (1, 1, ...) + scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z; + scalar_t rwf_r_e = rwf * interm.e_final * sig.r; + for (size_t i = 0; i < interm.c_bpp_mn; ++i) + g_scalars[i] += rwf_r_e * y_inverse_powers[i] * s_vec[i] + rwf_e_sq_z; + + DBG_PRINT("Hs(g_scalars): " << g_scalars.calc_hs()); + + // h vector multiplicands: + // rwf * (s' * e * s'_vec - e^2 * (d o y<^(mn) + 1_vec^(mn) * z)) + // rwf * s' * e * s'_vec - rwf * e^2 * z * (1, 1...) - rwf * e^2 * (d o y<^(mn)) + //scalar_t rwf_e_sq_z = rwf * interm.e_final_sq * interm.z; + scalar_t rwf_s_e = rwf * sig.s * interm.e_final; + scalar_t rwf_e_sq_y = rwf * interm.e_final_sq * interm.y; + for (size_t i = interm.c_bpp_mn - 1; i != SIZE_MAX; --i) + { + h_scalars[i] += rwf_s_e * s_vec[interm.c_bpp_mn - 1 - i] - rwf_e_sq_z - rwf_e_sq_y * d[i]; + rwf_e_sq_y *= interm.y; + } + + DBG_PRINT("Hs(h_scalars): " << h_scalars.calc_hs()); + + // G point multiplicands: + // rwf * (r' y s' - e ^ 2 * ((z - z ^ 2)*SUM(y^>mn) - z * y^(mn+1) * SUM(d)) = + // = rwf * r' y s' - rwf * e^2 * (z - z ^ 2)*SUM(y^>mn) + rwf * e^2 * z * y^(mn+1) * SUM(d) + G_scalar += rwf * sig.r * interm.y * sig.s + rwf_e_sq_y * sum_d * interm.z; + G_scalar -= rwf * interm.e_final_sq * (interm.z - interm.z_sq) * sum_of_powers(interm.y, log2_mn); + DBG_PRINT("sum_y: " << sum_of_powers(interm.y, log2_mn)); + DBG_PRINT("G_scalar: " << G_scalar); + + // H point multiplicands: + // rwf * delta_1 + H_scalar += rwf * sig.delta_1; + DBG_PRINT("H_scalar: " << H_scalar); + + // H2 point multiplicands: + // rwf * delta_2 + H2_scalar += rwf * sig.delta_2; + DBG_PRINT("H2_scalar: " << H2_scalar); + + // uncommon generators' multiplicands + point_t summand_8 = c_point_0; // this summand to be multiplied by 8 before adding to the main summand + // - rwf * e^2 * A0 + summand_8 -= rwf * interm.e_final_sq * interm.A0; + DBG_PRINT("A0_scalar: " << c_scalar_Lm1 * interm.e_final_sq * rwf); + + // - rwf * e^2 * y^(mn+1) * (SUM{j=1..m} (z^2)^j * V_j)) + scalar_t e_sq_y_mn1_z_sq_power = rwf * interm.e_final_sq * y_power_mnp1; + for (size_t j = 0; j < bsc.commitments.size(); ++j) + { + e_sq_y_mn1_z_sq_power *= interm.z_sq; + summand_8 -= e_sq_y_mn1_z_sq_power * bsc.commitments[j]; + DBG_PRINT("V_scalar[" << j << "]: " << c_scalar_Lm1 * e_sq_y_mn1_z_sq_power); + } + + // - rwf * e^2 * SUM{j=1..log(n)}(e_j^2 * L_j + e_j^-2 * R_j) + scalar_t rwf_e_sq = rwf * interm.e_final_sq; + for (size_t j = 0; j < log2_mn; ++j) + { + summand_8 -= rwf_e_sq * (interm.e_sq[j] * interm.L[j] + get_e_inv(j) * get_e_inv(j) * interm.R[j]); + DBG_PRINT("L_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * interm.e_sq[j]); + DBG_PRINT("R_scalar[" << j << "]: " << c_scalar_Lm1 * rwf_e_sq * get_e_inv(j) * get_e_inv(j)); + } + + // - rwf * e * A - rwf * B = 0 + summand_8 -= rwf * interm.e_final * interm.A + rwf * interm.B; + DBG_PRINT("A_scalar: " << c_scalar_Lm1 * rwf * interm.e_final); + DBG_PRINT("B_scalar: " << c_scalar_Lm1 * rwf); + + summand_8.modify_mul8(); + summand += summand_8; + } + + point_t GH_exponents = c_point_0; + CT::calc_pedersen_commitment_2(G_scalar, H_scalar, H2_scalar, GH_exponents); + bool result = multiexp_and_check_being_zero(g_scalars, h_scalars, summand + GH_exponents); + if (result) + DBG_PRINT(ENDL << " . . . . bppe_verify() -- SUCCEEDED!!!" << ENDL); + return result; +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + } + +} // namespace crypto diff --git a/src/crypto/range_proofs.h b/src/crypto/range_proofs.h index 623c5af0..2f7fcf00 100644 --- a/src/crypto/range_proofs.h +++ b/src/crypto/range_proofs.h @@ -148,3 +148,4 @@ namespace crypto } // namespace crypto #include "range_proof_bpp.h" +#include "range_proof_bppe.h" diff --git a/tests/functional_tests/crypto_tests_range_proofs.h b/tests/functional_tests/crypto_tests_range_proofs.h index 94f4db0b..3ccdc7f4 100644 --- a/tests/functional_tests/crypto_tests_range_proofs.h +++ b/tests/functional_tests/crypto_tests_range_proofs.h @@ -180,3 +180,82 @@ TEST(bpp, power_256) return true; } + + +// +// tests for Bulletproofs+ Extended (with double-blinded commitments) +// + + +TEST(bppe, basics) +{ + /* + srand(0); + for (size_t i = 0; i < 10; ++i) + std::cout << scalar_t::random().to_string_as_secret_key() << ENDL; + */ + + scalar_vec_t values = { 5 }; + scalar_vec_t masks = { 0 }; + scalar_vec_t masks_2 = { 0 }; + + bppe_signature bppe_sig; + std::vector commitments; + uint8_t err = 0; + + bool r = bppe_gen>(values, masks, masks_2, bppe_sig, commitments, &err); + + ASSERT_TRUE(r); + + return true; +} + +TEST(bppe, two) +{ + std::vector signatures_vector; + signatures_vector.reserve(10); + std::vector> commitments_vector; + commitments_vector.reserve(10); + + std::vector sigs; + uint8_t err = 0; + bool r = false; + + { + signatures_vector.resize(signatures_vector.size() + 1); + bppe_signature &bppe_sig = signatures_vector.back(); + commitments_vector.resize(commitments_vector.size() + 1); + std::vector& commitments = commitments_vector.back(); + + scalar_vec_t values = { 5 }; + scalar_vec_t masks = { scalar_t(77 + 256 * 77) }; + scalar_vec_t masks2 = { scalar_t(88 + 256 * 88) }; + + r = bppe_gen>(values, masks, masks2, bppe_sig, commitments, &err); + ASSERT_TRUE(r); + + sigs.emplace_back(bppe_sig, commitments); + } + + { + signatures_vector.resize(signatures_vector.size() + 1); + bppe_signature &bppe_sig = signatures_vector.back(); + commitments_vector.resize(commitments_vector.size() + 1); + std::vector& commitments = commitments_vector.back(); + + scalar_vec_t values = { 5, 700, 8 }; + scalar_vec_t masks = { scalar_t(77 + 256 * 77), scalar_t(255), scalar_t(17) }; + scalar_vec_t masks2 = { scalar_t(88 + 256 * 88), scalar_t(1), scalar_t(19) }; + + r = bppe_gen>(values, masks, masks2, bppe_sig, commitments, &err); + ASSERT_TRUE(r); + + sigs.emplace_back(bppe_sig, commitments); + } + + r = bppe_verify>(sigs, &err); + ASSERT_TRUE(r); + + + return true; +} From 0c53f58d5f082dffc35c6dbf4a22b71cfa46694b Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 11 Apr 2022 21:12:37 +0200 Subject: [PATCH 0138/1271] updated tor --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 2029c284..148a4731 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 2029c2842817f964947b624d3fa9cc0c5324f9e0 +Subproject commit 148a4731f7f8bb7639b441a94c6a0059c4b9eeab From 5f72b1c5eabbdcae2db399b3cf96b4f70a63665c Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 12 Apr 2022 15:49:56 +0200 Subject: [PATCH 0139/1271] added tor option API for UI --- src/gui/qt-daemon/application/mainwindow.cpp | 14 ++++++++++++++ src/gui/qt-daemon/application/mainwindow.h | 1 + src/wallet/wallets_manager.cpp | 16 +++++++++++++++- src/wallet/wallets_manager.h | 3 ++- 4 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 370d06c4..88cb6cdd 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -1665,6 +1665,20 @@ QString MainWindow::get_log_level(const QString& param) CATCH_ENTRY_FAIL_API_RESPONCE(); } +QString set_enable_tor(const QString& param) +{ + TRY_ENTRY(); + LOG_API_TIMING(); + PREPARE_ARG_FROM_JSON(currency::struct_with_one_t_type, enabl_tor); + m_backend. + //epee::log_space::get_set_log_detalisation_level(true, enabl_tor.v); + default_ar.error_code = API_RETURN_CODE_OK; + LOG_PRINT("[LOG LEVEL]: set to " << lvl.v, LOG_LEVEL_MIN); + + return MAKE_RESPONSE(default_ar); + CATCH_ENTRY_FAIL_API_RESPONCE(); +} + // QString MainWindow::dump_all_offers() // { // LOG_API_TIMING(); diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index dfe9ae4b..79251e2a 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -137,6 +137,7 @@ public: QString stop_pos_mining(const QString& param); QString set_log_level(const QString& param); QString get_log_level(const QString& param); + QString set_enable_tor(const QString& param); // QString dump_all_offers(); QString webkit_launched_script(); QString get_smart_wallet_info(const QString& param); diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 075b1bd5..015f5e7a 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -81,7 +81,8 @@ wallets_manager::wallets_manager():m_pview(&m_view_stub), m_is_pos_allowed(false), m_qt_logs_enbaled(false), m_dont_save_wallet_at_stop(false), - m_use_deffered_global_outputs(false) + m_use_deffered_global_outputs(false), + m_use_tor(true) { #ifndef MOBILE_WALLET_BUILD m_offers_service.set_disabled(true); @@ -791,6 +792,8 @@ void wallets_manager::init_wallet_entry(wallet_vs_options& wo, uint64_t id) m_wallet_log_prefixes.resize(id + 1); m_wallet_log_prefixes[id] = std::string("[") + epee::string_tools::num_to_string_fast(id) + ":" + wo.w->get()->get_account().get_public_address_str().substr(0, 6) + "] "; } + + wo.w->get()->set_disable_tor_relay(!m_use_tor); } @@ -1842,6 +1845,17 @@ void wallets_manager::on_pos_block_found(size_t wallet_id, const currency::block { m_pview->pos_block_found(b); } +bool wallets_manager::set_use_tor(bool use_tor) +{ + m_use_tor = use_tor; + SHARED_CRITICAL_REGION_LOCAL(m_wallets_lock); + for (auto& w : m_wallets) + { + w.second.w->get()->set_disable_tor_relay(!m_use_tor); + } + return true; +} + void wallets_manager::on_sync_progress(size_t wallet_id, const uint64_t& percents) { // do not lock m_wallets_lock down the callstack! It will lead to a deadlock, because wallet locked_object is aready locked diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 5b919a0b..77656785 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -163,7 +163,7 @@ public: bool is_qt_logs_enabled() const { return m_qt_logs_enbaled; } std::string get_qt_dev_tools_option() const { return m_qt_dev_tools; } void set_use_deffered_global_outputs(bool use) { m_use_deffered_global_outputs = use; } - + bool set_use_tor(bool use_tor); private: void main_worker(const po::variables_map& vm); bool init_local_daemon(); @@ -226,6 +226,7 @@ private: bool m_qt_logs_enbaled; std::string m_qt_dev_tools; std::atomic m_is_pos_allowed; + std::atomic m_use_tor; std::map m_wallets; From 2e2a4861a722cbed51d92efd0e4e76ac1b51108e Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 12 Apr 2022 19:12:28 +0200 Subject: [PATCH 0140/1271] fixed bug with compilation --- src/gui/qt-daemon/application/mainwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 88cb6cdd..030a8d20 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -1665,15 +1665,15 @@ QString MainWindow::get_log_level(const QString& param) CATCH_ENTRY_FAIL_API_RESPONCE(); } -QString set_enable_tor(const QString& param) +QString MainWindow::set_enable_tor(const QString& param) { TRY_ENTRY(); LOG_API_TIMING(); PREPARE_ARG_FROM_JSON(currency::struct_with_one_t_type, enabl_tor); - m_backend. + m_backend.set_use_tor(enabl_tor.v); //epee::log_space::get_set_log_detalisation_level(true, enabl_tor.v); default_ar.error_code = API_RETURN_CODE_OK; - LOG_PRINT("[LOG LEVEL]: set to " << lvl.v, LOG_LEVEL_MIN); + LOG_PRINT("[TOR]: Enable TOR set to " << enabl_tor.v, LOG_LEVEL_MIN); return MAKE_RESPONSE(default_ar); CATCH_ENTRY_FAIL_API_RESPONCE(); From 80d0023e3edc93b9beaea1df0db6452824d9de19 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 12 Apr 2022 19:46:19 +0200 Subject: [PATCH 0141/1271] build system: added support for OpenSSL --- utils/build_script_windows.bat | 3 ++- utils/configure_local_paths.cmd.example | 1 + utils/configure_local_paths_msvs2019.cmd | 1 + utils/configure_win32_msvs2015_gui.cmd | 2 +- utils/configure_win64_msvs2013_gui.cmd | 2 +- utils/configure_win64_msvs2015_gui.cmd | 2 +- utils/configure_win64_msvs2017_gui.cmd | 2 +- utils/configure_win64_msvs2017_gui_testnet.cmd | 2 +- utils/configure_win64_msvs2019_gui_testnet.cmd | 2 +- 9 files changed, 10 insertions(+), 7 deletions(-) diff --git a/utils/build_script_windows.bat b/utils/build_script_windows.bat index a224b843..1863cc31 100644 --- a/utils/build_script_windows.bat +++ b/utils/build_script_windows.bat @@ -7,6 +7,7 @@ SET LOCAL_BOOST_PATH=C:\dev\_sdk\boost_1_68_0 SET LOCAL_BOOST_LIB_PATH=C:\dev\_sdk\boost_1_68_0\lib64-msvc-14.1 SET MY_PATH=%~dp0 SET SOURCES_PATH=%MY_PATH:~0,-7% +SET OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 IF NOT [%build_prefix%] == [] ( SET ACHIVE_NAME_PREFIX=%ACHIVE_NAME_PREFIX%%build_prefix%- @@ -44,7 +45,7 @@ cd %SOURCES_PATH% rmdir build /s /q mkdir build cd build -cmake %TESTNET_DEF% -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%" -D BUILD_GUI=TRUE -D STATIC=FALSE -G "Visual Studio 15 2017 Win64" -T host=x64 .. +cmake %TESTNET_DEF% -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%" -D BUILD_GUI=TRUE -D STATIC=FALSE -G "Visual Studio 15 2017 Win64" -T host=x64 .. IF %ERRORLEVEL% NEQ 0 ( goto error ) diff --git a/utils/configure_local_paths.cmd.example b/utils/configure_local_paths.cmd.example index 4ed4c3c4..1e138a0e 100644 --- a/utils/configure_local_paths.cmd.example +++ b/utils/configure_local_paths.cmd.example @@ -6,3 +6,4 @@ rem set QT_PREFIX_PATH=C:\dev\_sdk\Qt5.11.2\5.11.2 set BOOST_ROOT=C:\dev\_sdk\boost_1_66_0 +set OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 diff --git a/utils/configure_local_paths_msvs2019.cmd b/utils/configure_local_paths_msvs2019.cmd index 742774bf..a8d442b2 100644 --- a/utils/configure_local_paths_msvs2019.cmd +++ b/utils/configure_local_paths_msvs2019.cmd @@ -6,3 +6,4 @@ rem set QT_PREFIX_PATH=C:\dev\_sdk\Qt5.15.2\5.15.2 set BOOST_ROOT=C:\dev\_sdk\boost_1_76_0 +set OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 diff --git a/utils/configure_win32_msvs2015_gui.cmd b/utils/configure_win32_msvs2015_gui.cmd index 335ec1c6..f0a84e32 100644 --- a/utils/configure_win32_msvs2015_gui.cmd +++ b/utils/configure_win32_msvs2015_gui.cmd @@ -4,4 +4,4 @@ cd .. @mkdir build_msvc2015_32 cd build_msvc2015_32 -cmake -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2015 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib32-msvc-14.0" -G "Visual Studio 14 2015" ".." +cmake -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2015 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib32-msvc-14.0" -G "Visual Studio 14 2015" ".." diff --git a/utils/configure_win64_msvs2013_gui.cmd b/utils/configure_win64_msvs2013_gui.cmd index ddc715d3..3e8a8b82 100644 --- a/utils/configure_win64_msvs2013_gui.cmd +++ b/utils/configure_win64_msvs2013_gui.cmd @@ -4,4 +4,4 @@ cd .. @mkdir build_msvc2013_64 cd build_msvc2013_64 -cmake -D BUILD_TESTS=TRUE -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2013_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-12.0" -G "Visual Studio 12 2013 Win64" ".." \ No newline at end of file +cmake -D BUILD_TESTS=TRUE -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2013_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-12.0" -G "Visual Studio 12 2013 Win64" ".." \ No newline at end of file diff --git a/utils/configure_win64_msvs2015_gui.cmd b/utils/configure_win64_msvs2015_gui.cmd index 73e44d17..d365843e 100644 --- a/utils/configure_win64_msvs2015_gui.cmd +++ b/utils/configure_win64_msvs2015_gui.cmd @@ -4,4 +4,4 @@ cd .. @mkdir build_msvc2015_64 cd build_msvc2015_64 -cmake -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2015_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.0" -G "Visual Studio 14 2015 Win64" -T host=x64 ".." \ No newline at end of file +cmake -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2015_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.0" -G "Visual Studio 14 2015 Win64" -T host=x64 ".." \ No newline at end of file diff --git a/utils/configure_win64_msvs2017_gui.cmd b/utils/configure_win64_msvs2017_gui.cmd index fe6e3eea..7b887d75 100644 --- a/utils/configure_win64_msvs2017_gui.cmd +++ b/utils/configure_win64_msvs2017_gui.cmd @@ -4,4 +4,4 @@ cd .. @mkdir build_msvc2017_64 cd build_msvc2017_64 -cmake -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2017_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.1" -G "Visual Studio 15 2017 Win64" -T host=x64 ".." \ No newline at end of file +cmake -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2017_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.1" -G "Visual Studio 15 2017 Win64" -T host=x64 ".." \ No newline at end of file diff --git a/utils/configure_win64_msvs2017_gui_testnet.cmd b/utils/configure_win64_msvs2017_gui_testnet.cmd index 892a199a..61d34790 100644 --- a/utils/configure_win64_msvs2017_gui_testnet.cmd +++ b/utils/configure_win64_msvs2017_gui_testnet.cmd @@ -4,4 +4,4 @@ cd .. @mkdir build_msvc2017_64_tn cd build_msvc2017_64_tn -cmake -D TESTNET=TRUE -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2017_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.1" -G "Visual Studio 15 2017 Win64" -T host=x64 ".." \ No newline at end of file +cmake -D TESTNET=TRUE -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2017_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.1" -G "Visual Studio 15 2017 Win64" -T host=x64 ".." \ No newline at end of file diff --git a/utils/configure_win64_msvs2019_gui_testnet.cmd b/utils/configure_win64_msvs2019_gui_testnet.cmd index 0fd0d89d..afc14035 100644 --- a/utils/configure_win64_msvs2019_gui_testnet.cmd +++ b/utils/configure_win64_msvs2019_gui_testnet.cmd @@ -4,4 +4,4 @@ cd .. @mkdir build_msvc2019_64_tn cd build_msvc2019_64_tn -cmake -D TESTNET=TRUE -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2019_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.2" -G "Visual Studio 16 2019" -A x64 -T host=x64 ".." +cmake -D TESTNET=TRUE -D USE_PCH=TRUE -D BUILD_TESTS=TRUE -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%"\msvc2019_64 -D BUILD_GUI=TRUE -D STATIC=FALSE -DBOOST_ROOT="%BOOST_ROOT%" -DBOOST_LIBRARYDIR="%BOOST_ROOT%\lib64-msvc-14.2" -G "Visual Studio 16 2019" -A x64 -T host=x64 ".." From 024e07c2d924abb5ef4ada07619a6d8f94416ca6 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 12 Apr 2022 21:04:55 +0200 Subject: [PATCH 0142/1271] [COVERITY] Fixed coveerity issue 391947 Unchecked return value from library --- src/common/ntp.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/common/ntp.cpp b/src/common/ntp.cpp index b7f8a076..db55610a 100644 --- a/src/common/ntp.cpp +++ b/src/common/ntp.cpp @@ -194,7 +194,12 @@ namespace tools ntp_packet packet_sent = AUTO_VAL_INIT(packet_sent); packet_sent.li_vn_mode = 0x1b; auto packet_sent_time = std::chrono::high_resolution_clock::now(); - socket.send_to(boost::asio::buffer(&packet_sent, sizeof packet_sent), receiver_endpoint); + auto send_res = socket.send_to(boost::asio::buffer(&packet_sent, sizeof packet_sent), receiver_endpoint); + if (send_res != sizeof packet_sent) + { + LOG_PRINT_L3("NTP: get_ntp_time(" << host_name << "): wrong send_res: " << send_res << ", expected sizeof packet_sent = " << sizeof packet_sent); + return 0; + } ntp_packet packet_received = AUTO_VAL_INIT(packet_received); boost::asio::ip::udp::endpoint sender_endpoint; From 034e4423f86aa21797423a302ca549b5c18c8418 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 12 Apr 2022 21:15:02 +0200 Subject: [PATCH 0143/1271] setup tor/openssl for static linking --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 148a4731..90db1157 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 148a4731f7f8bb7639b441a94c6a0059c4b9eeab +Subproject commit 90db115701775becb65fb3ffefc8a31a8d9902a4 From 35071cb39533bf6f5c05d61053a7fd8f729e65cf Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 13 Apr 2022 13:47:11 +0200 Subject: [PATCH 0144/1271] build system: added support for OpenSSL (linux) --- utils/build_script_linux.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/build_script_linux.sh b/utils/build_script_linux.sh index 2cd431a2..fe5adbef 100755 --- a/utils/build_script_linux.sh +++ b/utils/build_script_linux.sh @@ -3,16 +3,19 @@ # Environment prerequisites: # 1) QT_PREFIX_PATH should be set to Qt libs folder # 2) BOOST_ROOT should be set to the root of Boost +# 3) OPENSSL_ROOT_DIR should be set to the root of OpenSSL # # for example, place these lines to the end of your ~/.bashrc : # # export BOOST_ROOT=/home/user/boost_1_66_0 # export QT_PREFIX_PATH=/home/user/Qt5.10.1/5.10.1/gcc_64 +# export OPENSSL_ROOT_DIR=/home/user/openssl ARCHIVE_NAME_PREFIX=zano-linux-x64- : "${BOOST_ROOT:?BOOST_ROOT should be set to the root of Boost, ex.: /home/user/boost_1_66_0}" : "${QT_PREFIX_PATH:?QT_PREFIX_PATH should be set to Qt libs folder, ex.: /home/user/Qt5.10.1/5.10.1/gcc_64}" +: "${OPENSSL_ROOT_DIR:?OPENSSL_ROOT_DIR should be set to OpenSSL root folder, ex.: /home/user/openssl}" if [ -n "$build_prefix" ]; then ARCHIVE_NAME_PREFIX=${ARCHIVE_NAME_PREFIX}${build_prefix}- @@ -40,7 +43,7 @@ echo "--------------------------------------------------" echo "Building...." rm -rf build; mkdir -p build/release; cd build/release; -cmake $testnet_def -D STATIC=true -D ARCH=x86-64 -D BUILD_GUI=TRUE -D CMAKE_PREFIX_PATH="$QT_PREFIX_PATH" -D CMAKE_BUILD_TYPE=Release ../.. +cmake $testnet_def -D STATIC=true -D ARCH=x86-64 -D BUILD_GUI=TRUE -D OPENSSL_ROOT_DIR="$OPENSSL_ROOT_DIR" -D CMAKE_PREFIX_PATH="$QT_PREFIX_PATH" -D CMAKE_BUILD_TYPE=Release ../.. if [ $? -ne 0 ]; then echo "Failed to run cmake" exit 1 From 8c8207b71a03cf844b80c71b6c0181473125b10d Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 13 Apr 2022 17:27:59 +0200 Subject: [PATCH 0145/1271] tor: fixed static compilation for windows --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 90db1157..0ee5351b 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 90db115701775becb65fb3ffefc8a31a8d9902a4 +Subproject commit 0ee5351bcb8186276574d37468d53c9b00cbd008 From edb6dadaf73888143ce546525494bfc760b8cf97 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 13 Apr 2022 18:08:53 +0200 Subject: [PATCH 0146/1271] tor: moved to latest commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 0ee5351b..689d0033 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 0ee5351bcb8186276574d37468d53c9b00cbd008 +Subproject commit 689d0033ad9c520271974c0573327fda1dcc5fd1 From 36e79a3568b6549e590240475b8519eb85224595 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 13 Apr 2022 18:53:48 +0200 Subject: [PATCH 0147/1271] tor: moved to latest commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 689d0033..f5d03769 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 689d0033ad9c520271974c0573327fda1dcc5fd1 +Subproject commit f5d037695de29c81400469b5b1b18307b67b4dab From 427350b2d7a9fc7284384e40e1d43c2ef4eafb05 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 13 Apr 2022 19:37:48 +0200 Subject: [PATCH 0148/1271] tor: moved to latest commit --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index f5d03769..58540ee7 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit f5d037695de29c81400469b5b1b18307b67b4dab +Subproject commit 58540ee7490f97a61139386f73f73331105df63d From b64a08880de4e7399f5e9c0e0a895fdccf58dc42 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 13 Apr 2022 19:43:20 +0200 Subject: [PATCH 0149/1271] fixed connectivity tool build issue(old levin_xxxx naming) --- src/connectivity_tool/conn_tool.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index d1f2115b..33ea6bd9 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -640,7 +640,7 @@ bool handle_request_stat(po::variables_map& vm, peerid_type peer_id) response_schema rs = AUTO_VAL_INIT(rs); - levin::levin_client_impl2 transport; + net_utils::levin_client2 transport; if(!transport.connect(command_line::get_arg(vm, arg_ip), static_cast(command_line::get_arg(vm, arg_port)), static_cast(command_line::get_arg(vm, arg_timeout)))) { std::cout << "{" << ENDL << " \"status\": \"ERROR: " << "Failed to connect to " << command_line::get_arg(vm, arg_ip) << ":" << command_line::get_arg(vm, arg_port) << "\"" << ENDL << "}" << ENDL; @@ -878,7 +878,7 @@ bool generate_and_print_keys() } //--------------------------------------------------------------------------------------------------------------- template -bool invoke_debug_command(po::variables_map& vm, const crypto::secret_key& sk, levin::levin_client_impl2& transport, peerid_type& peer_id, typename command_t::request& req, typename command_t::response& rsp) +bool invoke_debug_command(po::variables_map& vm, const crypto::secret_key& sk, net_utils::levin_client2& transport, peerid_type& peer_id, typename command_t::request& req, typename command_t::response& rsp) { if (!transport.is_connected()) { @@ -933,7 +933,7 @@ bool handle_set_peer_log_level(po::variables_map& vm) return false; } - levin::levin_client_impl2 transport; + net_utils::levin_client2 transport; peerid_type peer_id = 0; COMMAND_SET_LOG_LEVEL::request req = AUTO_VAL_INIT(req); @@ -982,7 +982,7 @@ bool handle_download_peer_log(po::variables_map& vm) } uint64_t start_offset = static_cast(start_offset_signed); - levin::levin_client_impl2 transport; + net_utils::levin_client2 transport; peerid_type peer_id = 0; COMMAND_REQUEST_LOG::request req = AUTO_VAL_INIT(req); From 51e95010d8e9ec6477db6b1651af098c789febc8 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 13 Apr 2022 20:33:01 +0200 Subject: [PATCH 0150/1271] build system: added support for OpenSSL (macOS) --- utils/build_script_mac_osx.sh | 3 ++- utils/macosx_build_config.command | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/utils/build_script_mac_osx.sh b/utils/build_script_mac_osx.sh index bb70d5fc..15acd9e3 100755 --- a/utils/build_script_mac_osx.sh +++ b/utils/build_script_mac_osx.sh @@ -8,6 +8,7 @@ curr_path=${BASH_SOURCE%/*} : "${ZANO_BOOST_LIBS_PATH:?variable not set, see also macosx_build_config.command}" : "${ZANO_BUILD_DIR:?variable not set, see also macosx_build_config.command}" : "${CMAKE_OSX_SYSROOT:?CMAKE_OSX_SYSROOT should be set to macOS SDK path, e.g.: /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.13.sdk}" +: "${OPENSSL_ROOT_DIR:?variable not set, see also macosx_build_config.command}" ARCHIVE_NAME_PREFIX=zano-macos-x64- @@ -30,7 +31,7 @@ fi rm -rf $ZANO_BUILD_DIR; mkdir -p "$ZANO_BUILD_DIR/release"; cd "$ZANO_BUILD_DIR/release" -cmake $testnet_def -D CMAKE_OSX_SYSROOT=$CMAKE_OSX_SYSROOT -D BUILD_GUI=TRUE -D CMAKE_PREFIX_PATH="$ZANO_QT_PATH/clang_64" -D CMAKE_BUILD_TYPE=Release -D BOOST_ROOT="$ZANO_BOOST_ROOT" -D BOOST_LIBRARYDIR="$ZANO_BOOST_LIBS_PATH" ../.. +cmake $testnet_def -D OPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR -D CMAKE_OSX_SYSROOT=$CMAKE_OSX_SYSROOT -D BUILD_GUI=TRUE -D CMAKE_PREFIX_PATH="$ZANO_QT_PATH/clang_64" -D CMAKE_BUILD_TYPE=Release -D BOOST_ROOT="$ZANO_BOOST_ROOT" -D BOOST_LIBRARYDIR="$ZANO_BOOST_LIBS_PATH" ../.. if [ $? -ne 0 ]; then echo "Failed to cmake" exit 1 diff --git a/utils/macosx_build_config.command b/utils/macosx_build_config.command index 786e53a0..89cfe02b 100644 --- a/utils/macosx_build_config.command +++ b/utils/macosx_build_config.command @@ -9,3 +9,4 @@ export ZANO_QT_PATH="/Users/roky/Qt5.6.0/5.6" export ZANO_BOOST_ROOT="/Users/roky/boost_1_60_0" export ZANO_BOOST_LIBS_PATH="$ZANO_BOOST_ROOT/stage/lib" export ZANO_BUILD_DIR="build_mac_osx_64" +export OPENSSL_ROOT_DIR="/usr/local/opt/openssl" From 03ff180b83cba1ccfce828e2ce008ae504382f4e Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 14 Apr 2022 20:19:58 +0200 Subject: [PATCH 0151/1271] readme updated: Boost minimum version pumped up to 1.70; OpenSSL dependency added (both for the tor lib) --- README.md | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 189c88cf..dcf2926f 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,8 @@ Be sure to clone the repository properly:\ | [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2015 (14.0 update 1) | 2017 (15.9.0) | 2019 | | [XCode](https://developer.apple.com/downloads/) (macOS) | 9.2 | 12.3 | 12.3 | | [CMake](https://cmake.org/download/) | 2.8.6 | 3.15.5 | 3.20 | -| [Boost](https://www.boost.org/users/download/) | 1.56 | 1.68 | 1.76 | +| [Boost](https://www.boost.org/users/download/) | 1.70 | 1.70 | 1.76 | +| [OpenSSL](https://www.openssl.org/source/) | - | 1.1.1n | 1.1.1n | | [Qt](https://download.qt.io/archive/qt/) (*only for GUI*) | 5.8.0 | 5.11.2 | 5.15.2 | Note:\ @@ -43,10 +44,10 @@ Recommended OS version: Ubuntu 18.04 LTS. 2. Download and build Boost - wget https://boostorg.jfrog.io/artifactory/main/release/1.68.0/source/boost_1_68_0.tar.bz2 - tar -xjf boost_1_68_0.tar.bz2 - cd boost_1_68_0 - ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer + curl -OL https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.bz2 + tar -xjf boost_1_70_0.tar.bz2 + cd boost_1_70_0 + ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer,log ./b2 3. Install Qt\ @@ -54,27 +55,44 @@ Recommended OS version: Ubuntu 18.04 LTS. [*GUI version*] - wget https://download.qt.io/new_archive/qt/5.11/5.11.2/qt-opensource-linux-x64-5.11.2.run + curl -OL https://download.qt.io/new_archive/qt/5.11/5.11.2/qt-opensource-linux-x64-5.11.2.run chmod +x qt-opensource-linux-x64-5.11.2.run ./qt-opensource-linux-x64-5.11.2.run Then follow the instructions in Wizard. Don't forget to tick the WebEngine module checkbox! -4. Set environment variables properly\ + +4. Install OpenSSL + + We recommend installing OpenSSL v1.1.1 locally unless you would like to use the same version system-wide. + + sudo apt install build-essential checkinstall zlib1g-dev -y + curl -OL https://www.openssl.org/source/openssl-1.1.1n.tar.gz + tar xaf openssl-1.1.1n.tar.gz + cd openssl-1.1.1n/ + ./config --prefix=/home/user/openssl --openssldir=/home/user/openssl shared zlib + make + make test + make install + + +5. Set environment variables properly\ For instance, by adding the following lines to `~/.bashrc` [*server version*] export BOOST_ROOT=/home/user/boost_1_68_0 + export OPENSSL_ROOT_DIR=/home/user/openssl [*GUI version*] - export BOOST_ROOT=/home/user/boost_1_68_0 + export BOOST_ROOT=/home/user/boost_1_68_0 + export OPENSSL_ROOT_DIR=/home/user/openssl export QT_PREFIX_PATH=/home/user/Qt5.11.2/5.11.2/gcc_64 -5. Building binaries +6. Building binaries 1. Building daemon and simplewallet: cd zano/ && make -j1 @@ -99,9 +117,9 @@ For instance, by adding the following lines to `~/.bashrc` ### Windows Recommended OS version: Windows 7 x64. -1. Install required prerequisites (Boost, Qt, CMake). +1. Install required prerequisites (Boost, Qt, CMake, OpenSSL). 2. Edit paths in `utils/configure_local_paths.cmd`. -3. Run `utils/configure_win64_msvs2015_gui.cmd` or `utils/configure_win64_msvs2017_gui.cmd` according to your MSVC version. +3. Run one of `utils/configure_win64_msvsNNNN_gui.cmd` according to your MSVC version. 4. Go to the build folder and open generated Zano.sln in MSVC. 5. Build. @@ -115,7 +133,7 @@ In order to correctly deploy Qt GUI application, you also need to do the followi
### macOS -Recommended OS version: macOS Sierra 10.15.4 x64. +Recommended OS version: macOS Big Sur 11.4 x64. 1. Install required prerequisites. 2. Set environment variables as stated in `utils/macosx_build_config.command`. 3. `mkdir build`
`cd build`
`cmake ..`
`make` From 3dcde7ed06e283ac65ace6fe18c0aa7fbc2c3a8b Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 14 Apr 2022 20:41:10 +0200 Subject: [PATCH 0152/1271] minor fixes for readme and config file example --- README.md | 17 ++++++++--------- utils/configure_local_paths.cmd.example | 2 +- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index dcf2926f..b9d46345 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ Recommended OS version: Ubuntu 18.04 LTS. [*server version*] - sudo apt-get install -y build-essential g++ python-dev autotools-dev libicu-dev libbz2-dev cmake git screen + sudo apt-get install -y build-essential g++ python-dev autotools-dev libicu-dev libbz2-dev cmake git screen checkinstall zlib1g-dev [*GUI version*] - sudo apt-get install -y build-essential g++ python-dev autotools-dev libicu-dev libbz2-dev cmake git screen mesa-common-dev libglu1-mesa-dev + sudo apt-get install -y build-essential g++ python-dev autotools-dev libicu-dev libbz2-dev cmake git screen checkinstall zlib1g-dev mesa-common-dev libglu1-mesa-dev 2. Download and build Boost @@ -63,9 +63,8 @@ Recommended OS version: Ubuntu 18.04 LTS. 4. Install OpenSSL - We recommend installing OpenSSL v1.1.1 locally unless you would like to use the same version system-wide. + We recommend installing OpenSSL v1.1.1n locally unless you would like to use the same version system-wide. - sudo apt install build-essential checkinstall zlib1g-dev -y curl -OL https://www.openssl.org/source/openssl-1.1.1n.tar.gz tar xaf openssl-1.1.1n.tar.gz cd openssl-1.1.1n/ @@ -80,20 +79,20 @@ For instance, by adding the following lines to `~/.bashrc` [*server version*] - export BOOST_ROOT=/home/user/boost_1_68_0 + export BOOST_ROOT=/home/user/boost_1_70_0 export OPENSSL_ROOT_DIR=/home/user/openssl [*GUI version*] - export BOOST_ROOT=/home/user/boost_1_68_0 + export BOOST_ROOT=/home/user/boost_1_70_0 export OPENSSL_ROOT_DIR=/home/user/openssl export QT_PREFIX_PATH=/home/user/Qt5.11.2/5.11.2/gcc_64 -6. Building binaries - 1. Building daemon and simplewallet: +6. Build the binaries + 1. Build daemon and simplewallet: cd zano/ && make -j1 or @@ -106,7 +105,7 @@ For instance, by adding the following lines to `~/.bashrc` **NOTICE 2**: If you'd like to build binaries for the testnet, use `cmake -D TESTNET=TRUE ..` instead of `cmake ..` . - 1. Building GUI: + 1. Build GUI: cd zano utils/build_sript_linux.sh diff --git a/utils/configure_local_paths.cmd.example b/utils/configure_local_paths.cmd.example index 1e138a0e..5dceba0d 100644 --- a/utils/configure_local_paths.cmd.example +++ b/utils/configure_local_paths.cmd.example @@ -5,5 +5,5 @@ rem Rename to configure_local_paths.cmd and do not commit. rem set QT_PREFIX_PATH=C:\dev\_sdk\Qt5.11.2\5.11.2 -set BOOST_ROOT=C:\dev\_sdk\boost_1_66_0 +set BOOST_ROOT=C:\dev\_sdk\boost_1_70_0 set OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 From 307a2346b60f309b3bb51bbba4111c1d3e54d6ae Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 14 Apr 2022 18:08:33 +0200 Subject: [PATCH 0153/1271] tor: moved to latest --- contrib/tor-connect | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/tor-connect b/contrib/tor-connect index 58540ee7..67addaba 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 58540ee7490f97a61139386f73f73331105df63d +Subproject commit 67addabaf7e5fa7e9443f860dfcf62af8bd9b127 From 0e647875837890515c9901323819445f038a1c6a Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 14 Apr 2022 19:16:25 +0200 Subject: [PATCH 0154/1271] Removed epee from tor lib, moved boost minimal version to 1.70 --- CMakeLists.txt | 5 +---- contrib/tor-connect | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 675c14d6..ff384361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -57,9 +57,6 @@ endif() set(USE_PCH FALSE CACHE BOOL "Use shared precompiled headers") -get_filename_component(EPEE_INCLUDE_PATH_FOR_SUBMODULES contrib/epee/include ABSOLUTE ) -message("Epee absolute path set to: ${EPEE_INCLUDE_PATH_FOR_SUBMODULES}") - include_directories(src contrib/eos_portable_archive contrib contrib/epee/include "${CMAKE_BINARY_DIR}/version" "${CMAKE_BINARY_DIR}/contrib/zlib") add_definitions(-DSTATICLIB) @@ -200,7 +197,7 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Android") set(Boost_LIBRARY_DIRS "${Boost_LIBRARY_DIRS}/${CMAKE_ANDROID_ARCH_ABI}/") set(Boost_LIBRARIES "${Boost_LIBRARY_DIRS}libboost_system.a;${Boost_LIBRARY_DIRS}libboost_filesystem.a;${Boost_LIBRARY_DIRS}libboost_thread.a;${Boost_LIBRARY_DIRS}libboost_timer.a;${Boost_LIBRARY_DIRS}libboost_date_time.a;${Boost_LIBRARY_DIRS}libboost_chrono.a;${Boost_LIBRARY_DIRS}libboost_regex.a;${Boost_LIBRARY_DIRS}libboost_serialization.a;${Boost_LIBRARY_DIRS}libboost_atomic.a;${Boost_LIBRARY_DIRS}libboost_program_options.a") else() - find_package(Boost 1.55 REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale) + find_package(Boost 1.70 REQUIRED COMPONENTS system filesystem thread timer date_time chrono regex serialization atomic program_options locale) endif() diff --git a/contrib/tor-connect b/contrib/tor-connect index 67addaba..aa509880 160000 --- a/contrib/tor-connect +++ b/contrib/tor-connect @@ -1 +1 @@ -Subproject commit 67addabaf7e5fa7e9443f860dfcf62af8bd9b127 +Subproject commit aa509880f06292ac8078046f3d49ff854e400716 From 75b998a6b01b7cb968807cca78644eab31f6dc33 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 14 Apr 2022 20:44:27 +0200 Subject: [PATCH 0155/1271] removed unused signal --- src/gui/qt-daemon/application/mainwindow.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index 79251e2a..c7e4c70b 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -196,7 +196,6 @@ signals: void do_dispatch(const QString status, const QString params); //general function void on_core_event(const QString method_name); //general function void set_options(const QString str); //general function - void get_wallet_name(); void handle_deeplink_click(const QString str); void handle_current_action_state(const QString str); From bd39dce7e69b9b25477d426437872f9d160cfcfb Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 14 Apr 2022 21:01:14 +0200 Subject: [PATCH 0156/1271] Fixed #262(-do-pos-mining needs also rpc-bind-port specified to work) --- src/simplewallet/simplewallet.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 53b458b0..e32d8515 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -2200,6 +2200,13 @@ int main(int argc, char* argv[]) } else // if(command_line::has_arg(vm, tools::wallet_rpc_server::arg_rpc_bind_port)) { + if (command_line::get_arg(vm, arg_do_pos_mining)) + { + // PoS mining can be turned on only in RPC server mode, please provide --rpc-bind-port to make this + fail_msg_writer() << "PoS mining can be turned on only in RPC server mode, please provide --rpc-bind-port=PORT_NO to enable staking in simplewallet"; + return EXIT_FAILURE; + } + //runs wallet with console interface sw->set_offline_mode(offline_mode); r = sw->init(vm); From deb790d434cdbb358a0acfefca84a140e19f8abb Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 14 Apr 2022 21:21:00 +0200 Subject: [PATCH 0157/1271] build system: removed windows 32 support --- utils/build_script_windows32.bat | 184 ---------------------------- utils/build_script_windows_both.bat | 24 ---- 2 files changed, 208 deletions(-) delete mode 100644 utils/build_script_windows32.bat delete mode 100644 utils/build_script_windows_both.bat diff --git a/utils/build_script_windows32.bat b/utils/build_script_windows32.bat deleted file mode 100644 index 9bb05564..00000000 --- a/utils/build_script_windows32.bat +++ /dev/null @@ -1,184 +0,0 @@ -SET QT32_PREFIX_PATH=C:\Qt\Qt5.9.1\5.9.1\msvc2015 -SET INNOSETUP_PATH=C:\Program Files (x86)\Inno Setup 5\ISCC.exe -SET ETC32_BINARIES_PATH=C:\home\deploy\etc-binaries-32 -SET BUILDS_PATH=C:\home\deploy\zano -SET ACHIVE_NAME_PREFIX=zano-win-x32-webengine- -SET SOURCES_PATH=C:\home\deploy\zano\src - -SET LOCAL_BOOST_PATH_32=C:\local\boost_1_62_0 -SET LOCAL_BOOST_LIB_PATH_32=C:\local\boost_1_62_0\lib32-msvc-14.0 - - -@echo on - -set BOOST_ROOT=%LOCAL_BOOST_PATH_32% -set BOOST_LIBRARYDIR=%LOCAL_BOOST_LIB_PATH_32% - - - -@echo "---------------- PREPARING BINARIES ---------------------------" -@echo "---------------------------------------------------------------" - - - -cd %SOURCES_PATH% -git pull -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -@echo "---------------- BUILDING APPLICATIONS ------------------------" -@echo "---------------------------------------------------------------" - - - - -rmdir build /s /q -mkdir build -cd build -cmake -D CMAKE_PREFIX_PATH="%QT32_PREFIX_PATH%" -D BUILD_GUI=TRUE -D STATIC=FALSE -G "Visual Studio 14" .. -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - -setLocal -call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" x86_amd64 - -msbuild version.vcxproj /p:Configuration=Release /t:Build -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - -msbuild src/daemon.vcxproj /p:Configuration=Release /t:Build -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - -msbuild src/simplewallet.vcxproj /p:Configuration=Release /t:Build -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - -msbuild src/zano.vcxproj /p:Configuration=Release /t:Build - -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -endlocal - -@echo on -echo "sources are built" - - - - - -cd %SOURCES_PATH%/build - -set cmd=src\Release\simplewallet.exe --version -FOR /F "tokens=3" %%a IN ('%cmd%') DO set version=%%a -set version=%version:~0,-2% -echo '%version%' - -cd src\release - - - -@echo on - - -mkdir bunch - -copy /Y zano.exe bunch -copy /Y zanod.exe bunch -copy /Y simplewallet.exe bunch -copy /Y zano.exe bunch -%QT32_PREFIX_PATH%\bin\windeployqt.exe bunch\Zano.exe - -cd bunch - -zip -r %BUILDS_PATH%\builds\%ACHIVE_NAME_PREFIX%%version%.zip *.* -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -@echo "Add html" - -cd %SOURCES_PATH%\src\gui\qt-daemon\ -zip -r %BUILDS_PATH%\builds\%ACHIVE_NAME_PREFIX%%version%.zip html -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -@echo "Add runtime stuff" - - -cd %ETC32_BINARIES_PATH% -zip -r %BUILDS_PATH%\builds\%ACHIVE_NAME_PREFIX%%version%.zip *.* -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -cd %SOURCES_PATH%\build -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -@echo "---------------------------------------------------------------" -@echo "-------------------Building installer--------------------------" -@echo "---------------------------------------------------------------" - -mkdir installer_src - - -unzip %BUILDS_PATH%\builds\%ACHIVE_NAME_PREFIX%%version%.zip -d installer_src -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -"%INNOSETUP_PATH%" /dBinariesPath=../build/installer_src /DMyAppVersion=%version% /o%BUILDS_PATH%\builds\ /f%ACHIVE_NAME_PREFIX%%version%-installer ..\utils\setup_32.iss -IF %ERRORLEVEL% NEQ 0 ( - goto error -) - - -@echo "---------------------------------------------------------------" -@echo "---------------------------------------------------------------" - -@echo " UPLOADING TO SERVER ...." - -pscp -i C:\Users\Administrator\.ssh\putty.ppk %BUILDS_PATH%\builds\%ACHIVE_NAME_PREFIX%%version%-installer.exe root@95.216.11.16:/usr/share/nginx/html/%ACHIVE_NAME_PREFIX%%version%-installer.exe -IF %ERRORLEVEL% NEQ 0 ( - @echo "FAILED TO UPLOAD TO SERVER" - goto error -) - -echo "New build available at loaction: http://95.216.11.16/%ACHIVE_NAME_PREFIX%%version%-installer.exe" - - - - -goto success - -:error -echo "BUILD FAILED" -exit /B %ERRORLEVEL% - -:success -echo "BUILD SUCCESS" - -cd .. - - - - - - diff --git a/utils/build_script_windows_both.bat b/utils/build_script_windows_both.bat deleted file mode 100644 index 70ded703..00000000 --- a/utils/build_script_windows_both.bat +++ /dev/null @@ -1,24 +0,0 @@ -call utils/build_script_windows.bat -IF %ERRORLEVEL% NEQ 0 ( - @echo "FAILED TO build_script_windows.bat" - goto error -) - - -call utils/build_script_windows32.bat -IF %ERRORLEVEL% NEQ 0 ( - @echo "FAILED TO build_script_windows32.bat" - goto error -) - - - -goto success - -:error -echo "BUILD FAILED" -exit /B %ERRORLEVEL% - -:success -echo "BUILD SUCCESS" - From 5069422e64673f104a4867cf6a8935796ed3914f Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 14 Apr 2022 21:22:42 +0200 Subject: [PATCH 0158/1271] build system windows: moved to Boost 1.70 + minor refactoring --- utils/build_script_windows.bat | 22 +++++++++++++--------- utils/configure_local_paths.cmd.example | 6 +++++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/utils/build_script_windows.bat b/utils/build_script_windows.bat index 1863cc31..422fdbf0 100644 --- a/utils/build_script_windows.bat +++ b/utils/build_script_windows.bat @@ -1,13 +1,17 @@ -SET QT_PREFIX_PATH=C:\dev\_sdk\Qt5.11.2\5.11.2\msvc2017_64 -SET INNOSETUP_PATH=C:\Program Files (x86)\Inno Setup 5\ISCC.exe -SET ETC_BINARIES_PATH=C:\dev\deploy\etc-binaries -SET BUILDS_PATH=C:\dev\deploy\zano +;;SET QT_PREFIX_PATH=C:\dev\_sdk\Qt5.11.2\5.11.2 +;;SET INNOSETUP_PATH=C:\Program Files (x86)\Inno Setup 5\ISCC.exe +;;SET ETC_BINARIES_PATH=C:\dev\deploy\etc-binaries +;;SET BUILDS_PATH=C:\dev\deploy\zano +;;SET LOCAL_BOOST_PATH=C:\dev\_sdk\boost_1_70_0 +;;SET OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 + +;; MSVC version-specific paths +SET LOCAL_BOOST_LIB_PATH=%LOCAL_BOOST_PATH%\lib64-msvc-14.1 +SET QT_MSVC_PATH=%QT_PREFIX_PATH%\msvc2017_64 + SET ACHIVE_NAME_PREFIX=zano-win-x64- -SET LOCAL_BOOST_PATH=C:\dev\_sdk\boost_1_68_0 -SET LOCAL_BOOST_LIB_PATH=C:\dev\_sdk\boost_1_68_0\lib64-msvc-14.1 SET MY_PATH=%~dp0 SET SOURCES_PATH=%MY_PATH:~0,-7% -SET OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 IF NOT [%build_prefix%] == [] ( SET ACHIVE_NAME_PREFIX=%ACHIVE_NAME_PREFIX%%build_prefix%- @@ -45,7 +49,7 @@ cd %SOURCES_PATH% rmdir build /s /q mkdir build cd build -cmake %TESTNET_DEF% -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_PREFIX_PATH%" -D BUILD_GUI=TRUE -D STATIC=FALSE -G "Visual Studio 15 2017 Win64" -T host=x64 .. +cmake %TESTNET_DEF% -D OPENSSL_ROOT_DIR="%OPENSSL_ROOT_DIR%" -D CMAKE_PREFIX_PATH="%QT_MSVC_PATH%" -D BUILD_GUI=TRUE -D STATIC=FALSE -G "Visual Studio 15 2017 Win64" -T host=x64 .. IF %ERRORLEVEL% NEQ 0 ( goto error ) @@ -109,7 +113,7 @@ copy /Y zanod.exe bunch copy /Y simplewallet.exe bunch copy /Y *.pdb bunch -%QT_PREFIX_PATH%\bin\windeployqt.exe bunch\Zano.exe +%QT_MSVC_PATH%\bin\windeployqt.exe bunch\Zano.exe cd bunch diff --git a/utils/configure_local_paths.cmd.example b/utils/configure_local_paths.cmd.example index 5dceba0d..65c5e891 100644 --- a/utils/configure_local_paths.cmd.example +++ b/utils/configure_local_paths.cmd.example @@ -5,5 +5,9 @@ rem Rename to configure_local_paths.cmd and do not commit. rem set QT_PREFIX_PATH=C:\dev\_sdk\Qt5.11.2\5.11.2 -set BOOST_ROOT=C:\dev\_sdk\boost_1_70_0 +set INNOSETUP_PATH=C:\Program Files (x86)\Inno Setup 5\ISCC.exe +set ETC_BINARIES_PATH=C:\dev\deploy\etc-binaries +set BUILDS_PATH=C:\dev\deploy\zano +set LOCAL_BOOST_PATH=C:\dev\_sdk\boost_1_70_0 set OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 +set BOOST_ROOT=%LOCAL_BOOST_PATH% From 5a458b6dbe28a0be9060d884a0f500ba7eca1ccd Mon Sep 17 00:00:00 2001 From: sowle Date: Thu, 14 Apr 2022 21:52:35 +0200 Subject: [PATCH 0159/1271] build system windows: minor improvement --- utils/build_script_windows.bat | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/utils/build_script_windows.bat b/utils/build_script_windows.bat index 422fdbf0..b5a1451d 100644 --- a/utils/build_script_windows.bat +++ b/utils/build_script_windows.bat @@ -1,9 +1,4 @@ -;;SET QT_PREFIX_PATH=C:\dev\_sdk\Qt5.11.2\5.11.2 -;;SET INNOSETUP_PATH=C:\Program Files (x86)\Inno Setup 5\ISCC.exe -;;SET ETC_BINARIES_PATH=C:\dev\deploy\etc-binaries -;;SET BUILDS_PATH=C:\dev\deploy\zano -;;SET LOCAL_BOOST_PATH=C:\dev\_sdk\boost_1_70_0 -;;SET OPENSSL_ROOT_DIR=C:\dev\_sdk\OpenSSL-Win64 +call configure_local_paths.cmd ;; MSVC version-specific paths SET LOCAL_BOOST_LIB_PATH=%LOCAL_BOOST_PATH%\lib64-msvc-14.1 From 5dbb6a75591abe6fa67b0310c9218b047ed63e76 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Fri, 15 Apr 2022 18:00:57 +0200 Subject: [PATCH 0160/1271] changed url for mdbx(due to github blocking leo-yuriev account) --- .gitmodules | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 3e3d7e2c..e98d7700 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,8 @@ url = https://github.com/miniupnp/miniupnp [submodule "contrib/db/libmdbx"] path = contrib/db/libmdbx - url = https://github.com/leo-yuriev/libmdbx.git + url = https://abf.io/erthink/libmdbx.git [submodule "src/gui/qt-daemon/layout"] path = src/gui/qt-daemon/layout url = https://github.com/hyle-team/zano_ui.git + branch = main From a0211f2b27e1955d8fae63a72ccc79feeef5ffc1 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 15 Apr 2022 21:27:16 +0200 Subject: [PATCH 0161/1271] crypto tests: ASSERT_NEQ --- tests/functional_tests/crypto_tests.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 68350097..c15d3869 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -473,6 +473,7 @@ uint64_t hash_64(const void* data, size_t size) #define ASSERT_TRUE(expr) CHECK_AND_ASSERT_MES(expr, false, "This is not true: " #expr) #define ASSERT_FALSE(expr) CHECK_AND_ASSERT_MES((expr) == false, false, "This is not false: " #expr) #define ASSERT_EQ(a, b) CHECK_AND_ASSERT_MES(a == b, false, #a " != " #b "\n " << a << " != " << b) +#define ASSERT_NEQ(a, b) CHECK_AND_ASSERT_MES(a != b, false, #a " == " #b "\n " << a) typedef bool(*bool_func_ptr_t)(); static std::vector> g_tests; From b0e8e6c2eb2611060add7b3c566c267f25de2286 Mon Sep 17 00:00:00 2001 From: sowle Date: Fri, 15 Apr 2022 21:29:08 +0200 Subject: [PATCH 0162/1271] crypto: scalar_t: get_bit, set_bit, clear_bit, power_of_2 implemented; crypto_sc_set_bit_clear_bit test added --- src/crypto/crypto-sugar.h | 24 ++++++++++++-- tests/functional_tests/crypto_tests.cpp | 43 ++++++++++++++++++------- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index d1928a92..a2681642 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -431,13 +431,31 @@ namespace crypto return result; } - bool get_bit(size_t bit_index) const + // Little-endian assumed; TODO: consider Big-endian support + bool get_bit(uint8_t bit_index) const { - if (bit_index > 255) - return false; // TODO: consider performace implications return (m_u64[bit_index >> 6] & (1ull << (bit_index & 63))) != 0; } + // Little-endian assumed; TODO: consider Big-endian support + void set_bit(size_t bit_index) + { + m_u64[bit_index >> 6] |= (1ull << (bit_index & 63)); + } + + // Little-endian assumed; TODO: consider Big-endian support + void clear_bit(size_t bit_index) + { + m_u64[bit_index >> 6] &= ~(1ull << (bit_index & 63)); + } + + static scalar_t power_of_2(uint8_t exponent) + { + scalar_t result = 0; + result.set_bit(exponent); + return result; + } + }; // struct scalar_t // diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index c15d3869..e4e0c63e 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -1812,40 +1812,61 @@ TEST(crypto, sc_get_bit) scalar_t v = 0; // all bits are 0 for (size_t n = 0; n < 256; ++n) { - ASSERT_EQ(v.get_bit(n), false); + ASSERT_EQ(v.get_bit(static_cast(n)), false); } v = c_scalar_256m1; // all bits are 1 for (size_t n = 0; n < 256; ++n) { - ASSERT_EQ(v.get_bit(n), true); + ASSERT_EQ(v.get_bit(static_cast(n)), true); } // bits out of the [0; 255] range supposed to be always 0 for (size_t n = 256; n < 2048; ++n) { - ASSERT_EQ(v.get_bit(n), false); + ASSERT_EQ(v.get_bit(static_cast(n)), false); } // check random value const scalar_t x = scalar_t::random(); for (size_t n = 0; n < 64; ++n) - ASSERT_EQ(x.get_bit(n), ((x.m_u64[0] & (1ull << (n - 0))) != 0)); + ASSERT_EQ(x.get_bit(static_cast(n)), ((x.m_u64[0] & (1ull << (n - 0))) != 0)); for (size_t n = 64; n < 128; ++n) - ASSERT_EQ(x.get_bit(n), ((x.m_u64[1] & (1ull << (n - 64))) != 0)); + ASSERT_EQ(x.get_bit(static_cast(n)), ((x.m_u64[1] & (1ull << (n - 64))) != 0)); for (size_t n = 128; n < 192; ++n) - ASSERT_EQ(x.get_bit(n), ((x.m_u64[2] & (1ull << (n - 128))) != 0)); + ASSERT_EQ(x.get_bit(static_cast(n)), ((x.m_u64[2] & (1ull << (n - 128))) != 0)); for (size_t n = 192; n < 256; ++n) - ASSERT_EQ(x.get_bit(n), ((x.m_u64[3] & (1ull << (n - 192))) != 0)); - - // bits out of the [0; 255] range supposed to be always 0 - for (size_t n = 256; n < 2048; ++n) - ASSERT_EQ(x.get_bit(n), false); + ASSERT_EQ(x.get_bit(static_cast(n)), ((x.m_u64[3] & (1ull << (n - 192))) != 0)); return true; } +TEST(crypto, sc_set_bit_clear_bit) +{ + static_assert(sizeof(scalar_t) * 8 == 256, "size missmatch"); + + // check random value + const scalar_t x = scalar_t::random(); + scalar_t y = scalar_t::random(); + ASSERT_NEQ(x, y); + + uint8_t i = 0; + do + { + if (x.get_bit(i)) + y.set_bit(i); + else + y.clear_bit(i); + } while(++i != 0); + + ASSERT_EQ(x, y); + + return true; +} + + + // // test's runner // From 0fd6406158bd565c9bc181913fae5be2eb74e0bd Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 18 Apr 2022 18:12:15 +0200 Subject: [PATCH 0163/1271] crypto: BPP & BPPE: get_2_to_the_power_of_N_minus_1() --- src/crypto/range_proof_bpp.h | 3 +-- src/crypto/range_proof_bppe.h | 4 +--- src/crypto/range_proofs.h | 6 ++++++ 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/crypto/range_proof_bpp.h b/src/crypto/range_proof_bpp.h index a8b68807..88bd7092 100644 --- a/src/crypto/range_proof_bpp.h +++ b/src/crypto/range_proof_bpp.h @@ -573,8 +573,7 @@ namespace crypto for (size_t i = 0; i < interm.c_bpp_m; ++i) d(i, j) = d(i, j - 1) + d(i, j - 1); // sum(d) (see also note in proof function for this) - static const scalar_t c_scalar_2_power_n_minus_1 = { 0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000 }; - const scalar_t sum_d = c_scalar_2_power_n_minus_1 * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); + const scalar_t sum_d = CT::get_2_to_the_power_of_N_minus_1() * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); DBG_PRINT("Hs(d): " << d.calc_hs()); DBG_PRINT("sum(d): " << sum_d); diff --git a/src/crypto/range_proof_bppe.h b/src/crypto/range_proof_bppe.h index 83fc83c2..8785dad5 100644 --- a/src/crypto/range_proof_bppe.h +++ b/src/crypto/range_proof_bppe.h @@ -588,9 +588,7 @@ namespace crypto for (size_t i = 0; i < interm.c_bpp_m; ++i) d(i, j) = d(i, j - 1) + d(i, j - 1); // sum(d) (see also note in proof function for this) - // TODO: check for not 2^64 version - static const scalar_t c_scalar_2_power_n_minus_1 = { 0xffffffffffffffff, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000 }; - const scalar_t sum_d = c_scalar_2_power_n_minus_1 * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); + const scalar_t sum_d = CT::get_2_to_the_power_of_N_minus_1() * sum_of_powers(interm.z_sq, interm.c_bpp_log2_m); DBG_PRINT("Hs(d): " << d.calc_hs()); DBG_PRINT("sum(d): " << sum_d); diff --git a/src/crypto/range_proofs.h b/src/crypto/range_proofs.h index 2f7fcf00..bcd03393 100644 --- a/src/crypto/range_proofs.h +++ b/src/crypto/range_proofs.h @@ -110,6 +110,12 @@ namespace crypto return generators[2 * index + (select_H ? 1 : 0)]; } + static const scalar_t& get_2_to_the_power_of_N_minus_1() + { + static scalar_t result = scalar_t::power_of_2(c_bpp_n) - 1; + return result; + } + static const point_t& bpp_H; static const point_t& bpp_H2; }; // struct bpp_crypto_trait_zano From 25163db2bf64d33ba9a4eb277dcef7455f23a5da Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 18 Apr 2022 19:03:14 +0200 Subject: [PATCH 0164/1271] implemented non-blocking api for javascript calls --- src/common/threads_pool.h | 4 +-- src/gui/qt-daemon/application/mainwindow.cpp | 38 +++++++++++++++++++- src/gui/qt-daemon/application/mainwindow.h | 9 ++++- 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h index 4e4e3905..5870ea31 100644 --- a/src/common/threads_pool.h +++ b/src/common/threads_pool.h @@ -51,11 +51,11 @@ namespace utils int num_threads = std::thread::hardware_concurrency(); this->init(num_threads); } - void init(unsigned int num_threads) + void init(size_t num_threads) { m_is_stop = false; - for (int i = 0; i < num_threads; i++) + for (size_t i = 0; i < num_threads; i++) { m_threads.push_back(std::thread([this]() {this->worker_func(); })); } diff --git a/src/gui/qt-daemon/application/mainwindow.cpp b/src/gui/qt-daemon/application/mainwindow.cpp index 030a8d20..77e558cb 100644 --- a/src/gui/qt-daemon/application/mainwindow.cpp +++ b/src/gui/qt-daemon/application/mainwindow.cpp @@ -100,6 +100,7 @@ MainWindow::MainWindow() , m_system_shutdown(false) , m_view(nullptr) , m_channel(nullptr) + , m_ui_dispatch_id_counter(0) { #ifndef _MSC_VER //workaround for macos broken tolower from std, very dirty hack @@ -415,7 +416,7 @@ bool MainWindow::init(const std::string& html_path) //QtWebEngine::initialize(); init_tray_icon(html_path); set_html_path(html_path); - + m_threads_pool.init(2); m_backend.subscribe_to_core_events(this); bool r = QSslSocket::supportsSsl(); @@ -920,6 +921,41 @@ QString MainWindow::start_backend(const QString& params) CATCH_ENTRY_FAIL_API_RESPONCE(); } +QString MainWindow::sync_call(const QString& func_name, const QString& params) +{ + if (func_name == "transfer") + { + return this->transfer(params); + } + else if (func_name == "test_call") + { + return params; + } + else + { + return QString(QString() + "{ \"status\": \"Method '" + func_name + "' not found\"}"); + } +} + +QString MainWindow::async_call(const QString& func_name, const QString& params) +{ + + uint64_t job_id = m_ui_dispatch_id_counter++; + QString method_name = func_name; + QString argements = params; + + auto async_callback = [this, method_name, argements, job_id]() + { + QString res_str = this->sync_call(method_name, argements); + this->dispatch_async_call_result(std::to_string(job_id).c_str(), res_str); //general function + }; + + m_threads_pool.add_job(async_callback); + LOG_PRINT_L2("[UI_ASYNC_CALL]: started " << method_name.toStdString() << ", job id: " << job_id); + return QString::fromStdString(std::string("{ \"job_id\": ") + std::to_string(job_id) + "}"); +} + + bool MainWindow::update_wallet_status(const view::wallet_status_info& wsi) { TRY_ENTRY(); diff --git a/src/gui/qt-daemon/application/mainwindow.h b/src/gui/qt-daemon/application/mainwindow.h index c7e4c70b..f01dbe93 100644 --- a/src/gui/qt-daemon/application/mainwindow.h +++ b/src/gui/qt-daemon/application/mainwindow.h @@ -18,6 +18,8 @@ #include "currency_core/offers_services_helpers.h" #endif +#include "common/threads_pool.h" + QT_BEGIN_NAMESPACE class QWebEngineView; class QLineEdit; @@ -180,6 +182,9 @@ public: QString is_remnotenode_mode_preconfigured(); QString start_backend(const QString& params); + QString async_call(const QString& func_name, const QString& params); + QString sync_call(const QString& func_name, const QString& params); + //for test purposes onlys QString request_dummy(); @@ -193,11 +198,11 @@ signals: void wallet_sync_progress(const QString str); void handle_internal_callback(const QString str, const QString callback_name); void update_pos_mining_text(const QString str); - void do_dispatch(const QString status, const QString params); //general function void on_core_event(const QString method_name); //general function void set_options(const QString str); //general function void handle_deeplink_click(const QString str); void handle_current_action_state(const QString str); + void dispatch_async_call_result(const QString id, const QString resp); //general function private: //-------------------- i_core_event_handler -------------------- @@ -259,6 +264,8 @@ private: std::atomic m_gui_deinitialize_done_1; std::atomic m_backend_stopped_2; std::atomic m_system_shutdown; + std::atomic m_ui_dispatch_id_counter; + utils::threads_pool m_threads_pool; std::string m_master_password; From f40708985555a2433091d12e7ebfd1525049d4f3 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 18 Apr 2022 19:26:03 +0200 Subject: [PATCH 0165/1271] attempt to fix threads_pool on mac/clang --- src/common/threads_pool.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/threads_pool.h b/src/common/threads_pool.h index 5870ea31..461e8843 100644 --- a/src/common/threads_pool.h +++ b/src/common/threads_pool.h @@ -81,7 +81,7 @@ namespace utils std::mutex batch_mutex; - std::atomic cnt = 0; + std::atomic cnt(0); for (const auto& jb : cntr) { call_executor_base* pjob = jb.get(); From 78372d1bde2144b1cd2d5342921ef991e07070e3 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 19 Apr 2022 23:41:59 +0200 Subject: [PATCH 0166/1271] crypto: BPP and BPPE: minor improvements --- src/crypto/range_proof_bpp.h | 8 +++++--- src/crypto/range_proof_bppe.h | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/crypto/range_proof_bpp.h b/src/crypto/range_proof_bpp.h index 88bd7092..cd61ca85 100644 --- a/src/crypto/range_proof_bpp.h +++ b/src/crypto/range_proof_bpp.h @@ -33,6 +33,7 @@ namespace crypto if (!(cond)) { LOG_PRINT_RED("bpp_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ if (p_err) { *p_err = err_code; } return false; } + static_assert(CT::c_bpp_n <= 255, "too big N"); CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() > 0 && values.size() <= CT::c_bpp_values_max && values.size() == masks.size(), 1); CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(masks.is_reduced(), 3); @@ -65,7 +66,7 @@ namespace crypto for (size_t i = 0; i < values.size(); ++i) { const scalar_t& v = values[i]; - for (size_t j = 0; j < CT::c_bpp_n; ++j) + for (uint8_t j = 0; j < CT::c_bpp_n; ++j) { if (v.get_bit(j)) aLs(i, j) = c_scalar_1; // aL = 1, aR = 0 @@ -112,7 +113,7 @@ namespace crypto DBG_VAL_PRINT(z); // Computing vector d for aggregated version of the protocol (BP+ paper, page 17) - // (note: elements is stored column-by-column in memory) + // (note: elements are stored column-by-column in memory) // d = | 1 * z^(2*1), 1 * z^(2*2), 1 * z^(2*3), ..., 1 * z^(2*m) | // | 2 * z^(2*1), 2 * z^(2*2), 2 * z^(2*3), ..., 2 * z^(2*m) | // | 4 * z^(2*1), 4 * z^(2*2), 4 * z^(2*3), ..., 4 * z^(2*m) | @@ -164,7 +165,7 @@ namespace crypto DBG_VAL_PRINT(alpha_hat); - // calculate y^-1, y^-2, ... + // calculate 1, y^-1, y^-2, ... const scalar_t y_inverse = y.reciprocal(); scalar_vec_t y_inverse_powers(c_bpp_mn / 2 + 1); // the greatest power we need is c_bpp_mn/2 (at the first reduction round) y_inverse_powers[0] = 1; @@ -347,6 +348,7 @@ namespace crypto DBG_PRINT(ENDL << " . . . . bpp_verify() . . . . "); + static_assert(CT::c_bpp_n <= 255, "too big N"); const size_t kn = sigs.size(); CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(kn > 0, 1); diff --git a/src/crypto/range_proof_bppe.h b/src/crypto/range_proof_bppe.h index 8785dad5..7aaed80c 100644 --- a/src/crypto/range_proof_bppe.h +++ b/src/crypto/range_proof_bppe.h @@ -34,6 +34,7 @@ namespace crypto if (!(cond)) { LOG_PRINT_RED("bppe_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ if (p_err) { *p_err = err_code; } return false; } + static_assert(CT::c_bpp_n <= 255, "too big N"); CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() > 0 && values.size() <= CT::c_bpp_values_max && values.size() == masks.size() && masks.size() == masks2.size(), 1); CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(masks.is_reduced() && masks2.is_reduced(), 3); @@ -66,7 +67,7 @@ namespace crypto for (size_t i = 0; i < values.size(); ++i) { const scalar_t& v = values[i]; - for (size_t j = 0; j < CT::c_bpp_n; ++j) + for (uint8_t j = 0; j < CT::c_bpp_n; ++j) { if (v.get_bit(j)) aLs(i, j) = c_scalar_1; // aL = 1, aR = 0 @@ -114,7 +115,7 @@ namespace crypto DBG_VAL_PRINT(z); // Computing vector d for aggregated version of the protocol (BP+ paper, page 17) - // (note: elements is stored column-by-column in memory) + // (note: elements are stored column-by-column in memory) // d = | 1 * z^(2*1), 1 * z^(2*2), 1 * z^(2*3), ..., 1 * z^(2*m) | // | 2 * z^(2*1), 2 * z^(2*2), 2 * z^(2*3), ..., 2 * z^(2*m) | // | 4 * z^(2*1), 4 * z^(2*2), 4 * z^(2*3), ..., 4 * z^(2*m) | @@ -172,7 +173,7 @@ namespace crypto DBG_VAL_PRINT(alpha_hat_1); DBG_VAL_PRINT(alpha_hat_2); - // calculate y^-1, y^-2, ... + // calculate 1, y^-1, y^-2, ... const scalar_t y_inverse = y.reciprocal(); scalar_vec_t y_inverse_powers(c_bpp_mn / 2 + 1); // the greatest power we need is c_bpp_mn/2 (at the first reduction round) y_inverse_powers[0] = 1; @@ -359,6 +360,7 @@ namespace crypto DBG_PRINT(ENDL << " . . . . bppe_verify() . . . . "); + static_assert(CT::c_bpp_n <= 255, "too big N"); const size_t kn = sigs.size(); CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(kn > 0, 1); From 2eddfb8a1d6145bbf6bebabecbdcbaeda33ddf48 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 20 Apr 2022 15:08:18 +0200 Subject: [PATCH 0167/1271] command line options: data-dir moved to configurable section --- src/wallet/wallets_manager.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 015f5e7a..54f0f2f3 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -163,17 +163,17 @@ bool wallets_manager::init_command_line(int argc, char* argv[], std::string& fai command_line::add_arg(desc_cmd_only, command_line::arg_version); command_line::add_arg(desc_cmd_only, command_line::arg_os_version); // tools::get_default_data_dir() can't be called during static initialization - command_line::add_arg(desc_cmd_only, command_line::arg_data_dir, tools::get_default_data_dir()); + command_line::add_arg(desc_cmd_sett, command_line::arg_data_dir, tools::get_default_data_dir()); command_line::add_arg(desc_cmd_only, command_line::arg_stop_after_height); command_line::add_arg(desc_cmd_only, command_line::arg_config_file); command_line::add_arg(desc_cmd_sett, command_line::arg_log_dir); command_line::add_arg(desc_cmd_sett, command_line::arg_log_level); command_line::add_arg(desc_cmd_sett, command_line::arg_console); - command_line::add_arg(desc_cmd_sett, command_line::arg_show_details); + command_line::add_arg(desc_cmd_only, command_line::arg_show_details); command_line::add_arg(desc_cmd_sett, arg_alloc_win_console); command_line::add_arg(desc_cmd_sett, arg_html_folder); - command_line::add_arg(desc_cmd_sett, arg_xcode_stub); + command_line::add_arg(desc_cmd_only, arg_xcode_stub); command_line::add_arg(desc_cmd_sett, arg_enable_gui_debug_mode); command_line::add_arg(desc_cmd_sett, arg_qt_remote_debugging_port); command_line::add_arg(desc_cmd_sett, arg_remote_node); @@ -184,7 +184,7 @@ bool wallets_manager::init_command_line(int argc, char* argv[], std::string& fai command_line::add_arg(desc_cmd_sett, command_line::arg_force_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_validate_predownload); command_line::add_arg(desc_cmd_sett, command_line::arg_predownload_link); - command_line::add_arg(desc_cmd_sett, command_line::arg_deeplink); + command_line::add_arg(desc_cmd_only, command_line::arg_deeplink); command_line::add_arg(desc_cmd_sett, command_line::arg_disable_ntp); From decbbe973c19b62db930a0412d7ca7e33614bbf7 Mon Sep 17 00:00:00 2001 From: sowle Date: Wed, 20 Apr 2022 15:36:31 +0200 Subject: [PATCH 0168/1271] tests: random_helper minor improvements --- tests/core_tests/random_helper.cpp | 46 ---------------------------- tests/core_tests/random_helper.h | 49 +++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 51 deletions(-) diff --git a/tests/core_tests/random_helper.cpp b/tests/core_tests/random_helper.cpp index eac32896..724d9108 100644 --- a/tests/core_tests/random_helper.cpp +++ b/tests/core_tests/random_helper.cpp @@ -6,52 +6,6 @@ #include "chaingen.h" #include "random_helper.h" -random_state_test_restorer::random_state_test_restorer() -{ - crypto::random_prng_get_state(&m_state, sizeof m_state); -} - -random_state_test_restorer::~random_state_test_restorer() -{ - crypto::random_prng_set_state(&m_state, sizeof m_state); -} - -void random_state_test_restorer::reset_random(uint64_t seed /* = 0 */) -{ - crypto::random_prng_initialize_with_seed(seed); -} - -std::string get_random_text(size_t len) -{ - static const char text_chars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "~!@#$%^&*()-=_+\\/?,.<>|{}[]`';:\" "; - static const uint8_t text_chars_size = sizeof text_chars - 1; // to avoid '\0' - std::string result; - - if (len < 1) - return result; - - result.resize(len); - char* result_buf = &result[0]; - uint8_t* rnd_indices = new uint8_t[len]; - crypto::generate_random_bytes(len, rnd_indices); - - size_t n = len / 4, i; - for (i = 0; i < n; ++i) - { - result_buf[4 * i + 0] = text_chars[rnd_indices[4 * i + 0] % text_chars_size]; - result_buf[4 * i + 1] = text_chars[rnd_indices[4 * i + 1] % text_chars_size]; - result_buf[4 * i + 2] = text_chars[rnd_indices[4 * i + 2] % text_chars_size]; - result_buf[4 * i + 3] = text_chars[rnd_indices[4 * i + 3] % text_chars_size]; - } - for (size_t j = i * 4; j < len; ++j) - { - result_buf[j] = text_chars[rnd_indices[j] % text_chars_size]; - } - - delete[] rnd_indices; - return result; -} - //------------------------------------------------------------------------------ bool random_state_manupulation_test() diff --git a/tests/core_tests/random_helper.h b/tests/core_tests/random_helper.h index c9826665..360b326d 100644 --- a/tests/core_tests/random_helper.h +++ b/tests/core_tests/random_helper.h @@ -13,17 +13,56 @@ // Remebers random state at ctor, restores it at dtor struct random_state_test_restorer { - random_state_test_restorer(); - ~random_state_test_restorer(); - static void reset_random(uint64_t seed = 0); - + random_state_test_restorer() + { + crypto::random_prng_get_state(&m_state, sizeof m_state); + } + ~random_state_test_restorer() + { + crypto::random_prng_set_state(&m_state, sizeof m_state); + } + static void reset_random(uint64_t seed = 0) + { + crypto::random_prng_initialize_with_seed(seed); + } private: uint8_t m_state[RANDOM_STATE_SIZE]; }; #endif // #ifdef USE_INSECURE_RANDOM_RPNG_ROUTINES -std::string get_random_text(size_t len); +inline std::string get_random_text(size_t len) +{ + { + static const char text_chars[] = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789" "~!@#$%^&*()-=_+\\/?,.<>|{}[]`';:\" "; + static const uint8_t text_chars_size = sizeof text_chars - 1; // to avoid '\0' + std::string result; + + if (len < 1) + return result; + + result.resize(len); + char* result_buf = &result[0]; + uint8_t* rnd_indices = new uint8_t[len]; + crypto::generate_random_bytes(len, rnd_indices); + + size_t n = len / 4, i; + for (i = 0; i < n; ++i) + { + result_buf[4 * i + 0] = text_chars[rnd_indices[4 * i + 0] % text_chars_size]; + result_buf[4 * i + 1] = text_chars[rnd_indices[4 * i + 1] % text_chars_size]; + result_buf[4 * i + 2] = text_chars[rnd_indices[4 * i + 2] % text_chars_size]; + result_buf[4 * i + 3] = text_chars[rnd_indices[4 * i + 3] % text_chars_size]; + } + for (size_t j = i * 4; j < len; ++j) + { + result_buf[j] = text_chars[rnd_indices[j] % text_chars_size]; + } + + delete[] rnd_indices; + return result; + } +} bool random_state_manupulation_test(); bool random_evenness_test(); From 7e11ff07bfe979ff89fa05ac2ebf1dcf46fcfa8f Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 20 Apr 2022 17:17:11 +0200 Subject: [PATCH 0169/1271] Added option for disabling TOR from build --- CMakeLists.txt | 9 +++++++-- contrib/CMakeLists.txt | 10 ++++++++-- src/CMakeLists.txt | 11 +++++++++-- src/wallet/wallet2.cpp | 11 +++++++---- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ff384361..99243a7d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -56,18 +56,23 @@ if(APPLE) endif() set(USE_PCH FALSE CACHE BOOL "Use shared precompiled headers") +set(DISABLE_TOR FALSE CACHE BOOL "Disable TOR library(and related tor-connect submodule)") +set(TESTNET FALSE CACHE BOOL "Compile for testnet") +set(BUILD_GUI FALSE CACHE BOOL "Build qt-daemon") include_directories(src contrib/eos_portable_archive contrib contrib/epee/include "${CMAKE_BINARY_DIR}/version" "${CMAKE_BINARY_DIR}/contrib/zlib") add_definitions(-DSTATICLIB) -set(TESTNET FALSE CACHE BOOL "Compile for testnet") if(TESTNET) message("!!!!!! NOTICE: Project is building for TESTNET !!!!!!") add_definitions(-DTESTNET) endif() -set(BUILD_GUI FALSE CACHE BOOL "Build qt-daemon") +if(DISABLE_TOR) + message("NOTICE: Building with disabled TOR support!") + add_definitions(-DDISABLE_TOR) +endif() set(STATIC ${MSVC} CACHE BOOL "Link libraries statically") diff --git a/contrib/CMakeLists.txt b/contrib/CMakeLists.txt index 23ceb7a1..601672d5 100644 --- a/contrib/CMakeLists.txt +++ b/contrib/CMakeLists.txt @@ -4,7 +4,10 @@ set(UPNPC_BUILD_TESTS OFF CACHE BOOL "Build test executables") add_subdirectory(zlib) add_subdirectory(db) add_subdirectory(ethereum) -add_subdirectory(tor-connect) + +if( NOT DISABLE_TOR) + add_subdirectory(tor-connect) +endif() if(CMAKE_SYSTEM_NAME STREQUAL "iOS" OR CMAKE_SYSTEM_NAME STREQUAL "Android") @@ -18,7 +21,10 @@ set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib") set_property(TARGET zlibstatic PROPERTY FOLDER "contrib") set_property(TARGET mdbx PROPERTY FOLDER "contrib") set_property(TARGET lmdb PROPERTY FOLDER "contrib") -set_property(TARGET tor-connect PROPERTY FOLDER "contrib") +if( NOT DISABLE_TOR) + set_property(TARGET tor-connect PROPERTY FOLDER "contrib") +endif() + set_property(TARGET upnpc-static mdbx_chk mdbx_copy mdbx_dump mdbx_load mdbx_stat PROPERTY FOLDER "unused") if(MSVC) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 892a8585..7e3465f9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -170,7 +170,11 @@ ENABLE_SHARED_PCH_EXECUTABLE(connectivity_tool) add_executable(simplewallet ${SIMPLEWALLET}) add_dependencies(simplewallet version) -target_link_libraries(simplewallet wallet rpc currency_core crypto common zlibstatic ethash tor-connect ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) +target_link_libraries(simplewallet wallet rpc currency_core crypto common zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) + if(NOT DISABLE_TOR) + target_link_libraries(simplewallet tor-connect) + endif() + ENABLE_SHARED_PCH(simplewallet SIMPLEWALLET) ENABLE_SHARED_PCH_EXECUTABLE(simplewallet) @@ -194,7 +198,10 @@ if(BUILD_GUI) QT5_USE_MODULES(Zano WebEngineWidgets WebChannel) find_package(Qt5PrintSupport REQUIRED) - target_link_libraries(Zano wallet rpc currency_core crypto common zlibstatic ethash tor-connect Qt5::WebEngineWidgets Qt5::PrintSupport ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) + target_link_libraries(Zano wallet rpc currency_core crypto common zlibstatic ethash Qt5::WebEngineWidgets Qt5::PrintSupport ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) + if(NOT DISABLE_TOR) + target_link_libraries(Zano tor-connect) + endif() if (UNIX AND NOT APPLE) target_link_libraries(Zano rt) endif() diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 9f4abdc6..92bdd848 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -31,7 +31,10 @@ using namespace epee; #include "version.h" #include "common/encryption_filter.h" #include "crypto/bitcoin/sha256_helper.h" -#include "common/tor_helper.h" +#ifndef DISABLE_TOR + #include "common/tor_helper.h" +#endif + #include "storages/levin_abstract_invoke2.h" using namespace currency; @@ -4602,8 +4605,7 @@ void wallet2::notify_state_change(const std::string& state_code, const std::stri //---------------------------------------------------------------------------------------------------------------- void wallet2::send_transaction_to_network(const transaction& tx) { -#define ENABLE_TOR_RELAY -#ifdef ENABLE_TOR_RELAY +#ifndef DISABLE_TOR if (!m_disable_tor_relay) { //TODO check that core synchronized @@ -4644,6 +4646,7 @@ void wallet2::send_transaction_to_network(const transaction& tx) } } else +#endif // { COMMAND_RPC_SEND_RAW_TX::request req; req.tx_as_hex = epee::string_tools::buff_to_hex_nodelimer(tx_to_blob(tx)); @@ -4656,7 +4659,7 @@ void wallet2::send_transaction_to_network(const transaction& tx) WLT_LOG_L2("transaction " << get_transaction_hash(tx) << " generated ok and sent to daemon:" << ENDL << currency::obj_to_json_str(tx)); } -#endif // + } //---------------------------------------------------------------------------------------------------------------- void wallet2::add_sent_tx_detailed_info(const transaction& tx, From 2f0b5a323f863734420fcd84607fc577497422fe Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 20 Apr 2022 20:58:52 +0200 Subject: [PATCH 0170/1271] core tests fixes for TOR --- src/CMakeLists.txt | 12 +++++------- tests/core_tests/chaingen.cpp | 1 + tests/core_tests/wallet_tests_basic.cpp | 1 + 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7e3465f9..da31da22 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -130,6 +130,10 @@ else() ENABLE_SHARED_PCH(wallet WALLET) endif() +if(NOT DISABLE_TOR) + target_link_libraries(wallet tor-connect) +endif() + @@ -158,7 +162,7 @@ target_link_libraries(currency_core lmdb mdbx) add_executable(daemon ${DAEMON} ${P2P} ${CURRENCY_PROTOCOL}) add_dependencies(daemon version) - target_link_libraries(daemon rpc stratum currency_core crypto common libminiupnpc-static zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) +target_link_libraries(daemon rpc stratum currency_core crypto common libminiupnpc-static zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) ENABLE_SHARED_PCH(daemon DAEMON) ENABLE_SHARED_PCH_EXECUTABLE(daemon) @@ -171,9 +175,6 @@ ENABLE_SHARED_PCH_EXECUTABLE(connectivity_tool) add_executable(simplewallet ${SIMPLEWALLET}) add_dependencies(simplewallet version) target_link_libraries(simplewallet wallet rpc currency_core crypto common zlibstatic ethash ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) - if(NOT DISABLE_TOR) - target_link_libraries(simplewallet tor-connect) - endif() ENABLE_SHARED_PCH(simplewallet SIMPLEWALLET) ENABLE_SHARED_PCH_EXECUTABLE(simplewallet) @@ -199,9 +200,6 @@ if(BUILD_GUI) find_package(Qt5PrintSupport REQUIRED) target_link_libraries(Zano wallet rpc currency_core crypto common zlibstatic ethash Qt5::WebEngineWidgets Qt5::PrintSupport ${CMAKE_THREAD_LIBS_INIT} ${Boost_LIBRARIES}) - if(NOT DISABLE_TOR) - target_link_libraries(Zano tor-connect) - endif() if (UNIX AND NOT APPLE) target_link_libraries(Zano rt) endif() diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 01aa5f8b..4ec33355 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -927,6 +927,7 @@ bool test_generator::init_test_wallet(const currency::account_base& account, con w->assign_account(account); w->set_genesis(genesis_hash); w->set_core_proxy(m_wallet_test_core_proxy); + w->set_disable_tor_relay(true); result = w; return true; diff --git a/tests/core_tests/wallet_tests_basic.cpp b/tests/core_tests/wallet_tests_basic.cpp index fe4ec260..a3d40912 100644 --- a/tests/core_tests/wallet_tests_basic.cpp +++ b/tests/core_tests/wallet_tests_basic.cpp @@ -83,6 +83,7 @@ std::shared_ptr wallet_test::init_playtime_test_wallet(const std w->assign_account(acc); w->set_genesis(genesis_hash); w->set_core_proxy(m_core_proxy); + w->set_disable_tor_relay(true); return w; } From 3f1963b5831cc6fd755f172705f771f7fda19677 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 20 Apr 2022 21:44:25 +0200 Subject: [PATCH 0171/1271] fixed hard_fork_2_awo_wallets_basic_test --- tests/core_tests/hard_fork_2.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/core_tests/hard_fork_2.cpp b/tests/core_tests/hard_fork_2.cpp index fcff78a2..28fd1151 100644 --- a/tests/core_tests/hard_fork_2.cpp +++ b/tests/core_tests/hard_fork_2.cpp @@ -922,6 +922,7 @@ bool hard_fork_2_awo_wallets_basic_test::c1(currency::core& c, size bob_wlt_awo->load(bob_wo_filename, ""); bob_wlt_awo->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); bob_wlt_awo->set_core_proxy(m_core_proxy); + bob_wlt_awo->set_disable_tor_relay(true); CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Alice", alice_wlt, MK_TEST_COINS(110), false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob", bob_wlt, 0, false, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 1 + WALLET_DEFAULT_TX_SPENDABLE_AGE), false, ""); @@ -1054,6 +1055,7 @@ bool hard_fork_2_awo_wallets_basic_test::c1(currency::core& c, size bob_wlt_awo_restored->restore(bob_wo_restored_filename, "", bob_tracking_seed, true, ""); bob_wlt_awo_restored->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); bob_wlt_awo_restored->set_core_proxy(m_core_proxy); + bob_wlt_awo_restored->set_disable_tor_relay(true); CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_awo_restored", bob_wlt_awo_restored, MK_TEST_COINS(3), false), false, ""); @@ -1092,6 +1094,7 @@ bool hard_fork_2_awo_wallets_basic_test::c1(currency::core& c, size bob_wlt_non_auditable->restore(bob_non_auditable_filename, "", bob_seed, false, ""); bob_wlt_non_auditable->set_core_runtime_config(c.get_blockchain_storage().get_core_runtime_config()); bob_wlt_non_auditable->set_core_proxy(m_core_proxy); + bob_wlt_non_auditable->set_disable_tor_relay(true); // the balance for non-auditable wallet should be greather by mix_attr!=1 output (7 test coins + 1 left from prev step) CHECK_AND_ASSERT_MES(refresh_wallet_and_check_balance("", "Bob_non_auditable", bob_wlt_non_auditable, MK_TEST_COINS(8), false), false, ""); From a8fe3f279707cf5268f4fda17a6c6aaf890d5ce8 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 21 Apr 2022 17:43:50 +0200 Subject: [PATCH 0172/1271] Fixed #293 and refactored arg_descriptor(wanted to do it for such a long time!!!!) --- src/common/command_line.cpp | 46 +++++++++---------- src/common/command_line.h | 23 +++++----- src/connectivity_tool/conn_tool.cpp | 52 +++++++++++----------- src/currency_core/bc_offers_service.cpp | 2 +- src/currency_core/blockchain_storage.cpp | 4 +- src/currency_core/miner.cpp | 8 ++-- src/p2p/net_node.inl | 24 +++++----- src/rpc/core_rpc_server.cpp | 6 +-- src/simplewallet/simplewallet.cpp | 36 +++++++-------- src/stratum/stratum_server.cpp | 26 +++++------ src/wallet/wallet_rpc_server.cpp | 8 ++-- src/wallet/wallets_manager.cpp | 16 +++---- tests/core_tests/chaingen_main.cpp | 14 +++--- tests/functional_tests/main.cpp | 56 ++++++++++++------------ 14 files changed, 161 insertions(+), 160 deletions(-) diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index 6595763f..c3a9b057 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -10,36 +10,36 @@ namespace command_line { - const arg_descriptor arg_help = {"help", "Produce help message"}; - const arg_descriptor arg_version = {"version", "Output version information"}; - const arg_descriptor arg_data_dir = {"data-dir", "Specify data directory", ""}; + const arg_descriptor arg_help ("help", "Produce help message"); + const arg_descriptor arg_version ("version", "Output version information"); + const arg_descriptor arg_data_dir ("data-dir", "Specify data directory", ""); - const arg_descriptor arg_stop_after_height = { "stop-after-height", "If specified, the daemon will stop immediately after a block with the given height is added", 0 }; + const arg_descriptor arg_stop_after_height ( "stop-after-height", "If specified, the daemon will stop immediately after a block with the given height is added", 0 ); - const arg_descriptor arg_config_file = { "config-file", "Specify configuration file", std::string(CURRENCY_NAME_SHORT ".conf") }; - const arg_descriptor arg_os_version = { "os-version", "" }; + const arg_descriptor arg_config_file ( "config-file", "Specify configuration file", std::string(CURRENCY_NAME_SHORT ".conf") ); + const arg_descriptor arg_os_version ( "os-version", "" ); - const arg_descriptor arg_log_dir = { "log-dir", "", "", true}; - const arg_descriptor arg_log_file = { "log-file", "", "" }; - const arg_descriptor arg_log_level = { "log-level", "", LOG_LEVEL_0, true }; + const arg_descriptor arg_log_dir ( "log-dir", ""); + const arg_descriptor arg_log_file ( "log-file", "", ""); + const arg_descriptor arg_log_level ( "log-level", ""); - const arg_descriptor arg_console = { "no-console", "Disable daemon console commands" }; - const arg_descriptor arg_show_details = { "currency-details", "Display currency details" }; - const arg_descriptor arg_show_rpc_autodoc = { "show_rpc_autodoc", "Display rpc auto-generated documentation template" }; + const arg_descriptor arg_console ( "no-console", "Disable daemon console commands" ); + const arg_descriptor arg_show_details ( "currency-details", "Display currency details" ); + const arg_descriptor arg_show_rpc_autodoc ( "show_rpc_autodoc", "Display rpc auto-generated documentation template" ); - const arg_descriptor arg_disable_upnp = { "disable-upnp", "Disable UPnP (enhances local network privacy)", false, true }; - const arg_descriptor arg_disable_ntp = { "disable-ntp", "Disable NTP, could enhance to time synchronization issue but increase network privacy, consider using disable-stop-if-time-out-of-sync with it", false, true }; + const arg_descriptor arg_disable_upnp ( "disable-upnp", "Disable UPnP (enhances local network privacy)"); + const arg_descriptor arg_disable_ntp ( "disable-ntp", "Disable NTP, could enhance to time synchronization issue but increase network privacy, consider using disable-stop-if-time-out-of-sync with it"); - const arg_descriptor arg_disable_stop_if_time_out_of_sync = { "disable-stop-if-time-out-of-sync", "Do not stop the daemon if serious time synchronization problem is detected", false, true }; - const arg_descriptor arg_disable_stop_on_low_free_space = { "disable-stop-on-low-free-space", "Do not stop the daemon if free space at data dir is critically low", false, true }; - const arg_descriptor arg_enable_offers_service = { "enable-offers-service", "Enables marketplace feature", false, false}; - const arg_descriptor arg_db_engine = { "db-engine", "Specify database engine for storage. May be \"lmdb\"(default) or \"mdbx\"", ARG_DB_ENGINE_LMDB, false }; + const arg_descriptor arg_disable_stop_if_time_out_of_sync ( "disable-stop-if-time-out-of-sync", "Do not stop the daemon if serious time synchronization problem is detected"); + const arg_descriptor arg_disable_stop_on_low_free_space ( "disable-stop-on-low-free-space", "Do not stop the daemon if free space at data dir is critically low"); + const arg_descriptor arg_enable_offers_service ( "enable-offers-service", "Enables marketplace feature", false); + const arg_descriptor arg_db_engine ( "db-engine", "Specify database engine for storage. May be \"lmdb\"(default) or \"mdbx\"", ARG_DB_ENGINE_LMDB ); - const arg_descriptor arg_no_predownload = { "no-predownload", "Do not pre-download blockchain database", }; - const arg_descriptor arg_force_predownload = { "force-predownload", "Pre-download blockchain database regardless of it's status", }; - const arg_descriptor arg_validate_predownload = { "validate-predownload", "Paranoid mode, re-validate each block from pre-downloaded database and rebuild own database", }; - const arg_descriptor arg_predownload_link = { "predownload-link", "Override url for blockchain database pre-downloading", "", true }; + const arg_descriptor arg_no_predownload ( "no-predownload", "Do not pre-download blockchain database", ); + const arg_descriptor arg_force_predownload ( "force-predownload", "Pre-download blockchain database regardless of it's status", ); + const arg_descriptor arg_validate_predownload ( "validate-predownload", "Paranoid mode, re-validate each block from pre-downloaded database and rebuild own database", ); + const arg_descriptor arg_predownload_link ( "predownload-link", "Override url for blockchain database pre-downloading"); - const arg_descriptor arg_deeplink = { "deeplink-params", "Deeplink parameter, in that case app just forward params to running app", "", true }; + const arg_descriptor arg_deeplink ( "deeplink-params", "Deeplink parameter, in that case app just forward params to running app"); } diff --git a/src/common/command_line.h b/src/common/command_line.h index 652d1777..3d9107dc 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -26,27 +26,28 @@ namespace command_line arg_descriptor(const char* _name, const char* _description): name(_name), description(_description), - not_use_default(true), + use_default(false), default_value(T()) {} arg_descriptor(const char* _name, const char* _description, const T& default_val) : name(_name), description(_description), - not_use_default(false), + use_default(true), default_value(default_val) {} - arg_descriptor(const char* _name, const char* _description, const T& default_val, bool not_use_default) : - name(_name), - description(_description), - default_value(default_val), - not_use_default(not_use_default) - {} +// arg_descriptor(const char* _name, const char* _description, const T& default_val, bool not_use_default) : +// name(_name), +// description(_description), +// default_value(default_val), +// not_use_default(not_use_default) +// {} + const char* name; const char* description; + bool use_default; T default_value; - bool not_use_default; }; template @@ -79,7 +80,7 @@ namespace command_line boost::program_options::typed_value* make_semantic(const arg_descriptor& arg) { auto semantic = boost::program_options::value(); - if (!arg.not_use_default) + if (arg.use_default) semantic->default_value(arg.default_value); return semantic; } @@ -88,7 +89,7 @@ namespace command_line boost::program_options::typed_value* make_semantic(const arg_descriptor& arg, const T& def) { auto semantic = boost::program_options::value(); - if (!arg.not_use_default) + if (arg.use_default) semantic->default_value(def); return semantic; } diff --git a/src/connectivity_tool/conn_tool.cpp b/src/connectivity_tool/conn_tool.cpp index 33ea6bd9..1ee022b3 100644 --- a/src/connectivity_tool/conn_tool.cpp +++ b/src/connectivity_tool/conn_tool.cpp @@ -38,32 +38,32 @@ using namespace nodetool; namespace { - const command_line::arg_descriptor arg_ip = {"ip", "set ip", "127.0.0.1", false}; - const command_line::arg_descriptor arg_port = {"port", "set port"}; - const command_line::arg_descriptor arg_rpc_port = {"rpc-port", "set rpc port", RPC_DEFAULT_PORT, false}; - const command_line::arg_descriptor arg_timeout = {"timeout", "set timeout", 5000, false}; - const command_line::arg_descriptor arg_priv_key = {"private-key", "private key to subscribe debug command", "", true}; - const command_line::arg_descriptor arg_peer_id = {"peer-id", "peerid if known(if not - will be requested)", 0}; - const command_line::arg_descriptor arg_generate_keys = {"generate-keys-pair", "generate private and public keys pair"}; - const command_line::arg_descriptor arg_request_stat_info = {"request-stat-info", "request statistics information"}; - const command_line::arg_descriptor arg_log_journal_len = { "log-journal-len", "Specify length of list of last error messages in log", 0, true }; - const command_line::arg_descriptor arg_request_net_state = {"request-net-state", "request network state information (peer list, connections count)"}; - const command_line::arg_descriptor arg_get_daemon_info = {"rpc-get-daemon-info", "request daemon state info vie rpc (--rpc-port option should be set ).", "", true}; - const command_line::arg_descriptor arg_get_aliases = {"rpc-get-aliases", "request daemon aliases all list", "", true}; - const command_line::arg_descriptor arg_increment_build_no = { "increment-build-no", "Increment build nimber", "", true }; - const command_line::arg_descriptor arg_upate_maintainers_info = {"update-maintainers-info", "Push maintainers info into the network, update-maintainers-info=file-with-info.json", "", true}; - const command_line::arg_descriptor arg_update_build_no = {"update-build-no", "Updated version number in version template file", "", true}; - const command_line::arg_descriptor arg_generate_genesis = {"generate-genesis", "Generate genesis coinbase based on config file", "", true }; - const command_line::arg_descriptor arg_genesis_split_amount = { "genesis-split-amount", "Set split amount for generating genesis block", 0, true }; - const command_line::arg_descriptor arg_get_info_flags = { "getinfo-flags-hex", "Set of bits for rpc-get-daemon-info", "", true }; - const command_line::arg_descriptor arg_set_peer_log_level = { "set-peer-log-level", "Set log level for remote peer", 0, true }; - const command_line::arg_descriptor arg_download_peer_log = { "download-peer-log", "Download log from remote peer [,]", "", true }; - const command_line::arg_descriptor arg_do_consloe_log = { "do-console-log", "Tool generates debug console output(debug purposes)", "", true }; - const command_line::arg_descriptor arg_generate_integrated_address = { "generate-integrated-address", "Tool create integrated address from simple address and payment_id", "", true }; - const command_line::arg_descriptor arg_pack_file = {"pack-file", "perform gzip-packing and calculate hash for a given file", "", true }; - const command_line::arg_descriptor arg_unpack_file = {"unpack-file", "Perform gzip-unpacking and calculate hash for a given file", "", true }; - const command_line::arg_descriptor arg_target_file = {"target-file", "Specify target file for pack-file and unpack-file commands", "", true }; - //const command_line::arg_descriptor arg_send_ipc = {"send-ipc", "Send IPC request to UI", "", true }; + const command_line::arg_descriptor arg_ip ("ip", "set ip", "127.0.0.1"); + const command_line::arg_descriptor arg_port ("port", "set port"); + const command_line::arg_descriptor arg_rpc_port ("rpc-port", "set rpc port", RPC_DEFAULT_PORT); + const command_line::arg_descriptor arg_timeout ("timeout", "set timeout", 5000); + const command_line::arg_descriptor arg_priv_key ("private-key", "private key to subscribe debug command"); + const command_line::arg_descriptor arg_peer_id ("peer-id", "peerid if known(if not - will be requested)", 0); + const command_line::arg_descriptor arg_generate_keys ("generate-keys-pair", "generate private and public keys pair"); + const command_line::arg_descriptor arg_request_stat_info ("request-stat-info", "request statistics information"); + const command_line::arg_descriptor arg_log_journal_len ( "log-journal-len", "Specify length of list of last error messages in log"); + const command_line::arg_descriptor arg_request_net_state ("request-net-state", "request network state information (peer list, connections count)"); + const command_line::arg_descriptor arg_get_daemon_info ("rpc-get-daemon-info", "request daemon state info vie rpc (--rpc-port option should be set )."); + const command_line::arg_descriptor arg_get_aliases ("rpc-get-aliases", "request daemon aliases all list"); + const command_line::arg_descriptor arg_increment_build_no ( "increment-build-no", "Increment build nimber"); + const command_line::arg_descriptor arg_upate_maintainers_info ("update-maintainers-info", "Push maintainers info into the network, update-maintainers-info=file-with-info.json"); + const command_line::arg_descriptor arg_update_build_no ("update-build-no", "Updated version number in version template file"); + const command_line::arg_descriptor arg_generate_genesis ("generate-genesis", "Generate genesis coinbase based on config file"); + const command_line::arg_descriptor arg_genesis_split_amount ( "genesis-split-amount", "Set split amount for generating genesis block"); + const command_line::arg_descriptor arg_get_info_flags ( "getinfo-flags-hex", "Set of bits for rpc-get-daemon-info", ""); + const command_line::arg_descriptor arg_set_peer_log_level ( "set-peer-log-level", "Set log level for remote peer"); + const command_line::arg_descriptor arg_download_peer_log ( "download-peer-log", "Download log from remote peer [,]"); + const command_line::arg_descriptor arg_do_consloe_log ( "do-console-log", "Tool generates debug console output(debug purposes)"); + const command_line::arg_descriptor arg_generate_integrated_address ( "generate-integrated-address", "Tool create integrated address from simple address and payment_id"); + const command_line::arg_descriptor arg_pack_file ("pack-file", "perform gzip-packing and calculate hash for a given file"); + const command_line::arg_descriptor arg_unpack_file ("unpack-file", "Perform gzip-unpacking and calculate hash for a given file"); + const command_line::arg_descriptor arg_target_file ("target-file", "Specify target file for pack-file and unpack-file commands"); + //const command_line::arg_descriptor arg_send_ipc ("send-ipc", "Send IPC request to UI"); } typedef COMMAND_REQUEST_STAT_INFO_T::stat_info> COMMAND_REQUEST_STAT_INFO; diff --git a/src/currency_core/bc_offers_service.cpp b/src/currency_core/bc_offers_service.cpp index 59064632..b6349e90 100644 --- a/src/currency_core/bc_offers_service.cpp +++ b/src/currency_core/bc_offers_service.cpp @@ -13,7 +13,7 @@ #include "storages/portable_storage_template_helper.h" -command_line::arg_descriptor arg_market_disable = { "disable-market", "Start GUI with market service disabled", false, true }; +command_line::arg_descriptor arg_market_disable ( "disable-market", "Start GUI with market service disabled"); namespace bc_services diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 6bb47719..9ab636f5 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -80,8 +80,8 @@ DISABLE_VS_WARNINGS(4267) namespace { - const command_line::arg_descriptor arg_db_cache_l1 = { "db-cache-l1", "Specify size of memory mapped db cache file", 0, true }; - const command_line::arg_descriptor arg_db_cache_l2 = { "db-cache-l2", "Specify cached elements in db helpers", 0, true }; + const command_line::arg_descriptor arg_db_cache_l1 ( "db-cache-l1", "Specify size of memory mapped db cache file"); + const command_line::arg_descriptor arg_db_cache_l2 ( "db-cache-l2", "Specify cached elements in db helpers"); } //------------------------------------------------------------------ diff --git a/src/currency_core/miner.cpp b/src/currency_core/miner.cpp index 005c274c..d52e51e2 100644 --- a/src/currency_core/miner.cpp +++ b/src/currency_core/miner.cpp @@ -33,10 +33,10 @@ namespace currency namespace { - const command_line::arg_descriptor arg_extra_messages = {"extra-messages-file", "Specify file for extra messages to include into coinbase transactions", "", true}; - const command_line::arg_descriptor arg_start_mining = {"start-mining", "Specify wallet address to mining for", "", true}; - const command_line::arg_descriptor arg_mining_threads = {"mining-threads", "Specify mining threads count", 0, true}; - const command_line::arg_descriptor arg_block_template_extra_text = { "miner-text-info", "Set block extra text info", "", true }; + const command_line::arg_descriptor arg_extra_messages ("extra-messages-file", "Specify file for extra messages to include into coinbase transactions"); + const command_line::arg_descriptor arg_start_mining ("start-mining", "Specify wallet address to mining for"); + const command_line::arg_descriptor arg_mining_threads ("mining-threads", "Specify mining threads count"); + const command_line::arg_descriptor arg_block_template_extra_text ( "miner-text-info", "Set block extra text info"); } diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl index 477d7e8b..a63e88ed 100644 --- a/src/p2p/net_node.inl +++ b/src/p2p/net_node.inl @@ -26,18 +26,18 @@ namespace nodetool namespace { - const command_line::arg_descriptor arg_p2p_bind_ip = {"p2p-bind-ip", "Interface for p2p network protocol", "0.0.0.0"}; - const command_line::arg_descriptor arg_p2p_bind_port = {"p2p-bind-port", "Port for p2p network protocol", boost::to_string(P2P_DEFAULT_PORT)}; - const command_line::arg_descriptor arg_p2p_external_port = {"p2p-external-port", "External port for p2p network protocol (if port forwarding used with NAT)", 0}; - const command_line::arg_descriptor arg_p2p_allow_local_ip = {"allow-local-ip", "Allow local ip add to peer list, mostly in debug purposes"}; - const command_line::arg_descriptor > arg_p2p_add_peer = {"add-peer", "Manually add peer to local peerlist"}; - const command_line::arg_descriptor > arg_p2p_add_priority_node = {"add-priority-node", "Specify list of peers to connect to and attempt to keep the connection open"}; - const command_line::arg_descriptor arg_p2p_use_only_priority_nodes = {"use-only-priority-nodes", "Try to connect only to priority nodes"}; - const command_line::arg_descriptor > arg_p2p_seed_node = {"seed-node", "Connect to a node to retrieve peer addresses, and disconnect"}; - const command_line::arg_descriptor arg_p2p_hide_my_port = {"hide-my-port", "Do not announce yourself as peerlist candidate", false, true}; - const command_line::arg_descriptor arg_p2p_offline_mode = { "offline-mode", "Don't connect to any node and reject any connections", false, true }; - const command_line::arg_descriptor arg_p2p_disable_debug_reqs = { "disable-debug-p2p-requests", "Disable p2p debug requests", false, true }; - const command_line::arg_descriptor arg_p2p_ip_auto_blocking = { "p2p-ip-auto-blocking", "Enable (1) or disable (0) peers auto-blocking by IP <0|1>. Default: 0", 0, false }; + const command_line::arg_descriptor arg_p2p_bind_ip ("p2p-bind-ip", "Interface for p2p network protocol", "0.0.0.0"); + const command_line::arg_descriptor arg_p2p_bind_port ("p2p-bind-port", "Port for p2p network protocol", boost::to_string(P2P_DEFAULT_PORT)); + const command_line::arg_descriptor arg_p2p_external_port ("p2p-external-port", "External port for p2p network protocol (if port forwarding used with NAT)", 0); + const command_line::arg_descriptor arg_p2p_allow_local_ip ("allow-local-ip", "Allow local ip add to peer list, mostly in debug purposes"); + const command_line::arg_descriptor > arg_p2p_add_peer ("add-peer", "Manually add peer to local peerlist"); + const command_line::arg_descriptor > arg_p2p_add_priority_node ("add-priority-node", "Specify list of peers to connect to and attempt to keep the connection open"); + const command_line::arg_descriptor arg_p2p_use_only_priority_nodes ("use-only-priority-nodes", "Try to connect only to priority nodes"); + const command_line::arg_descriptor > arg_p2p_seed_node ("seed-node", "Connect to a node to retrieve peer addresses, and disconnect"); + const command_line::arg_descriptor arg_p2p_hide_my_port ("hide-my-port", "Do not announce yourself as peerlist candidate"); + const command_line::arg_descriptor arg_p2p_offline_mode ( "offline-mode", "Don't connect to any node and reject any connections"); + const command_line::arg_descriptor arg_p2p_disable_debug_reqs ( "disable-debug-p2p-requests", "Disable p2p debug requests"); + const command_line::arg_descriptor arg_p2p_ip_auto_blocking ( "p2p-ip-auto-blocking", "Enable (1) or disable (0) peers auto-blocking by IP <0|1>. Default: 0", 1); } //----------------------------------------------------------------------------------- diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 0f4d866c..5adc60f9 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -22,9 +22,9 @@ namespace currency { namespace { - const command_line::arg_descriptor arg_rpc_bind_ip = {"rpc-bind-ip", "", "127.0.0.1"}; - const command_line::arg_descriptor arg_rpc_bind_port = {"rpc-bind-port", "", std::to_string(RPC_DEFAULT_PORT)}; - const command_line::arg_descriptor arg_rpc_ignore_status = {"rpc-ignore-offline", "Let rpc calls despite online/offline status", false, true }; + const command_line::arg_descriptor arg_rpc_bind_ip ("rpc-bind-ip", "", "127.0.0.1"); + const command_line::arg_descriptor arg_rpc_bind_port ("rpc-bind-port", "", std::to_string(RPC_DEFAULT_PORT)); + const command_line::arg_descriptor arg_rpc_ignore_status ("rpc-ignore-offline", "Let rpc calls despite online/offline status"); } //----------------------------------------------------------------------------------- void core_rpc_server::init_options(boost::program_options::options_description& desc) diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index e2f2f88b..1cc8d064 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -40,25 +40,25 @@ namespace ph = boost::placeholders; namespace { - const command_line::arg_descriptor arg_wallet_file = {"wallet-file", "Use wallet ", ""}; - const command_line::arg_descriptor arg_generate_new_wallet = {"generate-new-wallet", "Generate new wallet and save it to or
.wallet by default", ""}; - const command_line::arg_descriptor arg_generate_new_auditable_wallet = {"generate-new-auditable-wallet", "Generate new auditable wallet and store it to ", ""}; - const command_line::arg_descriptor arg_daemon_address = {"daemon-address", "Use daemon instance at :", ""}; - const command_line::arg_descriptor arg_daemon_host = {"daemon-host", "Use daemon instance at host instead of localhost", ""}; - const command_line::arg_descriptor arg_password = {"password", "Wallet password", "", true}; - const command_line::arg_descriptor arg_dont_refresh = { "no-refresh", "Do not refresh after load", false, true }; - const command_line::arg_descriptor arg_dont_set_date = { "no-set-creation-date", "Do not set wallet creation date", false, false }; - const command_line::arg_descriptor arg_daemon_port = {"daemon-port", "Use daemon instance at port instead of default", 0}; - const command_line::arg_descriptor arg_log_level = {"set-log", "", 0, true}; - const command_line::arg_descriptor arg_do_pos_mining = { "do-pos-mining", "Do PoS mining", false, false }; - const command_line::arg_descriptor arg_pos_mining_reward_address = { "pos-mining-reward-address", "Block reward will be sent to the giving address if specified", "" }; - const command_line::arg_descriptor arg_restore_wallet = { "restore-wallet", "Restore wallet from seed phrase or tracking seed and save it to ", "" }; - const command_line::arg_descriptor arg_offline_mode = { "offline-mode", "Don't connect to daemon, work offline (for cold-signing process)", false, true }; - const command_line::arg_descriptor arg_scan_for_wallet = { "scan-for-wallet", "", "", true }; - const command_line::arg_descriptor arg_addr_to_compare = { "addr-to-compare", "", "", true }; - const command_line::arg_descriptor arg_disable_tor_relay = { "disable-tor-relay", "Do PoS mining", false, false }; + const command_line::arg_descriptor arg_wallet_file ("wallet-file", "Use wallet ", ""); + const command_line::arg_descriptor arg_generate_new_wallet ("generate-new-wallet", "Generate new wallet and save it to or
.wallet by default", ""); + const command_line::arg_descriptor arg_generate_new_auditable_wallet ("generate-new-auditable-wallet", "Generate new auditable wallet and store it to ", ""); + const command_line::arg_descriptor arg_daemon_address ("daemon-address", "Use daemon instance at :", ""); + const command_line::arg_descriptor arg_daemon_host ("daemon-host", "Use daemon instance at host instead of localhost", ""); + const command_line::arg_descriptor arg_password ("password", "Wallet password"); + const command_line::arg_descriptor arg_dont_refresh ( "no-refresh", "Do not refresh after load"); + const command_line::arg_descriptor arg_dont_set_date ( "no-set-creation-date", "Do not set wallet creation date", false); + const command_line::arg_descriptor arg_daemon_port ("daemon-port", "Use daemon instance at port instead of default", 0); + const command_line::arg_descriptor arg_log_level ("set-log", ""); + const command_line::arg_descriptor arg_do_pos_mining ( "do-pos-mining", "Do PoS mining", false); + const command_line::arg_descriptor arg_pos_mining_reward_address ( "pos-mining-reward-address", "Block reward will be sent to the giving address if specified", "" ); + const command_line::arg_descriptor arg_restore_wallet ( "restore-wallet", "Restore wallet from seed phrase or tracking seed and save it to ", "" ); + const command_line::arg_descriptor arg_offline_mode ( "offline-mode", "Don't connect to daemon, work offline (for cold-signing process)"); + const command_line::arg_descriptor arg_scan_for_wallet ( "scan-for-wallet", ""); + const command_line::arg_descriptor arg_addr_to_compare ( "addr-to-compare", ""); + const command_line::arg_descriptor arg_disable_tor_relay ( "disable-tor-relay", "Do PoS mining", false); - const command_line::arg_descriptor< std::vector > arg_command = {"command", ""}; + const command_line::arg_descriptor< std::vector > arg_command ("command", ""); inline std::string interpret_rpc_response(bool ok, const std::string& status) { diff --git a/src/stratum/stratum_server.cpp b/src/stratum/stratum_server.cpp index 5e239baa..f952cd7a 100644 --- a/src/stratum/stratum_server.cpp +++ b/src/stratum/stratum_server.cpp @@ -39,25 +39,25 @@ namespace #define VDIFF_RETARGET_SHARES_COUNT 12 // enforce retargeting if this many shares are received (high performace in terms of current difficulty) #define VDIFF_VARIANCE_PERCENT_DEFAULT 25 // % - const command_line::arg_descriptor arg_stratum = {"stratum", "Stratum server: enable" }; - const command_line::arg_descriptor arg_stratum_bind_ip = {"stratum-bind-ip", "Stratum server: IP to bind", STRATUM_BIND_IP_DEFAULT }; - const command_line::arg_descriptor arg_stratum_bind_port = {"stratum-bind-port", "Stratum server: port to listen at", std::to_string(STRATUM_DEFAULT_PORT) }; - const command_line::arg_descriptor arg_stratum_threads = {"stratum-threads-count", "Stratum server: number of server threads", STRATUM_THREADS_COUNT_DEFAULT }; + const command_line::arg_descriptor arg_stratum ("stratum", "Stratum server: enable" ); + const command_line::arg_descriptor arg_stratum_bind_ip ("stratum-bind-ip", "Stratum server: IP to bind", STRATUM_BIND_IP_DEFAULT ); + const command_line::arg_descriptor arg_stratum_bind_port ("stratum-bind-port", "Stratum server: port to listen at", std::to_string(STRATUM_DEFAULT_PORT) ); + const command_line::arg_descriptor arg_stratum_threads ("stratum-threads-count", "Stratum server: number of server threads", STRATUM_THREADS_COUNT_DEFAULT ); const command_line::arg_descriptor arg_stratum_miner_address = {"stratum-miner-address", "Stratum server: miner address. All workers" " will mine to this address. If not set here, ALL workers should use the very same wallet address as username." - " If set here - they're allowed to log in with username '" WORKER_ALLOWED_USERNAME "' instead of address.", "", true }; + " If set here - they're allowed to log in with username '" WORKER_ALLOWED_USERNAME "' instead of address."}; const command_line::arg_descriptor arg_stratum_block_template_update_period = {"stratum-template-update-period", "Stratum server: if there are no new blocks, update block template this often (sec.)", STRATUM_BLOCK_TEMPLATE_UPD_PERIOD_DEFAULT }; - const command_line::arg_descriptor arg_stratum_hr_print_interval = {"stratum-hr-print-interval", "Stratum server: how often to print hashrate stats (sec.)", STRATUM_TOTAL_HR_PRINT_INTERVAL_S_DEFAULT }; + const command_line::arg_descriptor arg_stratum_hr_print_interval ("stratum-hr-print-interval", "Stratum server: how often to print hashrate stats (sec.)", STRATUM_TOTAL_HR_PRINT_INTERVAL_S_DEFAULT ); - const command_line::arg_descriptor arg_stratum_vdiff_target_min = {"stratum-vdiff-target-min", "Stratum server: minimum worker difficulty", VDIFF_TARGET_MIN_DEFAULT }; - const command_line::arg_descriptor arg_stratum_vdiff_target_max = {"stratum-vdiff-target-max", "Stratum server: maximum worker difficulty", VDIFF_TARGET_MAX_DEFAULT }; - const command_line::arg_descriptor arg_stratum_vdiff_target_time = {"stratum-vdiff-target-time", "Stratum server: target time per share (i.e. try to get one share per this many seconds)", VDIFF_TARGET_TIME_DEFAULT }; - const command_line::arg_descriptor arg_stratum_vdiff_retarget_time = {"stratum-vdiff-retarget-time", "Stratum server: check to see if we should retarget this often (sec.)", VDIFF_RETARGET_TIME_DEFAULT }; - const command_line::arg_descriptor arg_stratum_vdiff_retarget_shares = {"stratum-vdiff-retarget-shares", "Stratum server: enforce retargeting if got this many shares", VDIFF_RETARGET_SHARES_COUNT }; - const command_line::arg_descriptor arg_stratum_vdiff_variance_percent = {"stratum-vdiff-variance-percent", "Stratum server: allow average time to very this % from target without retarget", VDIFF_VARIANCE_PERCENT_DEFAULT }; - const command_line::arg_descriptor arg_stratum_always_online = { "stratum-always-online", "Stratum server consider the core being synchronized regardless of online status, useful for debugging with --offline-mode" }; + const command_line::arg_descriptor arg_stratum_vdiff_target_min ("stratum-vdiff-target-min", "Stratum server: minimum worker difficulty", VDIFF_TARGET_MIN_DEFAULT ); + const command_line::arg_descriptor arg_stratum_vdiff_target_max ("stratum-vdiff-target-max", "Stratum server: maximum worker difficulty", VDIFF_TARGET_MAX_DEFAULT ); + const command_line::arg_descriptor arg_stratum_vdiff_target_time ("stratum-vdiff-target-time", "Stratum server: target time per share (i.e. try to get one share per this many seconds)", VDIFF_TARGET_TIME_DEFAULT ); + const command_line::arg_descriptor arg_stratum_vdiff_retarget_time ("stratum-vdiff-retarget-time", "Stratum server: check to see if we should retarget this often (sec.)", VDIFF_RETARGET_TIME_DEFAULT ); + const command_line::arg_descriptor arg_stratum_vdiff_retarget_shares ("stratum-vdiff-retarget-shares", "Stratum server: enforce retargeting if got this many shares", VDIFF_RETARGET_SHARES_COUNT ); + const command_line::arg_descriptor arg_stratum_vdiff_variance_percent ("stratum-vdiff-variance-percent", "Stratum server: allow average time to very this % from target without retarget", VDIFF_VARIANCE_PERCENT_DEFAULT ); + const command_line::arg_descriptor arg_stratum_always_online ( "stratum-always-online", "Stratum server consider the core being synchronized regardless of online status, useful for debugging with --offline-mode" ); //============================================================================================================================== diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index ca888e83..43d9625b 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -43,10 +43,10 @@ using namespace epee; namespace tools { //----------------------------------------------------------------------------------- - const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port = {"rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server", "", true}; - const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip = {"rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"}; - const command_line::arg_descriptor wallet_rpc_server::arg_miner_text_info = { "miner-text-info", "Wallet password", "", true }; - const command_line::arg_descriptor wallet_rpc_server::arg_deaf_mode = { "deaf", "Put wallet into 'deaf' mode make it ignore any rpc commands(usable for safe PoS mining)", false, true }; + const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_port ("rpc-bind-port", "Starts wallet as rpc server for wallet operations, sets bind port for server"); + const command_line::arg_descriptor wallet_rpc_server::arg_rpc_bind_ip ("rpc-bind-ip", "Specify ip to bind rpc server", "127.0.0.1"); + const command_line::arg_descriptor wallet_rpc_server::arg_miner_text_info ( "miner-text-info", "Wallet password"); + const command_line::arg_descriptor wallet_rpc_server::arg_deaf_mode ( "deaf", "Put wallet into 'deaf' mode make it ignore any rpc commands(usable for safe PoS mining)"); void wallet_rpc_server::init_options(boost::program_options::options_description& desc) { diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 54f0f2f3..298b338e 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -51,15 +51,15 @@ #define HTTP_PROXY_TIMEOUT 2000 #define HTTP_PROXY_ATTEMPTS_COUNT 1 -const command_line::arg_descriptor arg_alloc_win_console = { "alloc-win-console", "Allocates debug console with GUI", false }; -const command_line::arg_descriptor arg_html_folder = { "html-path", "Manually set GUI html folder path", "", true }; -const command_line::arg_descriptor arg_xcode_stub = { "-NSDocumentRevisionsDebugMode", "Substitute for xcode bug", "", true }; -const command_line::arg_descriptor arg_enable_gui_debug_mode = { "gui-debug-mode", "Enable debug options in GUI", false, true }; -const command_line::arg_descriptor arg_qt_remote_debugging_port = { "remote-debugging-port", "Specify port for Qt remote debugging", 30333, true }; -const command_line::arg_descriptor arg_remote_node = { "remote-node", "Switch GUI to work with remote node instead of local daemon", "", true }; -const command_line::arg_descriptor arg_enable_qt_logs = { "enable-qt-logs", "Forward Qt log messages into main log", false, true }; +const command_line::arg_descriptor arg_alloc_win_console ( "alloc-win-console", "Allocates debug console with GUI", false ); +const command_line::arg_descriptor arg_html_folder ( "html-path", "Manually set GUI html folder path"); +const command_line::arg_descriptor arg_xcode_stub ( "-NSDocumentRevisionsDebugMode", "Substitute for xcode bug"); +const command_line::arg_descriptor arg_enable_gui_debug_mode ( "gui-debug-mode", "Enable debug options in GUI"); +const command_line::arg_descriptor arg_qt_remote_debugging_port ( "remote-debugging-port", "Specify port for Qt remote debugging"); +const command_line::arg_descriptor arg_remote_node ( "remote-node", "Switch GUI to work with remote node instead of local daemon"); +const command_line::arg_descriptor arg_enable_qt_logs ( "enable-qt-logs", "Forward Qt log messages into main log"); const command_line::arg_descriptor arg_disable_logs_init("disable-logs-init", "Disable log initialization in GUI"); -const command_line::arg_descriptor arg_qt_dev_tools = { "qt-dev-tools", "Enable main web page inspection with Chromium DevTools, [,scale], e.g. \"horizontal,1.3\"", "", false }; +const command_line::arg_descriptor arg_qt_dev_tools ( "qt-dev-tools", "Enable main web page inspection with Chromium DevTools, [,scale], e.g. \"horizontal,1.3\"", ""); wallets_manager::wallets_manager():m_pview(&m_view_stub), m_stop_singal_sent(false), diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 0765a006..f19180f1 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -21,13 +21,13 @@ namespace po = boost::program_options; namespace { - const command_line::arg_descriptor arg_test_data_path = {"test-data-path", "", ""}; - const command_line::arg_descriptor arg_generate_test_data = {"generate-test-data", ""}; - const command_line::arg_descriptor arg_play_test_data = {"play-test-data", ""}; - const command_line::arg_descriptor arg_generate_and_play_test_data = {"generate-and-play-test-data", ""}; - const command_line::arg_descriptor arg_test_transactions = {"test-transactions", ""}; - const command_line::arg_descriptor arg_run_single_test = {"run-single-test", "" }; - const command_line::arg_descriptor arg_enable_debug_asserts = {"enable-debug-asserts", "" }; + const command_line::arg_descriptor arg_test_data_path ("test-data-path", "", ""); + const command_line::arg_descriptor arg_generate_test_data ("generate-test-data", ""); + const command_line::arg_descriptor arg_play_test_data ("play-test-data", ""); + const command_line::arg_descriptor arg_generate_and_play_test_data ("generate-and-play-test-data", ""); + const command_line::arg_descriptor arg_test_transactions ("test-transactions", ""); + const command_line::arg_descriptor arg_run_single_test ("run-single-test", "" ); + const command_line::arg_descriptor arg_enable_debug_asserts ("enable-debug-asserts", "" ); boost::program_options::variables_map g_vm; } diff --git a/tests/functional_tests/main.cpp b/tests/functional_tests/main.cpp index fc3da2d0..b275bd4a 100644 --- a/tests/functional_tests/main.cpp +++ b/tests/functional_tests/main.cpp @@ -23,40 +23,40 @@ using namespace epee; namespace po = boost::program_options; -const command_line::arg_descriptor arg_test_core_prepare_and_store = { "prepare-and-store-events-to-file", "", "", true }; -const command_line::arg_descriptor arg_test_core_load_and_replay = { "load-and-replay-events-from-file", "", "", true }; +const command_line::arg_descriptor arg_test_core_prepare_and_store ( "prepare-and-store-events-to-file", ""); +const command_line::arg_descriptor arg_test_core_load_and_replay ( "load-and-replay-events-from-file", ""); namespace { - const command_line::arg_descriptor arg_test_transactions_flow = {"test-transactions-flow", ""}; - const command_line::arg_descriptor arg_test_miniupnp = {"test-miniupnp", ""}; - const command_line::arg_descriptor arg_test_core_concurrency = {"test-core-concurrency", ""}; + const command_line::arg_descriptor arg_test_transactions_flow ("test-transactions-flow", ""); + const command_line::arg_descriptor arg_test_miniupnp ("test-miniupnp", ""); + const command_line::arg_descriptor arg_test_core_concurrency ("test-core-concurrency", ""); - const command_line::arg_descriptor arg_working_folder = {"working-folder", "", "."}; - const command_line::arg_descriptor arg_source_wallet = {"source-wallet", "", "", true}; - const command_line::arg_descriptor arg_dest_wallet = {"dest-wallet", "", "", true}; - const command_line::arg_descriptor arg_source_wallet_pass = {"source-wallet-pass", "", "", true}; - const command_line::arg_descriptor arg_dest_wallet_pass = {"dest-wallet-pass", "", "", true}; + const command_line::arg_descriptor arg_working_folder ("working-folder", "", "."); + const command_line::arg_descriptor arg_source_wallet ("source-wallet", ""); + const command_line::arg_descriptor arg_dest_wallet ("dest-wallet", ""); + const command_line::arg_descriptor arg_source_wallet_pass ("source-wallet-pass", ""); + const command_line::arg_descriptor arg_dest_wallet_pass ("dest-wallet-pass", ""); - const command_line::arg_descriptor arg_daemon_addr_a = {"daemon-addr-a", "", "127.0.0.1:8080"}; - const command_line::arg_descriptor arg_daemon_addr_b = {"daemon-addr-b", "", "127.0.0.1:8082"}; + const command_line::arg_descriptor arg_daemon_addr_a ("daemon-addr-a", "", "127.0.0.1:8080"); + const command_line::arg_descriptor arg_daemon_addr_b ("daemon-addr-b", "", "127.0.0.1:8082"); - const command_line::arg_descriptor arg_transfer_amount = {"transfer-amount", "", 60000000000000}; - const command_line::arg_descriptor arg_mix_in_factor = {"mix-in-factor", "", 10}; - const command_line::arg_descriptor arg_tx_count = {"tx-count", "", 100}; - const command_line::arg_descriptor arg_tx_per_second = {"tx-per-second", "", 20}; - const command_line::arg_descriptor arg_test_repeat_count = {"test-repeat-count", "", 1}; - const command_line::arg_descriptor arg_action = {"action", "", 0 }; - const command_line::arg_descriptor arg_max_tx_in_pool = { "max-tx-in-pool", "", 10000 }; - const command_line::arg_descriptor arg_data_dir = {"data-dir", "Specify data directory", "."}; - const command_line::arg_descriptor arg_wthreads = {"wthreads", "number of writing threads to run", 1}; - const command_line::arg_descriptor arg_rthreads = {"rthreads", "number of reading threads to run", 1}; - const command_line::arg_descriptor arg_blocks = {"blocks", "number of blocks to generate", 250}; - const command_line::arg_descriptor arg_generate_test_genesis_json = { "generate-test-genesis-json", "generates test genesis json, specify amount of accounts", 0, true }; - const command_line::arg_descriptor arg_deadlock_guard = { "test-deadlock-guard", "Do deadlock guard test", false, true }; - const command_line::arg_descriptor arg_difficulty_analysis = { "difficulty-analysis", "Do difficulty analysis", "", true }; - const command_line::arg_descriptor arg_test_plain_wallet = { "test-plainwallet", "Do testing of plain wallet interface", false, true }; - const command_line::arg_descriptor arg_crypto_tests = { "crypto-tests", "Run experimental crypto tests", "", true }; + const command_line::arg_descriptor arg_transfer_amount ("transfer-amount", "", 60000000000000); + const command_line::arg_descriptor arg_mix_in_factor ("mix-in-factor", "", 10); + const command_line::arg_descriptor arg_tx_count ("tx-count", "", 100); + const command_line::arg_descriptor arg_tx_per_second ("tx-per-second", "", 20); + const command_line::arg_descriptor arg_test_repeat_count ("test-repeat-count", "", 1); + const command_line::arg_descriptor arg_action ("action", "", 0 ); + const command_line::arg_descriptor arg_max_tx_in_pool ( "max-tx-in-pool", "", 10000 ); + const command_line::arg_descriptor arg_data_dir ("data-dir", "Specify data directory", "."); + const command_line::arg_descriptor arg_wthreads ("wthreads", "number of writing threads to run", 1); + const command_line::arg_descriptor arg_rthreads ("rthreads", "number of reading threads to run", 1); + const command_line::arg_descriptor arg_blocks ("blocks", "number of blocks to generate", 250); + const command_line::arg_descriptor arg_generate_test_genesis_json ( "generate-test-genesis-json", "generates test genesis json, specify amount of accounts"); + const command_line::arg_descriptor arg_deadlock_guard ( "test-deadlock-guard", "Do deadlock guard test"); + const command_line::arg_descriptor arg_difficulty_analysis ( "difficulty-analysis", "Do difficulty analysis"); + const command_line::arg_descriptor arg_test_plain_wallet ( "test-plainwallet", "Do testing of plain wallet interface"); + const command_line::arg_descriptor arg_crypto_tests ( "crypto-tests", "Run experimental crypto tests"); } From 631c1fa92133f181acbfd251c01f077fdd749bf2 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 21 Apr 2022 19:00:03 +0200 Subject: [PATCH 0173/1271] fixed minor issues related to arg_descriptor --- src/common/command_line.cpp | 6 +++--- src/common/command_line.h | 5 +++++ src/daemon/daemon.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp index c3a9b057..0474f9cd 100644 --- a/src/common/command_line.cpp +++ b/src/common/command_line.cpp @@ -35,9 +35,9 @@ namespace command_line const arg_descriptor arg_enable_offers_service ( "enable-offers-service", "Enables marketplace feature", false); const arg_descriptor arg_db_engine ( "db-engine", "Specify database engine for storage. May be \"lmdb\"(default) or \"mdbx\"", ARG_DB_ENGINE_LMDB ); - const arg_descriptor arg_no_predownload ( "no-predownload", "Do not pre-download blockchain database", ); - const arg_descriptor arg_force_predownload ( "force-predownload", "Pre-download blockchain database regardless of it's status", ); - const arg_descriptor arg_validate_predownload ( "validate-predownload", "Paranoid mode, re-validate each block from pre-downloaded database and rebuild own database", ); + const arg_descriptor arg_no_predownload ( "no-predownload", "Do not pre-download blockchain database"); + const arg_descriptor arg_force_predownload ( "force-predownload", "Pre-download blockchain database regardless of it's status"); + const arg_descriptor arg_validate_predownload ( "validate-predownload", "Paranoid mode, re-validate each block from pre-downloaded database and rebuild own database"); const arg_descriptor arg_predownload_link ( "predownload-link", "Override url for blockchain database pre-downloading"); const arg_descriptor arg_deeplink ( "deeplink-params", "Deeplink parameter, in that case app just forward params to running app"); diff --git a/src/common/command_line.h b/src/common/command_line.h index 3d9107dc..2c147d20 100644 --- a/src/common/command_line.h +++ b/src/common/command_line.h @@ -53,6 +53,11 @@ namespace command_line template struct arg_descriptor, false> { + arg_descriptor(const char* _name, const char* _description) : + name(_name), + description(_description) + {} + typedef std::vector value_type; const char* name; diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 889c1c55..de1e0a74 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -168,7 +168,7 @@ int main(int argc, char* argv[]) arg_market_disable.default_value = true; - arg_market_disable.not_use_default = false; + arg_market_disable.use_default = true; currency::core::init_options(desc_cmd_sett); currency::core_rpc_server::init_options(desc_cmd_sett); From f56c71c0f1da33d184f603b5fb88d8739548ba89 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Thu, 21 Apr 2022 20:57:46 +0200 Subject: [PATCH 0174/1271] Implemented #315 --- src/daemon/daemon.cpp | 6 +++--- src/wallet/view_iface.h | 2 ++ src/wallet/wallets_manager.cpp | 10 +++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index de1e0a74..43f1efe6 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -146,7 +146,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_cmd_only, command_line::arg_version); command_line::add_arg(desc_cmd_only, command_line::arg_os_version); // tools::get_default_data_dir() can't be called during static initialization - command_line::add_arg(desc_cmd_only, command_line::arg_data_dir, tools::get_default_data_dir()); + command_line::add_arg(desc_cmd_sett, command_line::arg_data_dir, tools::get_default_data_dir()); command_line::add_arg(desc_cmd_only, command_line::arg_stop_after_height); command_line::add_arg(desc_cmd_only, command_line::arg_config_file); command_line::add_arg(desc_cmd_only, command_line::arg_disable_upnp); @@ -154,8 +154,8 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_cmd_sett, command_line::arg_log_dir); command_line::add_arg(desc_cmd_sett, command_line::arg_log_level); command_line::add_arg(desc_cmd_sett, command_line::arg_console); - command_line::add_arg(desc_cmd_sett, command_line::arg_show_details); - command_line::add_arg(desc_cmd_sett, command_line::arg_show_rpc_autodoc); + command_line::add_arg(desc_cmd_only, command_line::arg_show_details); + command_line::add_arg(desc_cmd_only, command_line::arg_show_rpc_autodoc); command_line::add_arg(desc_cmd_sett, command_line::arg_disable_stop_if_time_out_of_sync); command_line::add_arg(desc_cmd_sett, command_line::arg_disable_stop_on_low_free_space); command_line::add_arg(desc_cmd_sett, command_line::arg_enable_offers_service); diff --git a/src/wallet/view_iface.h b/src/wallet/view_iface.h index edf78d14..3243242e 100644 --- a/src/wallet/view_iface.h +++ b/src/wallet/view_iface.h @@ -754,9 +754,11 @@ public: struct gui_options { bool use_debug_mode; + bool disable_price_fetch; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(use_debug_mode) + KV_SERIALIZE(disable_price_fetch) END_KV_SERIALIZE_MAP() }; diff --git a/src/wallet/wallets_manager.cpp b/src/wallet/wallets_manager.cpp index 298b338e..54a5cb26 100644 --- a/src/wallet/wallets_manager.cpp +++ b/src/wallet/wallets_manager.cpp @@ -60,6 +60,7 @@ const command_line::arg_descriptor arg_remote_node ( "remote-node" const command_line::arg_descriptor arg_enable_qt_logs ( "enable-qt-logs", "Forward Qt log messages into main log"); const command_line::arg_descriptor arg_disable_logs_init("disable-logs-init", "Disable log initialization in GUI"); const command_line::arg_descriptor arg_qt_dev_tools ( "qt-dev-tools", "Enable main web page inspection with Chromium DevTools, [,scale], e.g. \"horizontal,1.3\"", ""); +const command_line::arg_descriptor arg_disable_price_fetch("gui-disable-price-fetch", "Disable price fetching in UI(for privacy matter)"); wallets_manager::wallets_manager():m_pview(&m_view_stub), m_stop_singal_sent(false), @@ -186,6 +187,8 @@ bool wallets_manager::init_command_line(int argc, char* argv[], std::string& fai command_line::add_arg(desc_cmd_sett, command_line::arg_predownload_link); command_line::add_arg(desc_cmd_only, command_line::arg_deeplink); command_line::add_arg(desc_cmd_sett, command_line::arg_disable_ntp); + command_line::add_arg(desc_cmd_sett, arg_disable_price_fetch); + #ifndef MOBILE_WALLET_BUILD @@ -299,10 +302,15 @@ bool wallets_manager::init(view::i_view* pview_handler) { log_space::log_singletone::get_set_log_detalisation_level(true, command_line::get_arg(m_vm, command_line::arg_log_level)); } - if (command_line::has_arg(m_vm, arg_enable_gui_debug_mode)) + if (command_line::has_arg(m_vm, arg_enable_gui_debug_mode) && command_line::get_arg(m_vm, arg_enable_gui_debug_mode)) { m_ui_opt.use_debug_mode = true; } + if (command_line::has_arg(m_vm, arg_disable_price_fetch) && command_line::get_arg(m_vm, arg_disable_price_fetch)) + { + m_ui_opt.disable_price_fetch = true; + } + //set up logging options std::string log_dir; From 039077084a54d6bc62f026aa6d1ec62f66203b85 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 25 Apr 2022 13:45:43 +0200 Subject: [PATCH 0175/1271] mdbx moved as sources due to ban of it's repo --- .gitmodules | 3 --- contrib/db/libmdbx | 1 - 2 files changed, 4 deletions(-) delete mode 160000 contrib/db/libmdbx diff --git a/.gitmodules b/.gitmodules index e98d7700..e68cde5e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "contrib/miniupnp"] path = contrib/miniupnp url = https://github.com/miniupnp/miniupnp -[submodule "contrib/db/libmdbx"] - path = contrib/db/libmdbx - url = https://abf.io/erthink/libmdbx.git [submodule "src/gui/qt-daemon/layout"] path = src/gui/qt-daemon/layout url = https://github.com/hyle-team/zano_ui.git diff --git a/contrib/db/libmdbx b/contrib/db/libmdbx deleted file mode 160000 index b7ed6754..00000000 --- a/contrib/db/libmdbx +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b7ed67543fefb0878dba1c70dea2a81201041314 From a34b9ab200fddd94f961db7c91edeaa16f708c0b Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 25 Apr 2022 13:49:07 +0200 Subject: [PATCH 0176/1271] mdbx added as sources due to ban of it's repo --- contrib/db/libmdbx/.circleci/config.yml | 20 + contrib/db/libmdbx/.clang-format | 3 + contrib/db/libmdbx/.gitignore | 35 + contrib/db/libmdbx/.travis.yml | 61 + contrib/db/libmdbx/AUTHORS | 32 + contrib/db/libmdbx/CMakeLists.dist-minimal | 192 + contrib/db/libmdbx/CMakeLists.txt | 342 + contrib/db/libmdbx/COPYRIGHT | 22 + contrib/db/libmdbx/GNUmakefile | 362 + contrib/db/libmdbx/LICENSE | 47 + contrib/db/libmdbx/README.md | 591 + contrib/db/libmdbx/appveyor.yml | 99 + contrib/db/libmdbx/cmake/compiler.cmake | 666 + contrib/db/libmdbx/cmake/profile.cmake | 45 + contrib/db/libmdbx/cmake/utils.cmake | 183 + contrib/db/libmdbx/example/CMakeLists.txt | 6 + contrib/db/libmdbx/example/README.md | 1 + contrib/db/libmdbx/example/example-mdbx.c | 112 + contrib/db/libmdbx/example/sample-bdb.txt | 77 + contrib/db/libmdbx/mdbx.h | 3497 ++++ .../db/libmdbx/packages/rpm/CMakeLists.txt | 184 + contrib/db/libmdbx/packages/rpm/build.sh | 18 + contrib/db/libmdbx/packages/rpm/package.sh | 25 + contrib/db/libmdbx/src/CMakeLists.txt | 225 + contrib/db/libmdbx/src/alloy.c | 26 + contrib/db/libmdbx/src/elements/config.h.in | 56 + contrib/db/libmdbx/src/elements/core.c | 16709 ++++++++++++++++ contrib/db/libmdbx/src/elements/defs.h | 450 + contrib/db/libmdbx/src/elements/internals.h | 1367 ++ contrib/db/libmdbx/src/elements/lck-posix.c | 551 + contrib/db/libmdbx/src/elements/lck-windows.c | 777 + contrib/db/libmdbx/src/elements/ntdll.def | 1244 ++ contrib/db/libmdbx/src/elements/osal.c | 1899 ++ contrib/db/libmdbx/src/elements/osal.h | 959 + contrib/db/libmdbx/src/elements/version.c.in | 46 + contrib/db/libmdbx/src/man1/mdbx_chk.1 | 87 + contrib/db/libmdbx/src/man1/mdbx_copy.1 | 60 + contrib/db/libmdbx/src/man1/mdbx_dump.1 | 80 + contrib/db/libmdbx/src/man1/mdbx_load.1 | 89 + contrib/db/libmdbx/src/man1/mdbx_stat.1 | 69 + contrib/db/libmdbx/src/tools/CMakeLists.txt | 42 + contrib/db/libmdbx/src/tools/mdbx_chk.c | 1430 ++ contrib/db/libmdbx/src/tools/mdbx_copy.c | 130 + contrib/db/libmdbx/src/tools/mdbx_dump.c | 352 + contrib/db/libmdbx/src/tools/mdbx_load.c | 567 + contrib/db/libmdbx/src/tools/mdbx_stat.c | 436 + contrib/db/libmdbx/src/tools/wingetopt.c | 95 + contrib/db/libmdbx/src/tools/wingetopt.h | 30 + contrib/db/libmdbx/test/CMakeLists.txt | 53 + contrib/db/libmdbx/test/append.cc | 164 + contrib/db/libmdbx/test/base.h | 116 + contrib/db/libmdbx/test/cases.cc | 99 + contrib/db/libmdbx/test/chrono.cc | 136 + contrib/db/libmdbx/test/chrono.h | 99 + contrib/db/libmdbx/test/config.cc | 602 + contrib/db/libmdbx/test/config.h | 326 + contrib/db/libmdbx/test/copy.cc | 26 + contrib/db/libmdbx/test/darwin/LICENSE | 24 + contrib/db/libmdbx/test/darwin/README.md | 8 + .../db/libmdbx/test/darwin/pthread_barrier.c | 110 + .../db/libmdbx/test/darwin/pthread_barrier.h | 83 + contrib/db/libmdbx/test/dead.cc | 35 + contrib/db/libmdbx/test/hill.cc | 409 + contrib/db/libmdbx/test/jitter.cc | 59 + contrib/db/libmdbx/test/keygen.cc | 275 + contrib/db/libmdbx/test/keygen.h | 130 + contrib/db/libmdbx/test/log.cc | 371 + contrib/db/libmdbx/test/log.h | 104 + contrib/db/libmdbx/test/long_stochastic.sh | 220 + contrib/db/libmdbx/test/main.cc | 617 + contrib/db/libmdbx/test/nested.cc | 284 + contrib/db/libmdbx/test/osal-unix.cc | 368 + contrib/db/libmdbx/test/osal-windows.cc | 461 + contrib/db/libmdbx/test/osal.h | 49 + contrib/db/libmdbx/test/pcrf/CMakeLists.txt | 5 + contrib/db/libmdbx/test/pcrf/README.md | 2 + contrib/db/libmdbx/test/pcrf/pcrf_test.c | 413 + contrib/db/libmdbx/test/test.cc | 736 + contrib/db/libmdbx/test/test.h | 314 + contrib/db/libmdbx/test/try.cc | 20 + contrib/db/libmdbx/test/ttl.cc | 172 + contrib/db/libmdbx/test/utils.cc | 370 + contrib/db/libmdbx/test/utils.h | 362 + contrib/db/libmdbx/test/valgrind_suppress.txt | 24 + 84 files changed, 42042 insertions(+) create mode 100644 contrib/db/libmdbx/.circleci/config.yml create mode 100644 contrib/db/libmdbx/.clang-format create mode 100644 contrib/db/libmdbx/.gitignore create mode 100644 contrib/db/libmdbx/.travis.yml create mode 100644 contrib/db/libmdbx/AUTHORS create mode 100644 contrib/db/libmdbx/CMakeLists.dist-minimal create mode 100644 contrib/db/libmdbx/CMakeLists.txt create mode 100644 contrib/db/libmdbx/COPYRIGHT create mode 100644 contrib/db/libmdbx/GNUmakefile create mode 100644 contrib/db/libmdbx/LICENSE create mode 100644 contrib/db/libmdbx/README.md create mode 100644 contrib/db/libmdbx/appveyor.yml create mode 100644 contrib/db/libmdbx/cmake/compiler.cmake create mode 100644 contrib/db/libmdbx/cmake/profile.cmake create mode 100644 contrib/db/libmdbx/cmake/utils.cmake create mode 100644 contrib/db/libmdbx/example/CMakeLists.txt create mode 100644 contrib/db/libmdbx/example/README.md create mode 100644 contrib/db/libmdbx/example/example-mdbx.c create mode 100644 contrib/db/libmdbx/example/sample-bdb.txt create mode 100644 contrib/db/libmdbx/mdbx.h create mode 100644 contrib/db/libmdbx/packages/rpm/CMakeLists.txt create mode 100644 contrib/db/libmdbx/packages/rpm/build.sh create mode 100644 contrib/db/libmdbx/packages/rpm/package.sh create mode 100644 contrib/db/libmdbx/src/CMakeLists.txt create mode 100644 contrib/db/libmdbx/src/alloy.c create mode 100644 contrib/db/libmdbx/src/elements/config.h.in create mode 100644 contrib/db/libmdbx/src/elements/core.c create mode 100644 contrib/db/libmdbx/src/elements/defs.h create mode 100644 contrib/db/libmdbx/src/elements/internals.h create mode 100644 contrib/db/libmdbx/src/elements/lck-posix.c create mode 100644 contrib/db/libmdbx/src/elements/lck-windows.c create mode 100644 contrib/db/libmdbx/src/elements/ntdll.def create mode 100644 contrib/db/libmdbx/src/elements/osal.c create mode 100644 contrib/db/libmdbx/src/elements/osal.h create mode 100644 contrib/db/libmdbx/src/elements/version.c.in create mode 100644 contrib/db/libmdbx/src/man1/mdbx_chk.1 create mode 100644 contrib/db/libmdbx/src/man1/mdbx_copy.1 create mode 100644 contrib/db/libmdbx/src/man1/mdbx_dump.1 create mode 100644 contrib/db/libmdbx/src/man1/mdbx_load.1 create mode 100644 contrib/db/libmdbx/src/man1/mdbx_stat.1 create mode 100644 contrib/db/libmdbx/src/tools/CMakeLists.txt create mode 100644 contrib/db/libmdbx/src/tools/mdbx_chk.c create mode 100644 contrib/db/libmdbx/src/tools/mdbx_copy.c create mode 100644 contrib/db/libmdbx/src/tools/mdbx_dump.c create mode 100644 contrib/db/libmdbx/src/tools/mdbx_load.c create mode 100644 contrib/db/libmdbx/src/tools/mdbx_stat.c create mode 100644 contrib/db/libmdbx/src/tools/wingetopt.c create mode 100644 contrib/db/libmdbx/src/tools/wingetopt.h create mode 100644 contrib/db/libmdbx/test/CMakeLists.txt create mode 100644 contrib/db/libmdbx/test/append.cc create mode 100644 contrib/db/libmdbx/test/base.h create mode 100644 contrib/db/libmdbx/test/cases.cc create mode 100644 contrib/db/libmdbx/test/chrono.cc create mode 100644 contrib/db/libmdbx/test/chrono.h create mode 100644 contrib/db/libmdbx/test/config.cc create mode 100644 contrib/db/libmdbx/test/config.h create mode 100644 contrib/db/libmdbx/test/copy.cc create mode 100644 contrib/db/libmdbx/test/darwin/LICENSE create mode 100644 contrib/db/libmdbx/test/darwin/README.md create mode 100644 contrib/db/libmdbx/test/darwin/pthread_barrier.c create mode 100644 contrib/db/libmdbx/test/darwin/pthread_barrier.h create mode 100644 contrib/db/libmdbx/test/dead.cc create mode 100644 contrib/db/libmdbx/test/hill.cc create mode 100644 contrib/db/libmdbx/test/jitter.cc create mode 100644 contrib/db/libmdbx/test/keygen.cc create mode 100644 contrib/db/libmdbx/test/keygen.h create mode 100644 contrib/db/libmdbx/test/log.cc create mode 100644 contrib/db/libmdbx/test/log.h create mode 100644 contrib/db/libmdbx/test/long_stochastic.sh create mode 100644 contrib/db/libmdbx/test/main.cc create mode 100644 contrib/db/libmdbx/test/nested.cc create mode 100644 contrib/db/libmdbx/test/osal-unix.cc create mode 100644 contrib/db/libmdbx/test/osal-windows.cc create mode 100644 contrib/db/libmdbx/test/osal.h create mode 100644 contrib/db/libmdbx/test/pcrf/CMakeLists.txt create mode 100644 contrib/db/libmdbx/test/pcrf/README.md create mode 100644 contrib/db/libmdbx/test/pcrf/pcrf_test.c create mode 100644 contrib/db/libmdbx/test/test.cc create mode 100644 contrib/db/libmdbx/test/test.h create mode 100644 contrib/db/libmdbx/test/try.cc create mode 100644 contrib/db/libmdbx/test/ttl.cc create mode 100644 contrib/db/libmdbx/test/utils.cc create mode 100644 contrib/db/libmdbx/test/utils.h create mode 100644 contrib/db/libmdbx/test/valgrind_suppress.txt diff --git a/contrib/db/libmdbx/.circleci/config.yml b/contrib/db/libmdbx/.circleci/config.yml new file mode 100644 index 00000000..91e11a4b --- /dev/null +++ b/contrib/db/libmdbx/.circleci/config.yml @@ -0,0 +1,20 @@ +version: 2 +jobs: + build: + docker: + - image: circleci/buildpack-deps:artful + environment: + - TESTDB: /tmp/test.db + - TESTLOG: /tmp/test.log + steps: + - checkout + - run: make all + - run: ulimit -c unlimited && make check + - run: + command: | + mkdir -p /tmp/artifacts + mv -t /tmp/artifacts $TESTLOG $TESTDB core.* + when: on_fail + - store_artifacts: + path: /tmp/artifacts + destination: test-artifacts diff --git a/contrib/db/libmdbx/.clang-format b/contrib/db/libmdbx/.clang-format new file mode 100644 index 00000000..6c59ef3a --- /dev/null +++ b/contrib/db/libmdbx/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: LLVM +Standard: Cpp11 +ReflowComments: true diff --git a/contrib/db/libmdbx/.gitignore b/contrib/db/libmdbx/.gitignore new file mode 100644 index 00000000..0496b621 --- /dev/null +++ b/contrib/db/libmdbx/.gitignore @@ -0,0 +1,35 @@ +*.[ao] +*.bak +*.exe +*.gcda +*.gcno +*.gcov +*.lo +*.orig +*.rej +*.so +*[~#] +.idea +.le.ini +.vs/ +cmake-build-* +@* +core +mdbx_example +libmdbx.creator.user +mdbx_chk +mdbx_copy +mdbx_dump +mdbx_load +mdbx_stat +mdbx_test +test.log +test/tmp.db +test/tmp.db-lck +tmp.db +tmp.db-lck +valgrind.* +src/elements/version.c +src/elements/config.h +dist/ +*.tar* diff --git a/contrib/db/libmdbx/.travis.yml b/contrib/db/libmdbx/.travis.yml new file mode 100644 index 00000000..7b468ad7 --- /dev/null +++ b/contrib/db/libmdbx/.travis.yml @@ -0,0 +1,61 @@ +language: c cpp + +matrix: + include: + - os: linux + dist: precise + env: CC=cc CXX=c++ + - os: linux + dist: trusty + compiler: clang + env: CC=clang CXX=clang++ + - os: linux + dist: xenial + compiler: gcc + env: CC=gcc CXX=g++ + - os: linux + dist: bionic + compiler: clang + env: CC=clang CXX=clang++ + - os: osx + osx_image: xcode11 + env: CC=cc CXX=c++ + - os: osx + osx_image: xcode9.4 + env: CC=cc CXX=c++ + +script: > + if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then + git fetch --unshallow --tags --prune && + git submodule foreach --recursive git fetch --unshallow --tags --prune && + (if which clang-format-6.0 > /dev/null && make reformat && [[ -n $(git diff) ]]; + then + echo "You must run 'make reformat' before submitting a pull request"; + echo ""; + git diff; + exit -1; + fi) && + make --keep-going all && MALLOC_CHECK_=7 MALLOC_PERTURB_=42 make --keep-going check + else + [ ! -s cov-int/scm_log.txt ] || cat cov-int/scm_log.txt; + fi && sleep 3 + +env: + global: + - secure: "M+W+heGGyRQJoBq2W0uqWVrpL4KBXmL0MFL7FSs7f9vmAaDyEgziUXeZRj3GOKzW4kTef3LpIeiu9SmvqSMoQivGGiomZShqPVl045o/OUgRCAT7Al1RLzEZ0efSHpIPf0PZ6byEf6GR2ML76OfuL6JxTVdnz8iVyO2sgLE1HbX1VeB+wgd/jfMeOBhCCXskfK6MLyZihfMYsiYZYSaV98ZDhDLSlzuuRIgzb0bMi8aL6AErs0WLW0NelRBeHkKPYfAUc85pdQHscgrJw6Rh/zT6+8BQ/q5f4IgWhiu4xoRg3Ngl7SNoedRQh93ADM3UG2iGl6HDFpVORaXcFWKAtuYY+kHQ0HB84BRYpQmeBuXNpltsfxQ3d1Q3u0RlE45zRvmr2+X1mFnkcNUAWISLPbsOUlriDQM8irGwRpho77/uYnRC00bJsHW//s6+uPf9zrAw1nI4f0y3PAWukGF/xs6HAI3FZPsuSSnx18Tj3Opgbc9Spop+V3hkhdiJoPGpNKTkFX4ZRXfkPgoRVJmtp4PpbpH0Ps/mCriKjMEfGGi0HcVCi0pEGLXiecdqJ5KPg5+22zNycEujQBJcNTKd9shN+R3glrbmhAxTEzGdGwxXXJ2ybwJ2PWJLMYZ7g98nLyX+uQPaA3BlsbYJHNeS5283/9pJsd9DzfHKsN2nFSc=" + +before_install: + - echo -n | openssl s_client -connect scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- + - ${CC} --version + - ${CXX} --version + +addons: + coverity_scan: + project: + name: "ReOpen/libmdbx" + version: 0.1 + description: "Build submitted via Travis CI" + notification_email: leo@yuriev.ru + build_command_prepend: "git fetch --unshallow --tags --prune && make dist" + build_command: "make MDBX_OPTIONS=-DMDBX_DEBUG=2 -C dist all" + branch_pattern: coverity_scan diff --git a/contrib/db/libmdbx/AUTHORS b/contrib/db/libmdbx/AUTHORS new file mode 100644 index 00000000..10910e57 --- /dev/null +++ b/contrib/db/libmdbx/AUTHORS @@ -0,0 +1,32 @@ +Contributors +============ + +Alexey Naumov +Chris Mikkelson +Claude Brisson +David Barbour +David Wilson +dreamsxin +Hallvard Furuseth , +Heiko Becker +Howard Chu , +Ignacio Casal Quinteiro +James Rouzier +Jean-Christophe DUBOIS +John Hewson +Klaus Malorny +Kurt Zeilenga +Leonid Yuriev , +Lorenz Bauer +Luke Yeager +Martin Hedenfalk +Ondrej Kuznik +Orivej Desh +Oskari Timperi +Pavel Medvedev +Philipp Storz +Quanah Gibson-Mount +Salvador Ortiz +Sebastien Launay +Vladimir Romanov +Zano Foundation diff --git a/contrib/db/libmdbx/CMakeLists.dist-minimal b/contrib/db/libmdbx/CMakeLists.dist-minimal new file mode 100644 index 00000000..1f138af6 --- /dev/null +++ b/contrib/db/libmdbx/CMakeLists.dist-minimal @@ -0,0 +1,192 @@ +## +## This is the minimal template for CMakeList.txt which could be used +## to build libmdbx from the "amalgamated form" of libmdbx's source code. +## +## The amalgamated form is intended to embedding libmdbx in other projects +## in cases when using as git-submodule is not acceptable or inconveniently. +## +## The amalgamated form could be generated from full git repository +## on Linux just by `make dist`. +## + +## +## Copyright 2019 Leonid Yuriev +## and other libmdbx authors: please see AUTHORS file. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +## + +## +## libmdbx = { Revised and extended descendant of Symas LMDB. } +## Please see README.md at https://github.com/leo-yuriev/libmdbx +## +## Libmdbx is superior to LMDB in terms of features and reliability, +## not inferior in performance. libmdbx works on Linux, FreeBSD, MacOS X +## and other systems compliant with POSIX.1-2008, but also support Windows +## as a complementary platform. +## +## The next version is under active non-public development and will be +## released as MithrilDB and libmithrildb for libraries & packages. +## Admittedly mythical Mithril is resembling silver but being stronger and +## lighter than steel. Therefore MithrilDB is rightly relevant name. +## +## MithrilDB will be radically different from libmdbx by the new database +## format and API based on C++17, as well as the Apache 2.0 License. +## The goal of this revolution is to provide a clearer and robust API, +## add more features and new valuable properties of database. +## +## The Future will (be) Positive. Всё будет хорошо. +## + +cmake_minimum_required(VERSION 3.8.2) +cmake_policy(PUSH) +cmake_policy(VERSION 3.8.2) +if(NOT CMAKE_VERSION VERSION_LESS 3.9) + cmake_policy(SET CMP0069 NEW) +endif() +if(NOT CMAKE_VERSION VERSION_LESS 3.12) + cmake_policy(SET CMP0075 NEW) +endif() +if(NOT CMAKE_VERSION VERSION_LESS 3.13) + cmake_policy(SET CMP0077 NEW) +endif() + +if(DEFINED PROJECT_NAME) + set(SUBPROJECT ON) + set(NOT_SUBPROJECT OFF) +else() + set(SUBPROJECT OFF) + set(NOT_SUBPROJECT ON) + project(libmdbx C CXX) +endif() + +find_package(Threads REQUIRED) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() + +list(FIND CMAKE_C_COMPILE_FEATURES c_std_11 HAS_C11) +if(NOT HAS_C11 LESS 0) + set(MDBX_C_STANDARD 11) +else() + set(MDBX_C_STANDARD 99) +endif() +message(STATUS "Use C${MDBX_C_STANDARD} for libmdbx") + +# not supported by this (minimal) script +add_definitions(-DMDBX_AVOID_CRT=0) + +# provide build timestamp +string(TIMESTAMP MDBX_BUILD_TIMESTAMP UTC) +add_definitions(-DMDBX_BUILD_TIMESTAMP="${MDBX_BUILD_TIMESTAMP}") + +# provide compiler info +execute_process(COMMAND sh -c "${CMAKE_C_COMPILER} --version | head -1" + OUTPUT_VARIABLE MDBX_BUILD_COMPILER + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE rc) +if(rc OR NOT MDBX_BUILD_COMPILER) + string(STRIP "${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}" MDBX_BUILD_COMPILER) +endif() +add_definitions(-DMDBX_BUILD_COMPILER="${MDBX_BUILD_COMPILER}") + +# provide cpu/arch-system pair +if(CMAKE_C_COMPILER_TARGET) + set(MDBX_BUILD_TARGET "${CMAKE_C_COMPILER_TARGET}") +elseif(CMAKE_C_PLATFORM_ID AND NOT CMAKE_C_PLATFORM_ID STREQUAL CMAKE_SYSTEM_NAME) + string(STRIP "${CMAKE_C_PLATFORM_ID}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +elseif(CMAKE_LIBRARY_ARCHITECTURE) + string(STRIP "${CMAKE_LIBRARY_ARCHITECTURE}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +elseif(CMAKE_GENERATOR_PLATFORM AND NOT CMAKE_C_PLATFORM_ID STREQUAL CMAKE_SYSTEM_NAME) + string(STRIP "${CMAKE_GENERATOR_PLATFORM}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +elseif(CMAKE_SYSTEM_ARCH) + string(STRIP "${CMAKE_SYSTEM_ARCH}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +else() + string(STRIP "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +endif() +add_definitions(-DMDBX_BUILD_TARGET="${MDBX_BUILD_TARGET}") + +# provide build target-config +if(CMAKE_CONFIGURATION_TYPES) + add_definitions(-DMDBX_BUILD_CONFIG="$") +else() + add_definitions(-DMDBX_BUILD_CONFIG="${CMAKE_BUILD_TYPE}") +endif() + +# provide build cflags +set(MDBX_BUILD_FLAGS "") +list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_FLAGS}) +list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_DEFINES}) +if(CMAKE_CONFIGURATION_TYPES) + add_definitions(-DMDBX_BUILD_FLAGS_CONFIG="$<$:${CMAKE_C_FLAGS_DEBUG} ${CMAKE_C_DEFINES_DEBUG}>$<$:${CMAKE_C_FLAGS_RELEASE} ${CMAKE_C_DEFINES_RELEASE}>$<$:${CMAKE_C_FLAGS_RELWITHDEBINFO} ${CMAKE_C_DEFINES_RELWITHDEBINFO}>$<$:${CMAKE_C_FLAGS_MINSIZEREL} ${CMAKE_C_DEFINES_MINSIZEREL}>") +else() + string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPERCASE) + list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UPPERCASE}}) + list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_DEFINES_${CMAKE_BUILD_TYPE_UPPERCASE}}) +endif() +list(REMOVE_DUPLICATES MDBX_BUILD_FLAGS) +string(REPLACE ";" " " MDBX_BUILD_FLAGS "${MDBX_BUILD_FLAGS}") +add_definitions(-DMDBX_BUILD_FLAGS="${MDBX_BUILD_FLAGS}") + +# shared library +if(NOT DEFINED MDBX_BUILD_SHARED_LIBRARY) + if(DEFINED BUILD_SHARED_LIBS) + option(MDBX_BUILD_SHARED_LIBRARY "Build libmdbx as shared library (DLL)" ${BUILD_SHARED_LIBS}) + else() + option(MDBX_BUILD_SHARED_LIBRARY "Build libmdbx as shared library (DLL)" ON) + endif() +endif() +if(MDBX_BUILD_SHARED_LIBRARY) + add_library(mdbx SHARED mdbx.c mdbx.h) + set_target_properties(mdbx PROPERTIES + C_STANDARD ${MDBX_C_STANDARD} C_STANDARD_REQUIRED ON + PUBLIC_HEADER mdbx.h) + target_compile_definitions(mdbx PRIVATE LIBMDBX_EXPORTS INTERFACE LIBMDBX_IMPORTS) + if(DEFINED INTERPROCEDURAL_OPTIMIZATION) + set_target_properties(mdbx PROPERTIES + INTERPROCEDURAL_OPTIMIZATION $) + endif() + target_link_libraries(mdbx PRIVATE ${CMAKE_THREAD_LIBS_INIT}) + if(WIN32) + target_link_libraries(mdbx PRIVATE ntdll.lib) + endif() +endif() + +# static library used for tools, to avoid rpath/dll-path troubles +add_library(mdbx-static STATIC EXCLUDE_FROM_ALL mdbx.c mdbx.h) +set_target_properties(mdbx-static PROPERTIES + C_STANDARD ${MDBX_C_STANDARD} C_STANDARD_REQUIRED ON + PUBLIC_HEADER mdbx.h) +target_link_libraries(mdbx-static INTERFACE ${CMAKE_THREAD_LIBS_INIT}) +if(DEFINED INTERPROCEDURAL_OPTIMIZATION) + set_target_properties(mdbx-static PROPERTIES + INTERPROCEDURAL_OPTIMIZATION $) +endif() +if(WIN32) + target_link_libraries(mdbx-static INTERFACE ntdll.lib) +endif() + +# mdbx-tools +foreach(TOOL mdbx_chk mdbx_copy mdbx_stat mdbx_dump mdbx_load) + add_executable(${TOOL} ${TOOL}.c) + set_target_properties(${TOOL} PROPERTIES + C_STANDARD ${MDBX_C_STANDARD} C_STANDARD_REQUIRED ON) + if(DEFINED INTERPROCEDURAL_OPTIMIZATION) + set_target_properties(${TOOL} PROPERTIES + INTERPROCEDURAL_OPTIMIZATION $) + endif() + target_link_libraries(${TOOL} mdbx-static) +endforeach() + +cmake_policy(POP) diff --git a/contrib/db/libmdbx/CMakeLists.txt b/contrib/db/libmdbx/CMakeLists.txt new file mode 100644 index 00000000..75e9b3b0 --- /dev/null +++ b/contrib/db/libmdbx/CMakeLists.txt @@ -0,0 +1,342 @@ +## +## Copyright 2019 Leonid Yuriev +## and other libmdbx authors: please see AUTHORS file. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +## + +## +## libmdbx = { Revised and extended descendant of Symas LMDB. } +## Please see README.md at https://github.com/leo-yuriev/libmdbx +## +## Libmdbx is superior to LMDB in terms of features and reliability, +## not inferior in performance. libmdbx works on Linux, FreeBSD, MacOS X +## and other systems compliant with POSIX.1-2008, but also support Windows +## as a complementary platform. +## +## The next version is under active non-public development and will be +## released as MithrilDB and libmithrildb for libraries & packages. +## Admittedly mythical Mithril is resembling silver but being stronger and +## lighter than steel. Therefore MithrilDB is rightly relevant name. +## +## MithrilDB will be radically different from libmdbx by the new database +## format and API based on C++17, as well as the Apache 2.0 License. +## The goal of this revolution is to provide a clearer and robust API, +## add more features and new valuable properties of database. +## +## The Future will (be) Positive. Всё будет хорошо. +## + +cmake_minimum_required(VERSION 3.8.2) +cmake_policy(PUSH) +cmake_policy(VERSION 3.8.2) +if(NOT CMAKE_VERSION VERSION_LESS 3.13) + cmake_policy(SET CMP0077 NEW) +endif() +if(NOT CMAKE_VERSION VERSION_LESS 3.12) + cmake_policy(SET CMP0075 NEW) +endif() +if(NOT CMAKE_VERSION VERSION_LESS 3.9) + cmake_policy(SET CMP0069 NEW) + include(CheckIPOSupported) + check_ipo_supported(RESULT CMAKE_INTERPROCEDURAL_OPTIMIZATION_AVAILABLE) +else() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_AVAILABLE FALSE) +endif() + +if(DEFINED PROJECT_NAME) + set(SUBPROJECT ON) + set(NOT_SUBPROJECT OFF) + if(NOT DEFINED BUILD_TESTING) + set(BUILD_TESTING OFF) + endif() +else() + set(SUBPROJECT OFF) + set(NOT_SUBPROJECT ON) + project(libmdbx C CXX) + if(NOT DEFINED BUILD_TESTING) + set(BUILD_TESTING ON) + endif() +endif() + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE MinSizeRel CACHE STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() + +macro(add_mdbx_option NAME DESCRIPTION DEFAULT) + list(APPEND MDBX_BUILD_OPTIONS ${NAME}) + if(NOT ${DEFAULT} STREQUAL "AUTO") + option(${NAME} "${DESCRIPTION}" ${DEFAULT}) + endif() +endmacro() + +# only for compatibility testing +# set(CMAKE_CXX_STANDARD 14) + +if(NOT "$ENV{TEAMCITY_PROCESS_FLOW_ID}" STREQUAL "") + set(CI TEAMCITY) + message(STATUS "TeamCity CI") +elseif(NOT "$ENV{TRAVIS}" STREQUAL "") + set(CI TRAVIS) + message(STATUS "Travis CI") +elseif(NOT "$ENV{CIRCLECI}" STREQUAL "") + set(CI CIRCLE) + message(STATUS "Circle CI") +elseif(NOT "$ENV{APPVEYOR}" STREQUAL "") + set(CI APPVEYOR) + message(STATUS "AppVeyor CI") +elseif(NOT "$ENV{CI}" STREQUAL "") + set(CI "$ENV{CI}") + message(STATUS "Other CI (${CI})") +else() + message(STATUS "Assume No any CI environment") + unset(CI) +endif() + +# output all mdbx-related targets in single directory +if(NOT DEFINED MDBX_OUTPUT_DIR) + set(MDBX_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) +endif() +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${MDBX_OUTPUT_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${MDBX_OUTPUT_DIR}) +set(CMAKE_PDB_OUTPUT_DIRECTORY ${MDBX_OUTPUT_DIR}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${MDBX_OUTPUT_DIR}) + +include(CheckLibraryExists) +include(CheckIncludeFiles) +include(CheckCCompilerFlag) +include(CheckSymbolExists) +include(CheckCSourceRuns) +include(CheckCXXSourceRuns) +include(CheckCSourceCompiles) +include(CheckCXXSourceCompiles) +include(TestBigEndian) +include(CheckFunctionExists) +include(FindPackageMessage) +include(CheckStructHasMember) +include(CMakeDependentOption) +include(GNUInstallDirs) + +if(CMAKE_C_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION LESS 1900) + message(SEND_ERROR "MSVC compiler ${MSVC_VERSION} is too old for building MDBX." + " At least 'Microsoft Visual Studio 2015' is required.") +endif() + +# Set default build type to Release. This is to ease a User's life. +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release CACHE STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + FORCE) +endif() +string(TOUPPER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE_UPPERCASE) + +include(cmake/utils.cmake) +include(cmake/compiler.cmake) +include(cmake/profile.cmake) + +find_program(ECHO echo) +find_program(CAT cat) +find_program(GIT git) +find_program(LD ld) + +# CHECK_INCLUDE_FILES(unistd.h HAVE_UNISTD_H) +# CHECK_INCLUDE_FILES(sys/uio.h HAVE_SYS_UIO_H) +# CHECK_INCLUDE_FILES(sys/stat.h HAVE_SYS_STAT_H) + +CHECK_FUNCTION_EXISTS(pow NOT_NEED_LIBM) +if(NOT_NEED_LIBM) + set(LIB_MATH "") +else() + set(CMAKE_REQUIRED_LIBRARIES m) + CHECK_FUNCTION_EXISTS(pow HAVE_LIBM) + if(HAVE_LIBM) + set(LIB_MATH m) + else() + message(FATAL_ERROR "No libm found for math support") + endif() +endif() + +find_package(Threads REQUIRED) + +if(SUBPROJECT) + if(NOT DEFINED BUILD_SHARED_LIBS) + option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)" OFF) + endif() + if(NOT DEFINED CMAKE_POSITION_INDEPENDENT_CODE) + option(CMAKE_POSITION_INDEPENDENT_CODE "Generate position independed (PIC)" ON) + endif() +else() + option(BUILD_SHARED_LIBS "Build shared libraries (DLLs)" ON) + option(CMAKE_POSITION_INDEPENDENT_CODE "Generate position independed (PIC)" ON) + if (CC_HAS_ARCH_NATIVE) + option(BUILD_FOR_NATIVE_CPU "Generate code for the compiling machine CPU" OFF) + endif() + + if(CMAKE_CONFIGURATION_TYPES OR NOT CMAKE_BUILD_TYPE_UPPERCASE STREQUAL "DEBUG") + set(INTERPROCEDURAL_OPTIMIZATION_DEFAULT ON) + else() + set(INTERPROCEDURAL_OPTIMIZATION_DEFAULT OFF) + endif() + + if(CMAKE_INTERPROCEDURAL_OPTIMIZATION_AVAILABLE + OR GCC_LTO_AVAILABLE OR MSVC_LTO_AVAILABLE OR CLANG_LTO_AVAILABLE) + option(INTERPROCEDURAL_OPTIMIZATION "Enable interprocedural/LTO optimization" ${INTERPROCEDURAL_OPTIMIZATION_DEFAULT}) + endif() + + if(INTERPROCEDURAL_OPTIMIZATION) + if(GCC_LTO_AVAILABLE) + set(LTO_ENABLED TRUE) + set(CMAKE_AR ${CMAKE_GCC_AR} CACHE PATH "Path to ar program with LTO-plugin" FORCE) + set(CMAKE_NM ${CMAKE_GCC_NM} CACHE PATH "Path to nm program with LTO-plugin" FORCE) + set(CMAKE_RANLIB ${CMAKE_GCC_RANLIB} CACHE PATH "Path to ranlib program with LTO-plugin" FORCE) + message(STATUS "MDBX indulge Link-Time Optimization by GCC") + elseif(CLANG_LTO_AVAILABLE) + set(LTO_ENABLED TRUE) + set(CMAKE_AR ${CMAKE_CLANG_AR} CACHE PATH "Path to ar program with LTO-plugin" FORCE) + set(CMAKE_NM ${CMAKE_CLANG_NM} CACHE PATH "Path to nm program with LTO-plugin" FORCE) + set(CMAKE_RANLIB ${CMAKE_CLANG_RANLIB} CACHE PATH "Path to ranlib program with LTO-plugin" FORCE) + message(STATUS "MDBX indulge Link-Time Optimization by CLANG") + elseif(MSVC_LTO_AVAILABLE) + set(LTO_ENABLED TRUE) + message(STATUS "MDBX indulge Link-Time Optimization by MSVC") + elseif(CMAKE_INTERPROCEDURAL_OPTIMIZATION_AVAILABLE) + message(STATUS "MDBX indulge Interprocedural Optimization by CMake") + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) + set(LTO_ENABLED TRUE) + else() + message(WARNING "Unable to engage interprocedural/LTO optimization.") + endif() + else() + set(CMAKE_INTERPROCEDURAL_OPTIMIZATION FALSE) + set(LTO_ENABLED FALSE) + endif() + + find_program(VALGRIND valgrind) + if(VALGRIND) + # LY: cmake is ugly and nasty. + # - therefore memcheck-options should be defined before including ctest; + # - otherwise ctest may ignore it. + set(MEMORYCHECK_SUPPRESSIONS_FILE + "${PROJECT_SOURCE_DIR}/test/valgrind_suppress.txt" + CACHE FILEPATH "Suppressions file for Valgrind" FORCE) + set(MEMORYCHECK_COMMAND_OPTIONS + "--trace-children=yes --leak-check=full --track-origins=yes --error-exitcode=42 --error-markers=@ --errors-for-leak-kinds=definite --fair-sched=yes --suppressions=${MEMORYCHECK_SUPPRESSIONS_FILE}" + CACHE STRING "Valgrind options" FORCE) + set(VALGRIND_COMMAND_OPTIONS "${MEMORYCHECK_COMMAND_OPTIONS}" CACHE STRING "Valgrind options" FORCE) + endif() + + # + # Enable 'make tags' target. + find_program(CTAGS ctags) + if(CTAGS) + add_custom_target(tags COMMAND ${CTAGS} -R -f tags + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + add_custom_target(ctags DEPENDS tags) + endif(CTAGS) + + # + # Enable 'make reformat' target. + find_program(CLANG_FORMAT + NAMES clang-format-6.0 clang-format-5.0 clang-format-4.0 + clang-format-3.9 clang-format-3.8 clang-format-3.7 clang-format) + if(CLANG_FORMAT AND UNIX) + add_custom_target(reformat + VERBATIM + COMMAND + git ls-files | + grep -E \\.\(c|cxx|cc|cpp|h|hxx|hpp\)\(\\.in\)?\$ | + xargs ${CLANG_FORMAT} -i --style=file + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + endif() + + if(NOT "${PROJECT_BINARY_DIR}" STREQUAL "${PROJECT_SOURCE_DIR}") + add_custom_target(distclean) + add_custom_command(TARGET distclean + COMMAND ${CMAKE_COMMAND} -E remove_directory "${PROJECT_BINARY_DIR}" + COMMENT "Removing the build directory and its content") + elseif(IS_DIRECTORY .git AND GIT) + add_custom_target(distclean) + add_custom_command(TARGET distclean + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMAND ${GIT} submodule foreach --recursive git clean -f -X -d + COMMAND ${GIT} clean -f -X -d + COMMENT "Removing all build files from the source directory") + endif() + + setup_compile_flags() +endif(SUBPROJECT) + +list(FIND CMAKE_C_COMPILE_FEATURES c_std_11 HAS_C11) +if(NOT HAS_C11 LESS 0) + set(MDBX_C_STANDARD 11) +else() + set(MDBX_C_STANDARD 99) +endif() +message(STATUS "Use C${MDBX_C_STANDARD} for libmdbx") + +############################################################################## +############################################################################## +# +# #### ##### ##### # #### # # #### +# # # # # # # # # ## # # +# # # # # # # # # # # # #### +# # # ##### # # # # # # # # +# # # # # # # # # ## # # +# #### # # # #### # # #### +# + +set(MDBX_BUILD_OPTIONS ENABLE_ASAN MDBX_USE_VALGRIND ENABLE_GPROF ENABLE_GCOV) +add_mdbx_option(MDBX_BUILD_SHARED_LIBRARY "Build libmdbx as shared library (DLL)" ${BUILD_SHARED_LIBS}) +add_mdbx_option(MDBX_ALLOY_BUILD "Build MDBX library as single object file" ON) +add_mdbx_option(MDBX_TXN_CHECKOWNER "Checking transaction matches the calling thread inside libmdbx's API" ON) +add_mdbx_option(MDBX_TXN_CHECKPID "Paranoid checking PID inside libmdbx's API" AUTO) +mark_as_advanced(MDBX_TXN_CHECKPID) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + add_mdbx_option(MDBX_DISABLE_GNU_SOURCE "Don't use nonstandard GNU/Linux extension functions" OFF) + mark_as_advanced(MDBX_DISABLE_GNU_SOURCE) +endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + add_mdbx_option(MDBX_OSX_SPEED_INSTEADOF_DURABILITY "Disable use fcntl(F_FULLFSYNC) in favor of speed" OFF) + mark_as_advanced(MDBX_OSX_SPEED_INSTEADOF_DURABILITY) +endif() +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + add_mdbx_option(MDBX_AVOID_CRT "Avoid dependence from MSVC CRT" ${NOT_SUBPROJECT}) + if(NOT MDBX_BUILD_SHARED_LIBRARY) + add_mdbx_option(MDBX_CONFIG_MANUAL_TLS_CALLBACK + "Provide mdbx_dll_handler() for manual initialization" OFF) + mark_as_advanced(MDBX_CONFIG_MANUAL_TLS_CALLBACK) + endif() +else() + add_mdbx_option(MDBX_USE_ROBUST "Use POSIX.1-2008 robust mutexes" AUTO) + mark_as_advanced(MDBX_USE_ROBUST) + add_mdbx_option(MDBX_USE_OFDLOCKS "Use Open file description locks (aka OFD locks, non-POSIX)" AUTO) + mark_as_advanced(MDBX_USE_OFDLOCKS) +endif() +option(MDBX_ENABLE_TESTS "Build MDBX tests." ${BUILD_TESTING}) + +################################################################################ +################################################################################ + +add_subdirectory(src) +if(MDBX_ENABLE_TESTS) + add_subdirectory(test) +endif() + +set(PACKAGE "libmdbx") +set(CPACK_PACKAGE_VERSION_MAJOR ${MDBX_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${MDBX_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${MDBX_VERSION_RELEASE}) +set(CPACK_PACKAGE_VERSION_COMMIT ${MDBX_VERSION_REVISION}) +set(PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}.${CPACK_PACKAGE_VERSION_COMMIT}") +message(STATUS "libmdbx package version is ${PACKAGE_VERSION}") + +cmake_policy(POP) diff --git a/contrib/db/libmdbx/COPYRIGHT b/contrib/db/libmdbx/COPYRIGHT new file mode 100644 index 00000000..46e09610 --- /dev/null +++ b/contrib/db/libmdbx/COPYRIGHT @@ -0,0 +1,22 @@ +Copyright 2015-2019 Leonid Yuriev . +Copyright 2011-2015 Howard Chu, Symas Corp. +Copyright 2015,2016 Peter-Service R&D LLC. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted only as authorized by the OpenLDAP +Public License. + +A copy of this license is available in the file LICENSE in the +top-level directory of the distribution or, alternatively, at +. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Individual files and/or contributed packages may be copyright by +other parties and/or subject to additional restrictions. + +This work also contains materials derived from public sources. + +Additional information about OpenLDAP can be obtained at +. diff --git a/contrib/db/libmdbx/GNUmakefile b/contrib/db/libmdbx/GNUmakefile new file mode 100644 index 00000000..f6d0edf7 --- /dev/null +++ b/contrib/db/libmdbx/GNUmakefile @@ -0,0 +1,362 @@ +# This makefile is for GNU Make, and nowadays provided +# just for compatibility and preservation of traditions. +# Please use CMake in case of any difficulties or problems. +# +# Preprocessor macros (for MDBX_OPTIONS) of interest... +# Note that the defaults should already be correct for most platforms; +# you should not need to change any of these. Read their descriptions +# in README and source code if you do. There may be other macros of interest. +SHELL := /bin/bash + +# install sandbox +SANDBOX ?= + +# install prefixes (inside sandbox) +prefix ?= /usr/local +mandir ?= $(prefix)/man + +# lib/bin suffix for multiarch/biarch, e.g. '.x86_64' +suffix ?= + +CC ?= gcc +LD ?= ld +MDBX_OPTIONS ?= -DNDEBUG=1 +CFLAGS ?= -Os -g3 -Wall -Werror -Wextra -Wpedantic -ffunction-sections -fPIC -fvisibility=hidden -std=gnu11 -pthread -Wno-tautological-compare + +# LY: '--no-as-needed,-lrt' for ability to built with modern glibc, but then run with the old +LDFLAGS ?= $(shell $(LD) --help 2>/dev/null | grep -q -- --gc-sections && echo '-Wl,--gc-sections,-z,relro,-O1')$(shell $(LD) --help 2>/dev/null | grep -q -- -dead_strip && echo '-Wl,-dead_strip') +EXE_LDFLAGS ?= -pthread + +################################################################################ + +UNAME := $(shell uname -s 2>/dev/null || echo Unknown) +define uname2sosuffix + case "$(UNAME)" in + Darwin*|Mach*) echo dylib;; + CYGWIN*|MINGW*|MSYS*|Windows*) echo dll;; + *) echo so;; + esac +endef +SO_SUFFIX := $(shell $(uname2sosuffix)) + +HEADERS := mdbx.h +LIBRARIES := libmdbx.a libmdbx.$(SO_SUFFIX) +TOOLS := mdbx_stat mdbx_copy mdbx_dump mdbx_load mdbx_chk +MANPAGES := mdbx_stat.1 mdbx_copy.1 mdbx_dump.1 mdbx_load.1 mdbx_chk.1 + +.PHONY: mdbx all install clean test dist check + +all: $(LIBRARIES) $(TOOLS) + +mdbx: libmdbx.a libmdbx.$(SO_SUFFIX) + +tools: $(TOOLS) + +strip: all + strip libmdbx.$(SO_SUFFIX) $(TOOLS) + +clean: + rm -rf $(TOOLS) mdbx_test @* *.[ao] *.[ls]o *~ tmp.db/* \ + *.gcov *.log *.err src/*.o test/*.o mdbx_example dist \ + config.h src/elements/config.h src/elements/version.c *.tar* + +libmdbx.a: mdbx-static.o + $(AR) rs $@ $? + +libmdbx.$(SO_SUFFIX): mdbx-dylib.o + $(CC) $(CFLAGS) $^ -pthread -shared $(LDFLAGS) -o $@ + +#> dist-cutoff-begin +ifeq ($(wildcard mdbx.c),mdbx.c) +#< dist-cutoff-end + +################################################################################ +# Amalgamated source code, i.e. distributed after `make dists` +MAN_SRCDIR := man1/ + +config.h: mdbx.c $(lastword $(MAKEFILE_LIST)) + (echo '#define MDBX_BUILD_TIMESTAMP "$(shell date +%Y-%m-%dT%H:%M:%S%z)"' \ + && echo '#define MDBX_BUILD_FLAGS "$(CFLAGS) $(LDFLAGS)"' \ + && echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"' \ + && echo '#define MDBX_BUILD_TARGET "$(shell set -o pipefail; LC_ALL=C $(CC) -v 2>&1 | grep -i '^Target:' | cut -d ' ' -f 2- || echo 'Please use GCC or CLANG compatible compiler')"' \ + ) > $@ + +mdbx-dylib.o: config.h mdbx.c $(lastword $(MAKEFILE_LIST)) + $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c mdbx.c -o $@ + +mdbx-static.o: config.h mdbx.c $(lastword $(MAKEFILE_LIST)) + $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c mdbx.c -o $@ + +mdbx_%: mdbx_%.c libmdbx.a + $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) -o $@ + +#> dist-cutoff-begin +else +################################################################################ +# Plain (non-amalgamated) sources with test + +define uname2osal + case "$(UNAME)" in + CYGWIN*|MINGW*|MSYS*|Windows*) echo windows;; + *) echo unix;; + esac +endef + +define uname2titer + case "$(UNAME)" in + Darwin*|Mach*) echo 2;; + *) echo 12;; + esac +endef + +DIST_EXTRA := LICENSE README.md CMakeLists.txt GNUmakefile $(addprefix man1/, $(MANPAGES)) +DIST_SRC := mdbx.h mdbx.c $(addsuffix .c, $(TOOLS)) + +TEST_DB ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.db +TEST_LOG ?= $(shell [ -d /dev/shm ] && echo /dev/shm || echo /tmp)/mdbx-test.log +TEST_OSAL := $(shell $(uname2osal)) +TEST_ITER := $(shell $(uname2titer)) +TEST_SRC := test/osal-$(TEST_OSAL).cc $(filter-out $(wildcard test/osal-*.cc), $(wildcard test/*.cc)) +TEST_INC := $(wildcard test/*.h) +TEST_OBJ := $(patsubst %.cc,%.o,$(TEST_SRC)) +CXX ?= g++ +CXXSTD ?= $(shell $(CXX) -std=c++27 -c test/test.cc -o /dev/null 2>/dev/null && echo -std=c++17 || echo -std=c++11) +CXXFLAGS := $(CXXSTD) $(filter-out -std=gnu11,$(CFLAGS)) + +MAN_SRCDIR := src/man1/ +ALLOY_DEPS := $(wildcard src/elements/*) +MDBX_VERSION_GIT = ${shell set -o pipefail; git describe --tags | sed -n 's|^v*\([0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\)\(.*\)|\1|p' || echo 'Please fetch tags and/or install latest git version'} +MDBX_GIT_TIMESTAMP = $(shell git show --no-patch --format=%cI HEAD || echo 'Please install latest get version') +MDBX_GIT_DESCRIBE = $(shell git describe --tags --long --dirty=-dirty || echo 'Please fetch tags and/or install latest git version') +MDBX_VERSION_SUFFIX = $(shell set -o pipefail; echo -n '$(MDBX_GIT_DESCRIBE)' | tr -c -s '[a-zA-Z0-9]' _) +MDBX_BUILD_SOURCERY = $(shell set -o pipefail; $(MAKE) -s src/elements/version.c && (openssl dgst -r -sha256 src/elements/version.c || sha256sum src/elements/version.c || shasum -a 256 src/elements/version.c) 2>/dev/null | cut -d ' ' -f 1 || echo 'Please install openssl or sha256sum or shasum')_$(MDBX_VERSION_SUFFIX) + +test check: all mdbx_example mdbx_test + rm -f $(TEST_DB) $(TEST_LOG) && (set -o pipefail; \ + (./mdbx_test --progress --console=no --repeat=$(TEST_ITER) --pathname=$(TEST_DB) --dont-cleanup-after basic && \ + ./mdbx_test --mode=-writemap,-lifo --progress --console=no --repeat=1 --pathname=$(TEST_DB) --dont-cleanup-after basic) \ + | tee -a $(TEST_LOG) | tail -n 42) \ + && ./mdbx_chk -vvn $(TEST_DB) && ./mdbx_chk -vvn $(TEST_DB)-copy + +mdbx_example: mdbx.h example/example-mdbx.c libmdbx.$(SO_SUFFIX) + $(CC) $(CFLAGS) -I. example/example-mdbx.c ./libmdbx.$(SO_SUFFIX) -o $@ + +check-singleprocess: all mdbx_test + rm -f $(TEST_DB) $(TEST_LOG) && (set -o pipefail; \ + (./mdbx_test --progress --console=no --repeat=42 --pathname=$(TEST_DB) --dont-cleanup-after --hill && \ + ./mdbx_test --progress --console=no --repeat=2 --pathname=$(TEST_DB) --dont-cleanup-before --dont-cleanup-after --copy && \ + ./mdbx_test --mode=-writemap,-lifo --progress --console=no --repeat=42 --pathname=$(TEST_DB) --dont-cleanup-after --nested) \ + | tee -a $(TEST_LOG) | tail -n 42) \ + && ./mdbx_chk -vvn $(TEST_DB) && ./mdbx_chk -vvn $(TEST_DB)-copy + +check-fault: all mdbx_test + rm -f $(TEST_DB) $(TEST_LOG) && (set -o pipefail; ./mdbx_test --progress --console=no --pathname=$(TEST_DB) --inject-writefault=42 --dump-config --dont-cleanup-after basic | tee -a $(TEST_LOG) | tail -n 42) \ + ; ./mdbx_chk -vvnw $(TEST_DB) && ([ ! -e $(TEST_DB)-copy ] || ./mdbx_chk -vvn $(TEST_DB)-copy) + +VALGRIND=valgrind --trace-children=yes --log-file=valgrind-%p.log --leak-check=full --track-origins=yes --error-exitcode=42 --suppressions=test/valgrind_suppress.txt +memcheck check-valgrind: all mdbx_test + @echo "$(MDBX_OPTIONS)" | grep -q MDBX_USE_VALGRIND || echo "WARNING: Please build libmdbx with -DMDBX_USE_VALGRIND to avoid false-positives from Valgrind !!!" >&2 + rm -f valgrind-*.log $(TEST_DB) $(TEST_LOG) && (set -o pipefail; \ + ($(VALGRIND) ./mdbx_test --mode=-writemap,-lifo --progress --console=no --repeat=4 --pathname=$(TEST_DB) --dont-cleanup-after basic && \ + $(VALGRIND) ./mdbx_test --progress --console=no --pathname=$(TEST_DB) --dont-cleanup-before --dont-cleanup-after --copy && \ + $(VALGRIND) ./mdbx_test --progress --console=no --repeat=2 --pathname=$(TEST_DB) --dont-cleanup-after basic) \ + | tee -a $(TEST_LOG) | tail -n 42) \ + && $(VALGRIND) ./mdbx_chk -vvn $(TEST_DB) && ./mdbx_chk -vvn $(TEST_DB)-copy + +define test-rule +$(patsubst %.cc,%.o,$(1)): $(1) $(TEST_INC) mdbx.h $(lastword $(MAKEFILE_LIST)) + $(CXX) $(CXXFLAGS) $(MDBX_OPTIONS) -c $(1) -o $$@ + +endef +$(foreach file,$(TEST_SRC),$(eval $(call test-rule,$(file)))) + +mdbx_%: src/tools/mdbx_%.c libmdbx.a + $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' $^ $(EXE_LDFLAGS) -o $@ + +mdbx_test: $(TEST_OBJ) libmdbx.$(SO_SUFFIX) + $(CXX) $(CXXFLAGS) $(TEST_OBJ) -Wl,-rpath . -L . -l mdbx $(EXE_LDFLAGS) -o $@ + +git_DIR := $(shell if [ -d .git ]; then echo .git; elif [ -s .git -a -f .git ]; then grep '^gitdir: ' .git | cut -d ':' -f 2; else echo "Please use libmdbx as a git-submodule or the amalgamated source code" >&2 && echo git_directory; fi) + +src/elements/version.c: src/elements/version.c.in $(lastword $(MAKEFILE_LIST)) $(git_DIR)/HEAD $(git_DIR)/index $(git_DIR)/refs/tags + sed \ + -e "s|@MDBX_GIT_TIMESTAMP@|$(MDBX_GIT_TIMESTAMP)|" \ + -e "s|@MDBX_GIT_TREE@|$(shell git show --no-patch --format=%T HEAD || echo 'Please install latest get version')|" \ + -e "s|@MDBX_GIT_COMMIT@|$(shell git show --no-patch --format=%H HEAD || echo 'Please install latest get version')|" \ + -e "s|@MDBX_GIT_DESCRIBE@|$(MDBX_GIT_DESCRIBE)|" \ + -e "s|\$${MDBX_VERSION_MAJOR}|$(shell echo '$(MDBX_VERSION_GIT)' | cut -d . -f 1)|" \ + -e "s|\$${MDBX_VERSION_MINOR}|$(shell echo '$(MDBX_VERSION_GIT)' | cut -d . -f 2)|" \ + -e "s|\$${MDBX_VERSION_RELEASE}|$(shell echo '$(MDBX_VERSION_GIT)' | cut -d . -f 3)|" \ + -e "s|\$${MDBX_VERSION_REVISION}|$(shell git rev-list --count --no-merges HEAD || echo 'Please fetch tags and/or install latest git version')|" \ + src/elements/version.c.in > $@ + +src/elements/config.h: src/elements/version.c $(lastword $(MAKEFILE_LIST)) + (echo '#define MDBX_BUILD_TIMESTAMP "$(shell date +%Y-%m-%dT%H:%M:%S%z)"' \ + && echo '#define MDBX_BUILD_FLAGS "$(CFLAGS) $(LDFLAGS)"' \ + && echo '#define MDBX_BUILD_COMPILER "$(shell set -o pipefail; $(CC) --version | head -1 || echo 'Please use GCC or CLANG compatible compiler')"' \ + && echo '#define MDBX_BUILD_TARGET "$(shell set -o pipefail; LC_ALL=C $(CC) -v 2>&1 | grep -i '^Target:' | cut -d ' ' -f 2- || echo 'Please use GCC or CLANG compatible compiler')"' \ + && echo '#define MDBX_BUILD_SOURCERY $(MDBX_BUILD_SOURCERY)' \ + ) > $@ + +mdbx-dylib.o: src/elements/config.h src/elements/version.c src/alloy.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST)) + $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -DLIBMDBX_EXPORTS=1 -c src/alloy.c -o $@ + +mdbx-static.o: src/elements/config.h src/elements/version.c src/alloy.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST)) + $(CC) $(CFLAGS) $(MDBX_OPTIONS) '-DMDBX_CONFIG_H="config.h"' -ULIBMDBX_EXPORTS -c src/alloy.c -o $@ + +.PHONY: dist +dist: libmdbx-sources-$(MDBX_VERSION_SUFFIX).tar.gz $(lastword $(MAKEFILE_LIST)) + +libmdbx-sources-$(MDBX_VERSION_SUFFIX).tar.gz: $(addprefix dist/, $(DIST_SRC) $(DIST_EXTRA)) $(addprefix dist/man1/,$(MANPAGES)) + tar -c -a -f $@ --owner=0 --group=0 -C dist $(DIST_SRC) $(DIST_EXTRA) \ + && rm dist/@tmp-shared_internals.inc + +dist/mdbx.h: mdbx.h src/elements/version.c $(lastword $(MAKEFILE_LIST)) + mkdir -p dist && cp $< $@ + +dist/GNUmakefile: GNUmakefile + mkdir -p dist && sed -e '/^#> dist-cutoff-begin/,/^#< dist-cutoff-end/d' $< > $@ + +dist/@tmp-shared_internals.inc: src/elements/version.c $(ALLOY_DEPS) $(lastword $(MAKEFILE_LIST)) + mkdir -p dist && sed \ + -e 's|#pragma once|#define MDBX_ALLOY 1\n#define MDBX_BUILD_SOURCERY $(MDBX_BUILD_SOURCERY)|' \ + -e 's|#include "../../mdbx.h"|@INCLUDE "mdbx.h"|' \ + -e '/#include "defs.h"/r src/elements/defs.h' \ + -e '/#include "osal.h"/r src/elements/osal.h' \ + src/elements/internals.h > $@ + +dist/mdbx.c: dist/@tmp-shared_internals.inc $(lastword $(MAKEFILE_LIST)) + mkdir -p dist && (cat dist/@tmp-shared_internals.inc \ + && cat src/elements/core.c src/elements/osal.c src/elements/version.c \ + && echo '#if defined(_WIN32) || defined(_WIN64)' \ + && cat src/elements/lck-windows.c && echo '#else /* LCK-implementation */' \ + && cat src/elements/lck-posix.c && echo '#endif /* LCK-implementation */' \ + ) | grep -v -e '#include "' -e '#pragma once' | sed 's|@INCLUDE|#include|' > $@ + +define dist-tool-rule +dist/$(1).c: src/tools/$(1).c src/tools/wingetopt.h src/tools/wingetopt.c \ + dist/@tmp-shared_internals.inc $(lastword $(MAKEFILE_LIST)) + mkdir -p dist && sed \ + -e '/#include "..\/elements\/internals.h"/r dist/@tmp-shared_internals.inc' \ + -e '/#include "wingetopt.h"/r src/tools/wingetopt.c' \ + src/tools/$(1).c \ + | grep -v -e '#include "' -e '#pragma once' -e '#define MDBX_ALLOY' \ + | sed 's|@INCLUDE|#include|' > $$@ + +endef +$(foreach file,$(TOOLS),$(eval $(call dist-tool-rule,$(file)))) + +dist/man1/mdbx_%.1: src/man1/mdbx_%.1 + mkdir -p dist/man1/ && cp $< $@ +dist/LICENSE: LICENSE + mkdir -p dist/man1/ && cp $< $@ +dist/README.md: README.md + mkdir -p dist/man1/ && cp $< $@ +dist/CMakeLists.txt: CMakeLists.dist-minimal + mkdir -p dist/man1/ && cp $< $@ +endif + +################################################################################ +# Cross-compilation simple test + +CROSS_LIST = mips-linux-gnu-gcc \ + powerpc64-linux-gnu-gcc powerpc-linux-gnu-gcc \ + arm-linux-gnueabihf-gcc aarch64-linux-gnu-gcc \ + sh4-linux-gnu-gcc mips64-linux-gnuabi64-gcc + +# hppa-linux-gnu-gcc - don't supported by current qemu release +# s390x-linux-gnu-gcc - qemu troubles (hang/abort) +# sparc64-linux-gnu-gcc - qemu troubles (fcntl for F_SETLK/F_GETLK) +# alpha-linux-gnu-gcc - qemu (or gcc) troubles (coredump) + +CROSS_LIST_NOQEMU = hppa-linux-gnu-gcc s390x-linux-gnu-gcc \ + sparc64-linux-gnu-gcc alpha-linux-gnu-gcc + +cross-gcc: + @echo "CORRESPONDING CROSS-COMPILERs ARE REQUIRED." + @echo "FOR INSTANCE: apt install g++-aarch64-linux-gnu g++-alpha-linux-gnu g++-arm-linux-gnueabihf g++-hppa-linux-gnu g++-mips-linux-gnu g++-mips64-linux-gnuabi64 g++-powerpc-linux-gnu g++-powerpc64-linux-gnu g++-s390x-linux-gnu g++-sh4-linux-gnu g++-sparc64-linux-gnu" + @for CC in $(CROSS_LIST_NOQEMU) $(CROSS_LIST); do \ + echo "===================== $$CC"; \ + $(MAKE) clean && CC=$$CC CXX=$$(echo $$CC | sed 's/-gcc/-g++/') EXE_LDFLAGS=-static $(MAKE) all || exit $$?; \ + done + +# Unfortunately qemu don't provide robust support for futexes. +# Therefore it is impossible to run full multi-process tests. +cross-qemu: + @echo "CORRESPONDING CROSS-COMPILERs AND QEMUs ARE REQUIRED." + @echo "FOR INSTANCE: " + @echo " 1) apt install g++-aarch64-linux-gnu g++-alpha-linux-gnu g++-arm-linux-gnueabihf g++-hppa-linux-gnu g++-mips-linux-gnu g++-mips64-linux-gnuabi64 g++-powerpc-linux-gnu g++-powerpc64-linux-gnu g++-s390x-linux-gnu g++-sh4-linux-gnu g++-sparc64-linux-gnu" + @echo " 2) apt install binfmt-support qemu-user-static qemu-user qemu-system-arm qemu-system-mips qemu-system-misc qemu-system-ppc qemu-system-sparc" + @for CC in $(CROSS_LIST); do \ + echo "===================== $$CC + qemu"; \ + $(MAKE) clean && \ + CC=$$CC CXX=$$(echo $$CC | sed 's/-gcc/-g++/') EXE_LDFLAGS=-static MDBX_OPTIONS="-DMDBX_SAFE4QEMU $(MDBX_OPTIONS)" \ + $(MAKE) check-singleprocess || exit $$?; \ + done + +#< dist-cutoff-end +install: $(LIBRARIES) $(TOOLS) $(HEADERS) + mkdir -p $(SANDBOX)$(prefix)/bin$(suffix) \ + && cp -t $(SANDBOX)$(prefix)/bin$(suffix) $(TOOLS) && \ + mkdir -p $(SANDBOX)$(prefix)/lib$(suffix) \ + && cp -t $(SANDBOX)$(prefix)/lib$(suffix) $(LIBRARIES) && \ + mkdir -p $(SANDBOX)$(prefix)/include \ + && cp -t $(SANDBOX)$(prefix)/include $(HEADERS) && \ + mkdir -p $(SANDBOX)$(mandir)/man1 \ + && cp -t $(SANDBOX)$(mandir)/man1 $(addprefix $(MAN_SRCDIR), $(MANPAGES)) + +################################################################################ +# Benchmarking by ioarena + +IOARENA ?= $(shell \ + (test -x ../ioarena/@BUILD/src/ioarena && echo ../ioarena/@BUILD/src/ioarena) || \ + (test -x ../../@BUILD/src/ioarena && echo ../../@BUILD/src/ioarena) || \ + (test -x ../../src/ioarena && echo ../../src/ioarena) || which ioarena) +NN ?= 25000000 + +ifneq ($(wildcard $(IOARENA)),) + +.PHONY: bench clean-bench re-bench + +clean-bench: + rm -rf bench-*.txt _ioarena/* + +re-bench: clean-bench bench + +define bench-rule +bench-$(1)_$(2).txt: $(3) $(IOARENA) $(lastword $(MAKEFILE_LIST)) + LD_LIBRARY_PATH="./:$$$${LD_LIBRARY_PATH}" \ + $(IOARENA) -D $(1) -B crud -m nosync -n $(2) \ + | tee $$@ | grep throughput && \ + LD_LIBRARY_PATH="./:$$$${LD_LIBRARY_PATH}" \ + $(IOARENA) -D $(1) -B get,iterate -m sync -r 4 -n $(2) \ + | tee -a $$@ | grep throughput \ + || mv -f $$@ $$@.error + +endef + +$(eval $(call bench-rule,mdbx,$(NN),libmdbx.$(SO_SUFFIX))) + +$(eval $(call bench-rule,sophia,$(NN))) +$(eval $(call bench-rule,leveldb,$(NN))) +$(eval $(call bench-rule,rocksdb,$(NN))) +$(eval $(call bench-rule,wiredtiger,$(NN))) +$(eval $(call bench-rule,forestdb,$(NN))) +$(eval $(call bench-rule,lmdb,$(NN))) +$(eval $(call bench-rule,nessdb,$(NN))) +$(eval $(call bench-rule,sqlite3,$(NN))) +$(eval $(call bench-rule,ejdb,$(NN))) +$(eval $(call bench-rule,vedisdb,$(NN))) +$(eval $(call bench-rule,dummy,$(NN))) + +$(eval $(call bench-rule,debug,10)) + +bench: bench-mdbx_$(NN).txt + +.PHONY: bench-debug + +bench-debug: bench-debug_10.txt + +bench-quartet: bench-mdbx_$(NN).txt bench-lmdb_$(NN).txt bench-rocksdb_$(NN).txt bench-wiredtiger_$(NN).txt + +endif diff --git a/contrib/db/libmdbx/LICENSE b/contrib/db/libmdbx/LICENSE new file mode 100644 index 00000000..05ad7571 --- /dev/null +++ b/contrib/db/libmdbx/LICENSE @@ -0,0 +1,47 @@ +The OpenLDAP Public License + Version 2.8, 17 August 2003 + +Redistribution and use of this software and associated documentation +("Software"), with or without modification, are permitted provided +that the following conditions are met: + +1. Redistributions in source form must retain copyright statements + and notices, + +2. Redistributions in binary form must reproduce applicable copyright + statements and notices, this list of conditions, and the following + disclaimer in the documentation and/or other materials provided + with the distribution, and + +3. Redistributions must contain a verbatim copy of this document. + +The OpenLDAP Foundation may revise this license from time to time. +Each revision is distinguished by a version number. You may use +this Software under terms of this license revision or under the +terms of any subsequent revision of the license. + +THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS +CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT +SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S) +OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +The names of the authors and copyright holders must not be used in +advertising or otherwise to promote the sale, use or other dealing +in this Software without specific, written prior permission. Title +to copyright in this Software shall at all times remain with copyright +holders. + +OpenLDAP is a registered trademark of the OpenLDAP Foundation. + +Copyright 1999-2003 The OpenLDAP Foundation, Redwood City, +California, USA. All Rights Reserved. Permission to copy and +distribute verbatim copies of this document is granted. diff --git a/contrib/db/libmdbx/README.md b/contrib/db/libmdbx/README.md new file mode 100644 index 00000000..1e82f3f3 --- /dev/null +++ b/contrib/db/libmdbx/README.md @@ -0,0 +1,591 @@ +### The [repository now only mirrored on the Github](https://abf.io/erthink/libmdbx) due to illegal discriminatory restrictions for Russian Crimea and for sovereign crimeans. + +----- + +libmdbx +====================================== + +_libmdbx_ is an extremely fast, compact, powerful, embedded +transactional [key-value +store](https://en.wikipedia.org/wiki/Key-value_database) +database, with permissive [OpenLDAP Public License](LICENSE). +_libmdbx_ has a specific set of properties and capabilities, +focused on creating unique lightweight solutions with +extraordinary performance. + +The next version is under active non-public development and will be +released as **_MithrilDB_** and `libmithrildb` for libraries & packages. +Admittedly mythical [Mithril](https://en.wikipedia.org/wiki/Mithril) is +resembling silver but being stronger and lighter than steel. Therefore +_MithrilDB_ is rightly relevant name. +> _MithrilDB_ will be radically different from _libmdbx_ by the new +> database format and API based on C++17, as well as the [Apache 2.0 +> License](https://www.apache.org/licenses/LICENSE-2.0). The goal of this +> revolution is to provide a clearer and robust API, add more features and +> new valuable properties of database. + +*The Future will (be) [Positive](https://www.ptsecurity.com). Всё будет хорошо.* + +[![Build Status](https://travis-ci.org/leo-yuriev/libmdbx.svg?branch=master)](https://travis-ci.org/leo-yuriev/libmdbx) +[![Build status](https://ci.appveyor.com/api/projects/status/ue94mlopn50dqiqg/branch/master?svg=true)](https://ci.appveyor.com/project/leo-yuriev/libmdbx/branch/master) +[![Coverity Scan Status](https://scan.coverity.com/projects/12915/badge.svg)](https://scan.coverity.com/projects/reopen-libmdbx) + +## Table of Contents +- [Overview](#overview) + - [Comparison with other databases](#comparison-with-other-databases) + - [History & Acknowledgments](#history) +- [Description](#description) + - [Key features](#key-features) + - [Improvements over LMDB](#improvements-over-lmdb) + - [Gotchas](#gotchas) +- [Usage](#usage) + - [Building](#building) + - [Bindings](#bindings) +- [Performance comparison](#performance-comparison) + - [Integral performance](#integral-performance) + - [Read scalability](#read-scalability) + - [Sync-write mode](#sync-write-mode) + - [Lazy-write mode](#lazy-write-mode) + - [Async-write mode](#async-write-mode) + - [Cost comparison](#cost-comparison) + +----- + +## Overview + +_libmdbx_ is revised and extended descendant of amazing [Lightning +Memory-Mapped +Database](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database). +_libmdbx_ inherits all features and characteristics from +[LMDB](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), +but resolves some issues and adds several features. + + - _libmdbx_ guarantee data integrity after crash unless this was explicitly +neglected in favour of write performance. + + - _libmdbx_ allows multiple processes to read and update several key-value +tables concurrently, while being +[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, with minimal +overhead and Olog(N) operation cost. + + - _libmdbx_ enforce +[serializability](https://en.wikipedia.org/wiki/Serializability) for +writers by single +[mutex](https://en.wikipedia.org/wiki/Mutual_exclusion) and affords +[wait-free](https://en.wikipedia.org/wiki/Non-blocking_algorithm#Wait-freedom) +for parallel readers without atomic/interlocked operations, while +writing and reading transactions do not block each other. + + - _libmdbx_ uses [B+Trees](https://en.wikipedia.org/wiki/B%2B_tree) and +[Memory-Mapping](https://en.wikipedia.org/wiki/Memory-mapped_file), +doesn't use [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) +which might be a caveat for some workloads. + + - _libmdbx_ implements a simplified variant of the [Berkeley +DB](https://en.wikipedia.org/wiki/Berkeley_DB) and/or +[dbm](https://en.wikipedia.org/wiki/DBM_(computing)) API. + + - _libmdbx_ supports Linux, Windows, MacOS, FreeBSD and other systems +compliant with POSIX.1-2008. + +### Comparison with other databases +For now please refer to [chapter of "BoltDB comparison with other +databases"](https://github.com/coreos/bbolt#comparison-with-other-databases) +which is also (mostly) applicable to _libmdbx_. + +### History +At first the development was carried out within the +[ReOpenLDAP](https://github.com/leo-yuriev/ReOpenLDAP) project. About a +year later _libmdbx_ was separated into standalone project, which was +[presented at Highload++ 2015 +conference](http://www.highload.ru/2015/abstracts/1831.html). + +Since 2017 _libmdbx_ is used in [Fast Positive Tables](https://github.com/leo-yuriev/libfpta), +and development is funded by [Positive Technologies](https://www.ptsecurity.com). + +### Acknowledgments +Howard Chu is the author of LMDB, from which +originated the MDBX in 2015. + +Martin Hedenfalk is the author of `btree.c` code, which +was used for begin development of LMDB. + +----- + +Description +=========== + +## Key features + +1. Key-value pairs are stored in ordered map(s), keys are always sorted, +range lookups are supported. + +2. Data is [memory-mapped](https://en.wikipedia.org/wiki/Memory-mapped_file) +into each worker DB process, and could be accessed zero-copy from transactions. + +3. Transactions are +[ACID](https://en.wikipedia.org/wiki/ACID)-compliant, through to +[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) +and [CoW](https://en.wikipedia.org/wiki/Copy-on-write). Writes are +strongly serialized and aren't blocked by reads, transactions can't +conflict with each other. Reads are guaranteed to get only commited data +([relaxing serializability](https://en.wikipedia.org/wiki/Serializability#Relaxing_serializability)). + +4. Read transactions are +[non-blocking](https://en.wikipedia.org/wiki/Non-blocking_algorithm), +don't use [atomic operations](https://en.wikipedia.org/wiki/Linearizability#High-level_atomic_operations). +Readers don't block each other and aren't blocked by writers. Read +performance scales linearly with CPU core count. + > Nonetheless, "connect to DB" (starting the first read transaction in a thread) and + > "disconnect from DB" (closing DB or thread termination) requires a lock + > acquisition to register/unregister at the "readers table". + +5. Keys with multiple values are stored efficiently without key +duplication, sorted by value, including integers (valuable for +secondary indexes). + +6. Efficient operation on short fixed length keys, +including 32/64-bit integer types. + +7. [WAF](https://en.wikipedia.org/wiki/Write_amplification) (Write +Amplification Factor) и RAF (Read Amplification Factor) are Olog(N). + +8. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) and +transaction journal. In case of a crash no recovery needed. No need for +regular maintenance. Backups can be made on the fly on working DB +without freezing writers. + +9. No additional memory management, all done by basic OS services. + + +## Improvements over LMDB + +_libmdbx_ is superior to _legendary [LMDB](https://symas.com/lmdb/)_ in +terms of features and reliability, not inferior in performance. In +comparison to LMDB, _libmdbx_ make things "just work" perfectly and +out-of-the-box, not silently and catastrophically break down. The list +below is pruned down to the improvements most notable and obvious from +the user's point of view. + +1. Automatic on-the-fly database size control by preset parameters, both +reduction and increment. + > _libmdbx_ manage the database size according to parameters specified + > by `mdbx_env_set_geometry()` function, + > ones include the growth step and the truncation threshold. + +2. Automatic continuous zero-overhead database compactification. + > _libmdbx_ logically move as possible a freed pages + > at end of allocation area into unallocated space, + > and then release such space if a lot of. + +3. LIFO policy for recycling a Garbage Collection items. On systems with a disk +write-back cache, this can significantly increase write performance, up to +several times in a best case scenario. + > LIFO means that for reuse pages will be taken which became unused the lastest. + > Therefore the loop of database pages circulation becomes as short as possible. + > In other words, the number of pages, that are overwritten in memory + > and on disk during a series of write transactions, will be as small as possible. + > Thus creates ideal conditions for the efficient operation of the disk write-back cache. + +4. Fast estimation of range query result volume, i.e. how many items can +be found between a `KEY1` and a `KEY2`. This is prerequisite for build +and/or optimize query execution plans. + > _libmdbx_ performs a rough estimate based only on b-tree pages that + > are common for the both stacks of cursors that were set to corresponing + > keys. + +5. `mdbx_chk` tool for database integrity check. + +6. Guarantee of database integrity even in asynchronous unordered write-to-disk mode. + > _libmdbx_ propose additional trade-off by implementing append-like manner for updates + > in `NOSYNC` and `MAPASYNC` modes, that avoid database corruption after a system crash + > contrary to LMDB. Nevertheless, the `MDBX_UTTERLY_NOSYNC` mode available to match LMDB behaviour, + > and for a special use-cases. + +7. Automated steady flush to disk upon volume of changes and/or by +timeout via cheap polling. + +8. Sequence generation and three cheap persistent 64-bit markers with ACID. + +9. Support for keys and values of zero length, including multi-values +(aka sorted duplicates). + +10. The handler of lack-of-space condition with a callback, +that allow you to control and resolve such situations. + +11. Support for opening a database in the exclusive mode, including on a network share. + +12. Extended transaction info, including dirty and leftover space info +for a write transaction, reading lag and hold over space for read +transactions. + +13. Extended whole-database info (aka environment) and reader enumeration. + +14. Extended update or delete, _at once_ with getting previous value +and addressing the particular item from multi-value with the same key. + +15. Support for explicitly updating the existing record, not insertion a new one. + +16. All cursors are uniformly, can be reused and should be closed explicitly, +regardless ones were opened within write or read transaction. + +17. Correct update of current record with `MDBX_CURRENT` flag when size +of key or data was changed, including sorted duplicated. + +18. Opening database handles is spared from race conditions and +pre-opening is not needed. + +19. Ability to determine whether the particular data is on a dirty page +or not, that allows to avoid copy-out before updates. + +20. Ability to determine whether the cursor is pointed to a key-value +pair, to the first, to the last, or not set to anything. + +21. Returning `MDBX_EMULTIVAL` error in case of ambiguous update or delete. + +22. On **MacOS** the `fcntl(F_FULLFSYNC)` syscall is used _by +default_ to synchronize data with the disk, as this is [the only way to +guarantee data +durability](https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html) +in case of power failure. Unfortunately, in scenarios with high write +intensity, the use of `F_FULLFSYNC` significant degrades performance +compared to LMDB, where the `fsync()` syscall is used. Therefore, +_libmdbx_ allows you to override this behavior by defining the +`MDBX_OSX_SPEED_INSTEADOF_DURABILITY=1` option while build the library. + +23. On **Windows** the `LockFileEx()` syscall is used for locking, since +it allows place the database on network drives, and provides protection +against incompetent user actions (aka +[poka-yoke](https://en.wikipedia.org/wiki/Poka-yoke)). Therefore +_libmdbx_ may be a little lag in performance tests from LMDB where a +named mutexes are used. + + +## Gotchas + +1. There cannot be more than one writer at a time. + > On the other hand, this allows serialize an updates and eliminate any + > possibility of conflicts, deadlocks or logical errors. + +2. No [WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) means +relatively big [WAF](https://en.wikipedia.org/wiki/Write_amplification) +(Write Amplification Factor). Because of this syncing data to disk might +be quite resource intensive and be main performance bottleneck during +intensive write workload. + > As compromise _libmdbx_ allows several modes of lazy and/or periodic + > syncing, including `MAPASYNC` mode, which modificate data in memory and + > asynchronously syncs data to disk, moment to sync is picked by OS. + > + > Although this should be used with care, synchronous transactions in a DB + > with transaction journal will require 2 IOPS minimum (probably 3-4 in + > practice) because of filesystem overhead, overhead depends on + > filesystem, not on record count or record size. In _libmdbx_ IOPS count + > will grow logarithmically depending on record count in DB (height of B+ + > tree) and will require at least 2 IOPS per transaction too. + +3. [CoW](https://en.wikipedia.org/wiki/Copy-on-write) for +[MVCC](https://en.wikipedia.org/wiki/Multiversion_concurrency_control) +is done on memory page level with +[B+trees](https://ru.wikipedia.org/wiki/B-%D0%B4%D0%B5%D1%80%D0%B5%D0%B2%D0%BE). +Therefore altering data requires to copy about Olog(N) memory pages, +which uses [memory bandwidth](https://en.wikipedia.org/wiki/Memory_bandwidth) and is main +performance bottleneck in `MDBX_MAPASYNC` mode. + > This is unavoidable, but isn't that bad. Syncing data to disk requires + > much more similar operations which will be done by OS, therefore this is + > noticeable only if data sync to persistent storage is fully disabled. + > _libmdbx_ allows to safely save data to persistent storage with minimal + > performance overhead. If there is no need to save data to persistent + > storage then it's much more preferable to use `std::map`. + +4. Massive altering of data during a parallel long read operation will +increase the process work set, may exhaust entire free database space and +result in subsequent write performance degradation. + > _libmdbx_ mostly solve this issue by lack-of-space callback and `MDBX_LIFORECLAIM` mode. + > See [`mdbx.h`](mdbx.h) with API description for details. + > The "next" version of libmdbx (MithrilDB) will completely solve this. + +5. There are no built-in checksums or digests to verify database integrity. + > The "next" version of _libmdbx_ (MithrilDB) will solve this issue employing [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree). + +-------------------------------------------------------------------------------- + +Usage +===== + +## Source code embedding + +_libmdbx_ provides two official ways for integration in source code form: + +1. Using the amalgamated source code. + > The amalgamated source code includes all files requires to build and + > use _libmdbx_, but not for testing _libmdbx_ itself. + +2. Adding the complete original source code as a `git submodule`. + > This allows you to build as _libmdbx_ and testing tool. + > On the other hand, this way requires you to pull git tags, and use C++11 compiler for test tool. + +**_Please, avoid using any other techniques._** Otherwise, at least +don't ask for support and don't name such chimeras `libmdbx`. + +The amalgamated source code could be created from original clone of git +repository on Linux by executing `make dist`. As a result, the desired +set of files will be formed in the `dist` subdirectory. + +## Building + +Both amalgamated and original source code provides build through the use +[CMake](https://cmake.org/) or [GNU +Make](https://www.gnu.org/software/make/) with +[bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)). All build ways +are completely traditional and have minimal prerequirements like +`build-essential`, i.e. the non-obsolete C/C++ compiler and a +[SDK](https://en.wikipedia.org/wiki/Software_development_kit) for the +target platform. Obviously you need building tools itself, i.e. `git`, +`cmake` or GNU `make` with `bash`. + +So just use CMake or GNU Make in your habitual manner and feel free to +fill an issue or make pull request in the case something will be +unexpected or broken down. + +#### DSO/DLL unloading and destructors of Thread-Local-Storage objects +When building _libmdbx_ as a shared library or use static _libmdbx_ as a +part of another dynamic library, it is advisable to make sure that your +system ensures the correctness of the call destructors of +Thread-Local-Storage objects when unloading dynamic libraries. + +If this is not the case, then unloading a dynamic-link library with +_libmdbx_ code inside, can result in either a resource leak or a crash +due to calling destructors from an already unloaded DSO/DLL object. The +problem can only manifest in a multithreaded application, which makes +the unloading of shared dynamic libraries with _libmdbx_ code inside, +after using _libmdbx_. It is known that TLS-destructors are properly +maintained in the following cases: + +- On all modern versions of Windows (Windows 7 and later). + +- On systems with the +[`__cxa_thread_atexit_impl()`](https://sourceware.org/glibc/wiki/Destructor%20support%20for%20thread_local%20variables) +function in the standard C library, including systems with GNU libc +version 2.18 and later. + +- On systems with libpthread/ntpl from GNU libc with bug fixes +[#21031](https://sourceware.org/bugzilla/show_bug.cgi?id=21031) and +[#21032](https://sourceware.org/bugzilla/show_bug.cgi?id=21032), or +where there are no similar bugs in the pthreads implementation. + +### Linux and other platforms with GNU Make +To build the library it is enough to execute `make all` in the directory +of source code, and `make check` for execute the basic tests. + +If the `make` installed on the system is not GNU Make, there will be a +lot of errors from make when trying to build. In this case, perhaps you +should use `gmake` instead of `make`, or even `gnu-make`, etc. + +### FreeBSD and related platforms +As a rule, in such systems, the default is to use Berkeley Make. And GNU +Make is called by the gmake command or may be missing. In addition, +[bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) may be absent. + +You need to install the required components: GNU Make, bash, C and C++ +compilers compatible with GCC or CLANG. After that, to build the +library, it is enough execute `gmake all` (or `make all`) in the +directory with source code, and `gmake check` (or `make check`) to run +the basic tests. + +### Windows +For build _libmdbx_ on Windows the _original_ CMake and [Microsoft Visual +Studio](https://en.wikipedia.org/wiki/Microsoft_Visual_Studio) are +recommended. + +Building by MinGW, MSYS or Cygwin is potentially possible. However, +these scripts are not tested and will probably require you to modify the +CMakeLists.txt or Makefile respectively. + +It should be noted that in _libmdbx_ was efforts to resolve +runtime dependencies from CRT and other libraries Visual Studio. +For this is enough define the `MDBX_AVOID_CRT` during build. + +An example of running a basic test script can be found in the +[CI-script](appveyor.yml) for [AppVeyor](https://www.appveyor.com/). To +run the [long stochastic test scenario](test/long_stochastic.sh), +[bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) is required, and +the such testing is recommended with place the test data on the +[RAM-disk](https://en.wikipedia.org/wiki/RAM_drive). + +### MacOS +Current [native build tools](https://en.wikipedia.org/wiki/Xcode) for +MacOS include GNU Make, CLANG and an outdated version of bash. +Therefore, to build the library, it is enough to run `make all` in the +directory with source code, and run `make check` to execute the base +tests. If something goes wrong, it is recommended to install +[Homebrew](https://brew.sh/) and try again. + +To run the [long stochastic test scenario](test/long_stochastic.sh), you +will need to install the current (not outdated) version of +[bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)). To do this, we +recommend that you install [Homebrew](https://brew.sh/) and then execute +`brew install bash`. + +## Bindings + + | Runtime | GitHub | Author | + | -------- | ------ | ------ | + | Java | [mdbxjni](https://github.com/castortech/mdbxjni) | [Castor Technologies](https://castortech.com/) | + | .NET | [mdbx.NET](https://github.com/wangjia184/mdbx.NET) | [Jerry Wang](https://github.com/wangjia184) | + + +-------------------------------------------------------------------------------- + +Performance comparison +====================== + +All benchmarks were done by [IOArena](https://github.com/pmwkaa/ioarena) +and multiple [scripts](https://github.com/pmwkaa/ioarena/tree/HL%2B%2B2015) +runs on Lenovo Carbon-2 laptop, i7-4600U 2.1 GHz, 8 Gb RAM, +SSD SAMSUNG MZNTD512HAGL-000L1 (DXT23L0Q) 512 Gb. + +## Integral performance + +Here showed sum of performance metrics in 3 benchmarks: + + - Read/Search on 4 CPU cores machine; + + - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) + operations in sync-write mode (fdatasync is called after each + transaction); + + - Transactions with [CRUD](https://en.wikipedia.org/wiki/CRUD) + operations in lazy-write mode (moment to sync data to persistent storage + is decided by OS). + +*Reasons why asynchronous mode isn't benchmarked here:* + + 1. It doesn't make sense as it has to be done with DB engines, oriented + for keeping data in memory e.g. [Tarantool](https://tarantool.io/), + [Redis](https://redis.io/)), etc. + + 2. Performance gap is too high to compare in any meaningful way. + +![Comparison #1: Integral Performance](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-1.png) + +-------------------------------------------------------------------------------- + +## Read Scalability + +Summary performance with concurrent read/search queries in 1-2-4-8 +threads on 4 CPU cores machine. + +![Comparison #2: Read Scalability](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-2.png) + +-------------------------------------------------------------------------------- + +## Sync-write mode + + - Linear scale on left and dark rectangles mean arithmetic mean + transactions per second; + + - Logarithmic scale on right is in seconds and yellow intervals mean + execution time of transactions. Each interval shows minimal and maximum + execution time, cross marks standard deviation. + +**10,000 transactions in sync-write mode**. In case of a crash all data +is consistent and state is right after last successful transaction. +[fdatasync](https://linux.die.net/man/2/fdatasync) syscall is used after +each write transaction in this mode. + +In the benchmark each transaction contains combined CRUD operations (2 +inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database +and after full run the database contains 10,000 small key-value records. + +![Comparison #3: Sync-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-3.png) + +-------------------------------------------------------------------------------- + +## Lazy-write mode + + - Linear scale on left and dark rectangles mean arithmetic mean of + thousands transactions per second; + + - Logarithmic scale on right in seconds and yellow intervals mean + execution time of transactions. Each interval shows minimal and maximum + execution time, cross marks standard deviation. + +**100,000 transactions in lazy-write mode**. In case of a crash all data +is consistent and state is right after one of last transactions, but +transactions after it will be lost. Other DB engines use +[WAL](https://en.wikipedia.org/wiki/Write-ahead_logging) or transaction +journal for that, which in turn depends on order of operations in +journaled filesystem. _libmdbx_ doesn't use WAL and hands I/O operations +to filesystem and OS kernel (mmap). + +In the benchmark each transaction contains combined CRUD operations (2 +inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database +and after full run the database contains 100,000 small key-value +records. + + +![Comparison #4: Lazy-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-4.png) + +-------------------------------------------------------------------------------- + +## Async-write mode + + - Linear scale on left and dark rectangles mean arithmetic mean of + thousands transactions per second; + + - Logarithmic scale on right in seconds and yellow intervals mean + execution time of transactions. Each interval shows minimal and maximum + execution time, cross marks standard deviation. + +**1,000,000 transactions in async-write mode**. In case of a crash all +data will be consistent and state will be right after one of last +transactions, but lost transaction count is much higher than in +lazy-write mode. All DB engines in this mode do as little writes as +possible on persistent storage. _libmdbx_ uses +[msync(MS_ASYNC)](https://linux.die.net/man/2/msync) in this mode. + +In the benchmark each transaction contains combined CRUD operations (2 +inserts, 1 read, 1 update, 1 delete). Benchmark starts on empty database +and after full run the database contains 10,000 small key-value records. + +![Comparison #5: Async-write mode](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-5.png) + +-------------------------------------------------------------------------------- + +## Cost comparison + +Summary of used resources during lazy-write mode benchmarks: + + - Read and write IOPS; + + - Sum of user CPU time and sys CPU time; + + - Used space on persistent storage after the test and closed DB, but not + waiting for the end of all internal housekeeping operations (LSM + compactification, etc). + +_ForestDB_ is excluded because benchmark showed it's resource +consumption for each resource (CPU, IOPS) much higher than other engines +which prevents to meaningfully compare it with them. + +All benchmark data is gathered by +[getrusage()](http://man7.org/linux/man-pages/man2/getrusage.2.html) +syscall and by scanning data directory. + +![Comparison #6: Cost comparison](https://raw.githubusercontent.com/wiki/leo-yuriev/libmdbx/img/perf-slide-6.png) + +-------------------------------------------------------------------------------- + +``` +$ objdump -f -h -j .text libmdbx.so + +libmdbx.so: file format elf64-x86-64 +architecture: i386:x86-64, flags 0x00000150: +HAS_SYMS, DYNAMIC, D_PAGED +start address 0x0000000000003710 + +Sections: +Idx Name Size VMA LMA File off Algn + 11 .text 00015eff 0000000000003710 0000000000003710 00003710 2**4 + CONTENTS, ALLOC, LOAD, READONLY, CODE +``` diff --git a/contrib/db/libmdbx/appveyor.yml b/contrib/db/libmdbx/appveyor.yml new file mode 100644 index 00000000..be7ee76a --- /dev/null +++ b/contrib/db/libmdbx/appveyor.yml @@ -0,0 +1,99 @@ +version: 0.3.2.{build} + +environment: + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CMAKE_GENERATOR: Visual Studio 16 2019 + TOOLSET: 142 + MDBX_BUILD_SHARED_LIBRARY: OFF + MDBX_AVOID_CRT: OFF + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CMAKE_GENERATOR: Visual Studio 16 2019 + TOOLSET: 142 + MDBX_BUILD_SHARED_LIBRARY: ON + MDBX_AVOID_CRT: ON + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CMAKE_GENERATOR: Visual Studio 16 2019 + TOOLSET: 142 + MDBX_BUILD_SHARED_LIBRARY: OFF + MDBX_AVOID_CRT: ON + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CMAKE_GENERATOR: Visual Studio 16 2019 + TOOLSET: 142 + MDBX_BUILD_SHARED_LIBRARY: ON + MDBX_AVOID_CRT: OFF + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 + CMAKE_GENERATOR: Visual Studio 15 2017 + TOOLSET: 141 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 + CMAKE_GENERATOR: Visual Studio 14 2015 + TOOLSET: 140 + +branches: + except: + - coverity_scan + +configuration: +- Debug +- Release + +platform: +- Win32 +- x64 + +before_build: +- git clean -x -f -d +- git submodule sync +- git fetch --tags --prune +- git submodule update --init --recursive +- git submodule foreach --recursive git fetch --tags --prune +- cmake --version + +build_script: +- ps: | + Write-Output "*******************************************************************************" + Write-Output "Configuration: $env:CONFIGURATION" + Write-Output "Platform: $env:PLATFORM" + Write-Output "Toolchain: $env:CMAKE_GENERATOR v$env:TOOLSET" + Write-Output "Options: MDBX_AVOID_CRT=$env:MDBX_AVOID_CRT MDBX_BUILD_SHARED_LIBRARY=$env:MDBX_BUILD_SHARED_LIBRARY" + Write-Output "*******************************************************************************" + + md _build -Force | Out-Null + cd _build + + $generator = $env:CMAKE_GENERATOR + if ($env:TOOLSET -lt 142) { + if ($env:PLATFORM -eq "x64") { + $generator = "$generator Win64" + } + & cmake -G "$generator" -D CMAKE_CONFIGURATION_TYPES="Debug;Release" -D MDBX_AVOID_CRT:BOOL=$env:MDBX_AVOID_CRT -D MDBX_BUILD_SHARED_LIBRARY:BOOL=$env:MDBX_BUILD_SHARED_LIBRARY .. + } else { + & cmake -G "$generator" -A $env:PLATFORM -D CMAKE_CONFIGURATION_TYPES="Debug;Release" -DMDBX_AVOID_CRT:BOOL=$env:MDBX_AVOID_CRT -D MDBX_BUILD_SHARED_LIBRARY:BOOL=$env:MDBX_BUILD_SHARED_LIBRARY .. + } + if ($LastExitCode -ne 0) { + throw "Exec: $ErrorMessage" + } + Write-Output "*******************************************************************************" + + & cmake --build . --config $env:CONFIGURATION + if ($LastExitCode -ne 0) { + throw "Exec: $ErrorMessage" + } + Write-Output "*******************************************************************************" + +test_script: +- ps: | + if (($env:PLATFORM -ne "ARM") -and ($env:PLATFORM -ne "ARM64")) { + & ./$env:CONFIGURATION/mdbx_test.exe --progress --console=no --pathname=test.db --dont-cleanup-after basic > test.log + Get-Content test.log | Select-Object -last 42 + if ($LastExitCode -ne 0) { + throw "Exec: $ErrorMessage" + } else { + & ./$env:CONFIGURATION/mdbx_chk.exe -nvv test.db | Tee-Object -file chk.log | Select-Object -last 42 + } + } + +on_failure: +- ps: Push-AppveyorArtifact \projects\libmdbx\_build\test.log +- ps: Push-AppveyorArtifact \projects\libmdbx\_build\test.db +- ps: Push-AppveyorArtifact \projects\libmdbx\_build\chk.log diff --git a/contrib/db/libmdbx/cmake/compiler.cmake b/contrib/db/libmdbx/cmake/compiler.cmake new file mode 100644 index 00000000..03b0805b --- /dev/null +++ b/contrib/db/libmdbx/cmake/compiler.cmake @@ -0,0 +1,666 @@ +## Copyright (c) 2012-2019 Leonid Yuriev . +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## + +cmake_minimum_required(VERSION 3.8.2) +cmake_policy(PUSH) +cmake_policy(VERSION 3.8.2) + +if (CMAKE_VERSION MATCHES ".*MSVC.*") + message(FATAL_ERROR "CMake from MSVC kit is unfit! " + "Please use the original CMake from https://cmake.org/download/") +endif() + +if (NOT (CMAKE_C_COMPILER_LOADED OR CMAKE_CXX_COMPILER_LOADED)) + message(FATAL_ERROR "This module required C or C++ to be enabled") +endif() + +include(CMakeDependentOption) + +if(CMAKE_CXX_COMPILER_LOADED) + include(CheckCXXSourceRuns) + include(CheckCXXSourceCompiles) + include(CheckCXXCompilerFlag) +endif() +if(CMAKE_C_COMPILER_LOADED) + include(CheckCSourceRuns) + include(CheckCSourceCompiles) + include(CheckCCompilerFlag) +endif() + +# Check if the same compile family is used for both C and CXX +if(CMAKE_C_COMPILER_LOADED AND CMAKE_CXX_COMPILER_LOADED AND + NOT (CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID)) + message(WARNING "CMAKE_C_COMPILER_ID (${CMAKE_C_COMPILER_ID}) is different " + "from CMAKE_CXX_COMPILER_ID (${CMAKE_CXX_COMPILER_ID}). " + "The final binary may be unusable.") +endif() + +if(CMAKE_CXX_COMPILER_LOADED) + set(CMAKE_PRIMARY_LANG "CXX") +else() + set(CMAKE_PRIMARY_LANG "C") +endif() + +macro(check_compiler_flag flag variable) + if(CMAKE_CXX_COMPILER_LOADED) + check_cxx_compiler_flag(${flag} ${variable}) + else() + check_c_compiler_flag(${flag} ${variable}) + endif() +endmacro(check_compiler_flag) + +# We support building with Clang and gcc. First check +# what we're using for build. +if(CMAKE_C_COMPILER_LOADED AND CMAKE_C_COMPILER_ID STREQUAL "Clang") + set(CMAKE_COMPILER_IS_CLANG ON) + set(CMAKE_COMPILER_IS_GNUCC OFF) +endif() +if(CMAKE_CXX_COMPILER_LOADED AND CMAKE_CXx_COMPILER_ID STREQUAL "Clang") + set(CMAKE_COMPILER_IS_CLANG ON) + set(CMAKE_COMPILER_IS_GNUCXX OFF) +endif() + +# Hard coding the compiler version is ugly from cmake POV, but +# at least gives user a friendly error message. The most critical +# demand for C++ compiler is support of C++11 lambdas, added +# only in version 4.5 https://gcc.gnu.org/projects/cxx0x.html +if(CMAKE_COMPILER_IS_GNUCC) + if(CMAKE_C_COMPILER_VERSION VERSION_LESS 4.5) + message(FATAL_ERROR " + Your GCC version is ${CMAKE_C_COMPILER_VERSION}, please update") + endif() +endif() +if(CMAKE_COMPILER_IS_GNUCXX) + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.5) + message(FATAL_ERROR " + Your G++ version is ${CMAKE_CXX_COMPILER_VERSION}, please update") + endif() +endif() + +if(CMAKE_C_COMPILER_LOADED) + # Check for Elbrus lcc + execute_process(COMMAND ${CMAKE_C_COMPILER} --version + OUTPUT_VARIABLE tmp_lcc_probe_version + RESULT_VARIABLE tmp_lcc_probe_result ERROR_QUIET) + if(tmp_lcc_probe_result EQUAL 0) + string(FIND "${tmp_lcc_probe_version}" "lcc:" tmp_lcc_marker) + string(FIND "${tmp_lcc_probe_version}" ":e2k-" tmp_e2k_marker) + if(tmp_lcc_marker GREATER -1 AND tmp_e2k_marker GREATER tmp_lcc_marker) + execute_process(COMMAND ${CMAKE_C_COMPILER} -print-version + OUTPUT_VARIABLE CMAKE_C_COMPILER_VERSION + RESULT_VARIABLE tmp_lcc_probe_result) + set(CMAKE_COMPILER_IS_ELBRUSC ON) + set(CMAKE_C_COMPILER_ID "Elbrus") + else() + set(CMAKE_COMPILER_IS_ELBRUSC OFF) + endif() + unset(tmp_lcc_marker) + unset(tmp_e2k_marker) + endif() + unset(tmp_lcc_probe_version) + unset(tmp_lcc_probe_result) +endif() + +if(CMAKE_CXX_COMPILER_LOADED) + # Check for Elbrus l++ + execute_process(COMMAND ${CMAKE_CXX_COMPILER} --version + OUTPUT_VARIABLE tmp_lxx_probe_version + RESULT_VARIABLE tmp_lxx_probe_result ERROR_QUIET) + if(tmp_lxx_probe_result EQUAL 0) + string(FIND "${tmp_lxx_probe_version}" "lcc:" tmp_lcc_marker) + string(FIND "${tmp_lxx_probe_version}" ":e2k-" tmp_e2k_marker) + if(tmp_lcc_marker GREATER -1 AND tmp_e2k_marker GREATER tmp_lcc_marker) + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -print-version + OUTPUT_VARIABLE CMAKE_CXX_COMPILER_VERSION + RESULT_VARIABLE tmp_lxx_probe_result) + set(CMAKE_COMPILER_IS_ELBRUSCXX ON) + set(CMAKE_CXX_COMPILER_ID "Elbrus") + else() + set(CMAKE_COMPILER_IS_ELBRUSCXX OFF) + endif() + unset(tmp_lcc_marker) + unset(tmp_e2k_marker) + endif() + unset(tmp_lxx_probe_version) + unset(tmp_lxx_probe_result) +endif() + +if(CMAKE_CL_64) + set(MSVC64 1) +endif() +if(WIN32 AND CMAKE_COMPILER_IS_GNU${CMAKE_PRIMARY_LANG}) + execute_process(COMMAND ${CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER} -dumpmachine + OUTPUT_VARIABLE __GCC_TARGET_MACHINE + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(__GCC_TARGET_MACHINE MATCHES "amd64|x86_64|AMD64") + set(MINGW64 1) + endif() + unset(__GCC_TARGET_MACHINE) +endif() + +if(CMAKE_COMPILER_IS_ELBRUSC OR CMAKE_SYSTEM_PROCESSOR MATCHES "e2k.*|E2K.*|elbrus.*|ELBRUS.*") + set(E2K TRUE) + set(CMAKE_SYSTEM_ARCH "Elbrus") +elseif((MSVC64 OR MINGW64) AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(X86_64 TRUE) + set(CMAKE_SYSTEM_ARCH "x86_64") +elseif(MINGW OR (MSVC AND NOT CMAKE_CROSSCOMPILING)) + set(X86_32 TRUE) + set(CMAKE_SYSTEM_ARCH "x86") +elseif(CMAKE_COMPILER_IS_ELBRUSC OR CMAKE_SYSTEM_PROCESSOR MATCHES "e2k.*|E2K.*|elbrus.*|ELBRUS.*") + set(E2K TRUE) + set(CMAKE_SYSTEM_ARCH "Elbrus") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64.*|x86_64.*|AMD64.*" AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(X86_64 TRUE) + set(CMAKE_SYSTEM_ARCH "x86_64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686.*|i386.*|x86.*") + set(X86_32 TRUE) + set(CMAKE_SYSTEM_ARCH "x86") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*|ARM64.*)" AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(AARCH64 TRUE) + set(CMAKE_SYSTEM_ARCH "ARM64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm.*|ARM.*)") + set(ARM32 TRUE) + set(CMAKE_SYSTEM_ARCH "ARM") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64le.*" AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(PPC64LE TRUE) + set(CMAKE_SYSTEM_ARCH "PPC64LE") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc)64.*" AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(PPC64 TRUE) + set(CMAKE_SYSTEM_ARCH "PPC64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(powerpc|ppc).*") + set(PPC32 TRUE) + set(CMAKE_SYSTEM_ARCH "PPC") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(mips|MIPS)64.*" AND CMAKE_SIZEOF_VOID_P EQUAL 8) + set(MIPS64 TRUE) + set(CMAKE_SYSTEM_ARCH "MIPS64") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(mips|MIPS).*") + set(MIPS32 TRUE) + set(CMAKE_SYSTEM_ARCH "MIPS") +endif() + +if(MSVC) + check_compiler_flag("/WX" CC_HAS_WERROR) +else() + # + # GCC started to warn for unused result starting from 4.2, and + # this is when it introduced -Wno-unused-result + # GCC can also be built on top of llvm runtime (on mac). + check_compiler_flag("-Wno-unknown-pragmas" CC_HAS_WNO_UNKNOWN_PRAGMAS) + check_compiler_flag("-Wextra" CC_HAS_WEXTRA) + check_compiler_flag("-Werror" CC_HAS_WERROR) + check_compiler_flag("-fexceptions" CC_HAS_FEXCEPTIONS) + check_cxx_compiler_flag("-fcxx-exceptions" CC_HAS_FCXX_EXCEPTIONS) + check_compiler_flag("-funwind-tables" CC_HAS_FUNWIND_TABLES) + check_compiler_flag("-fno-omit-frame-pointer" CC_HAS_FNO_OMIT_FRAME_POINTER) + check_compiler_flag("-fno-common" CC_HAS_FNO_COMMON) + check_compiler_flag("-ggdb" CC_HAS_GGDB) + check_compiler_flag("-fvisibility=hidden" CC_HAS_VISIBILITY) + check_compiler_flag("-march=native" CC_HAS_ARCH_NATIVE) + check_compiler_flag("-Og" CC_HAS_DEBUG_FRENDLY_OPTIMIZATION) + check_compiler_flag("-Wall" CC_HAS_WALL) + check_compiler_flag("-Ominimal" CC_HAS_OMINIMAL) + check_compiler_flag("-ffunction-sections -fdata-sections" CC_HAS_SECTIONS) + check_compiler_flag("-ffast-math" CC_HAS_FASTMATH) + + # Check for an omp support + set(CMAKE_REQUIRED_FLAGS "-fopenmp -Werror") + check_cxx_source_compiles("int main(void) { + #pragma omp parallel + return 0; + }" HAVE_OPENMP) + set(CMAKE_REQUIRED_FLAGS "") +endif() + +# Check for LTO support by GCC +if(CMAKE_COMPILER_IS_GNU${CMAKE_PRIMARY_LANG}) + unset(gcc_collect) + unset(gcc_lto_wrapper) + + if(NOT CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 4.7) + execute_process(COMMAND ${CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER} -v + OUTPUT_VARIABLE gcc_info_v ERROR_VARIABLE gcc_info_v) + + string(REGEX MATCH "^(.+\nCOLLECT_GCC=)([^ \n]+)(\n.+)$" gcc_collect_valid ${gcc_info_v}) + if(gcc_collect_valid) + string(REGEX REPLACE "^(.+\nCOLLECT_GCC=)([^ \n]+)(\n.+)$" "\\2" gcc_collect ${gcc_info_v}) + endif() + + string(REGEX MATCH "^(.+\nCOLLECT_LTO_WRAPPER=)([^ \n]+/lto-wrapper)(\n.+)$" gcc_lto_wrapper_valid ${gcc_info_v}) + if(gcc_lto_wrapper_valid) + string(REGEX REPLACE "^(.+\nCOLLECT_LTO_WRAPPER=)([^ \n]+/lto-wrapper)(\n.+)$" "\\2" gcc_lto_wrapper ${gcc_info_v}) + endif() + + set(gcc_suffix "") + if(gcc_collect_valid AND gcc_collect) + string(REGEX MATCH "^(.*cc)(-.+)$" gcc_suffix_valid ${gcc_collect}) + if(gcc_suffix_valid) + string(REGEX MATCH "^(.*cc)(-.+)$" "\\2" gcc_suffix ${gcc_collect}) + endif() + endif() + + get_filename_component(gcc_dir ${CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER} DIRECTORY) + if(NOT CMAKE_GCC_AR) + find_program(CMAKE_GCC_AR NAMES gcc${gcc_suffix}-ar gcc-ar${gcc_suffix} PATHS ${gcc_dir} NO_DEFAULT_PATH) + endif() + if(NOT CMAKE_GCC_NM) + find_program(CMAKE_GCC_NM NAMES gcc${gcc_suffix}-nm gcc-nm${gcc_suffix} PATHS ${gcc_dir} NO_DEFAULT_PATH) + endif() + if(NOT CMAKE_GCC_RANLIB) + find_program(CMAKE_GCC_RANLIB NAMES gcc${gcc_suffix}-ranlib gcc-ranlib${gcc_suffix} PATHS ${gcc_dir} NO_DEFAULT_PATH) + endif() + + unset(gcc_dir) + unset(gcc_suffix_valid) + unset(gcc_suffix) + unset(gcc_lto_wrapper_valid) + unset(gcc_collect_valid) + unset(gcc_collect) + unset(gcc_info_v) + endif() + + if(CMAKE_GCC_AR AND CMAKE_GCC_NM AND CMAKE_GCC_RANLIB AND gcc_lto_wrapper) + message(STATUS "Found GCC's LTO toolset: ${gcc_lto_wrapper}, ${CMAKE_GCC_AR}, ${CMAKE_GCC_RANLIB}") + set(GCC_LTO_CFLAGS "-flto -fno-fat-lto-objects -fuse-linker-plugin") + set(GCC_LTO_AVAILABLE TRUE) + message(STATUS "Link-Time Optimization by GCC is available") + else() + set(GCC_LTO_AVAILABLE FALSE) + message(STATUS "Link-Time Optimization by GCC is NOT available") + endif() + unset(gcc_lto_wrapper) +endif() + +# check for LTO by MSVC +if(MSVC) + if(NOT MSVC_VERSION LESS 1600) + set(MSVC_LTO_AVAILABLE TRUE) + message(STATUS "Link-Time Optimization by MSVC is available") + else() + set(MSVC_LTO_AVAILABLE FALSE) + message(STATUS "Link-Time Optimization by MSVC is NOT available") + endif() +endif() + +# Check for LTO support by CLANG +if(CMAKE_COMPILER_IS_CLANG) + if(NOT CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 3.5) + execute_process(COMMAND ${CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER} -print-search-dirs + OUTPUT_VARIABLE clang_search_dirs) + + unset(clang_bindir) + unset(clang_libdir) + string(REGEX MATCH "^(.*programs: =)([^:]*:)*([^:]+/llvm[-.0-9]+/bin[^:]*)(:[^:]*)*(\n.+)$" clang_bindir_valid ${clang_search_dirs}) + if(clang_bindir_valid) + string(REGEX REPLACE "^(.*programs: =)([^:]*:)*([^:]+/llvm[-.0-9]+/bin[^:]*)(:[^:]*)*(\n.+)$" "\\3" clang_bindir ${clang_search_dirs}) + get_filename_component(clang_libdir "${clang_bindir}/../lib" REALPATH) + if(clang_libdir) + message(STATUS "Found CLANG/LLVM directories: ${clang_bindir}, ${clang_libdir}") + endif() + endif() + + if(NOT (clang_bindir AND clang_libdir)) + message(STATUS "Could NOT find CLANG/LLVM directories (bin and/or lib).") + endif() + + if(NOT CMAKE_CLANG_LD AND clang_bindir) + find_program(CMAKE_CLANG_LD NAMES llvm-link link llvm-ld ld PATHS ${clang_bindir} NO_DEFAULT_PATH) + endif() + if(NOT CMAKE_CLANG_AR AND clang_bindir) + find_program(CMAKE_CLANG_AR NAMES llvm-ar ar PATHS ${clang_bindir} NO_DEFAULT_PATH) + endif() + if(NOT CMAKE_CLANG_NM AND clang_bindir) + find_program(CMAKE_CLANG_NM NAMES llvm-nm nm PATHS ${clang_bindir} NO_DEFAULT_PATH) + endif() + if(NOT CMAKE_CLANG_RANLIB AND clang_bindir) + find_program(CMAKE_CLANG_RANLIB NAMES llvm-ranlib ranlib PATHS ${clang_bindir} NO_DEFAULT_PATH) + endif() + + set(clang_lto_plugin_name "LLVMgold${CMAKE_SHARED_LIBRARY_SUFFIX}") + if(NOT CMAKE_LD_GOLD AND clang_bindir) + find_program(CMAKE_LD_GOLD NAMES ld.gold PATHS) + endif() + if(NOT CLANG_LTO_PLUGIN AND clang_libdir) + find_file(CLANG_LTO_PLUGIN ${clang_lto_plugin_name} PATH ${clang_libdir} NO_DEFAULT_PATH) + endif() + if(CLANG_LTO_PLUGIN) + message(STATUS "Found CLANG/LLVM's plugin for LTO: ${CLANG_LTO_PLUGIN}") + else() + message(STATUS "Could NOT find CLANG/LLVM's plugin (${clang_lto_plugin_name}) for LTO.") + endif() + + if(CMAKE_CLANG_LD AND CMAKE_CLANG_AR AND CMAKE_CLANG_NM AND CMAKE_CLANG_RANLIB) + message(STATUS "Found CLANG/LLVM's binutils for LTO: ${CMAKE_CLANG_AR}, ${CMAKE_CLANG_RANLIB}") + else() + message(STATUS "Could NOT find CLANG/LLVM's binutils (ar, ranlib, nm) for LTO.") + endif() + + unset(clang_lto_plugin_name) + unset(clang_libdir) + unset(clang_bindir_valid) + unset(clang_bindir) + unset(clang_search_dirs) + endif() + + if((CLANG_LTO_PLUGIN AND CMAKE_LD_GOLD) AND + (CMAKE_CLANG_LD AND CMAKE_CLANG_AR AND CMAKE_CLANG_NM AND CMAKE_CLANG_RANLIB)) + set(CLANG_LTO_AVAILABLE TRUE) + message(STATUS "Link-Time Optimization by CLANG/LLVM is available") + elseif(CMAKE_TOOLCHAIN_FILE AND NOT CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 7.0) + set(CLANG_LTO_AVAILABLE TRUE) + if (NOT CMAKE_CLANG_AR) + set(CMAKE_CLANG_AR ${CMAKE_AR}) + endif() + if (NOT CMAKE_CLANG_NM) + set(CMAKE_CLANG_NM ${CMAKE_NM}) + endif() + if (NOT CMAKE_CLANG_RANLIB) + set(CMAKE_CLANG_RANLIB ${CMAKE_RANLIB }) + endif() + message(STATUS "Assume Link-Time Optimization by CLANG/LLVM is available via ${CMAKE_TOOLCHAIN_FILE}") + else() + set(CLANG_LTO_AVAILABLE FALSE) + message(STATUS "Link-Time Optimization by CLANG/LLVM is NOT available") + endif() +endif() + +# Perform build type specific configuration. +option(ENABLE_BACKTRACE "Enable output of fiber backtrace information in 'show + fiber' administrative command. Only works on x86 architectures, if compiled + with gcc. If GNU binutils and binutils-dev libraries are installed, backtrace + is output with resolved function (symbol) names. Otherwise only frame + addresses are printed." OFF) + +set(HAVE_BFD False) +if(ENABLE_BACKTRACE) + if(NOT (X86_32 OR X86_64) OR NOT CMAKE_COMPILER_IS_GNU${CMAKE_PRIMARY_LANG}) + # We only know this option to work with gcc + message(FATAL_ERROR "ENABLE_BACKTRACE option is set but the system + is not x86 based (${CMAKE_SYSTEM_PROCESSOR}) or the compiler + is not GNU GCC (${CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER}).") + endif() + # Use GNU bfd if present. + find_library(BFD_LIBRARY NAMES libbfd.a) + if(BFD_LIBRARY) + check_library_exists(${BFD_LIBRARY} bfd_init "" HAVE_BFD_LIB) + endif() + find_library(IBERTY_LIBRARY NAMES libiberty.a) + if(IBERTY_LIBRARY) + check_library_exists(${IBERTY_LIBRARY} cplus_demangle "" HAVE_IBERTY_LIB) + endif() + set(CMAKE_REQUIRED_DEFINITIONS -DPACKAGE=${PACKAGE} -DPACKAGE_VERSION=${PACKAGE_VERSION}) + check_include_files(bfd.h HAVE_BFD_H) + set(CMAKE_REQUIRED_DEFINITIONS) + find_package(ZLIB) + if(HAVE_BFD_LIB AND HAVE_BFD_H AND HAVE_IBERTY_LIB AND ZLIB_FOUND) + set(HAVE_BFD ON) + set(BFD_LIBRARIES ${BFD_LIBRARY} ${IBERTY_LIBRARY} ${ZLIB_LIBRARIES}) + find_package_message(BFD_LIBRARIES "Found libbfd and dependencies" + ${BFD_LIBRARIES}) + if(TARGET_OS_FREEBSD AND NOT TARGET_OS_DEBIAN_FREEBSD) + set(BFD_LIBRARIES ${BFD_LIBRARIES} iconv) + endif() + endif() +endif() + +macro(setup_compile_flags) + # LY: save initial C/CXX flags + if(NOT INITIAL_CMAKE_FLAGS_SAVED) + if(MSVC) + string(REGEX REPLACE "^(.*)(/EHsc)( *)(.*)$" "\\1/EHs\\3\\4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") + endif() + set(INITIAL_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} CACHE STRING "Initial CMake's flags" FORCE) + set(INITIAL_CMAKE_C_FLAGS ${CMAKE_C_FLAGS} CACHE STRING "Initial CMake's flags" FORCE) + set(INITIAL_CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} CACHE STRING "Initial CMake's flags" FORCE) + set(INITIAL_CMAKE_SHARED_LINKER_FLAGS ${CMAKE_SHARED_LINKER_FLAGS} CACHE STRING "Initial CMake's flags" FORCE) + set(INITIAL_CMAKE_STATIC_LINKER_FLAGS ${CMAKE_STATIC_LINKER_FLAGS} CACHE STRING "Initial CMake's flags" FORCE) + set(INITIAL_CMAKE_MODULE_LINKER_FLAGS ${CMAKE_MODULE_LINKER_FLAGS} CACHE STRING "Initial CMake's flags" FORCE) + set(INITIAL_CMAKE_FLAGS_SAVED TRUE CACHE INTERNAL "State of initial CMake's flags" FORCE) + endif() + + # LY: reset C/CXX flags + set(CXX_FLAGS ${INITIAL_CMAKE_CXX_FLAGS}) + set(C_FLAGS ${INITIAL_CMAKE_C_FLAGS}) + set(EXE_LINKER_FLAGS ${INITIAL_CMAKE_EXE_LINKER_FLAGS}) + set(SHARED_LINKER_FLAGS ${INITIAL_CMAKE_SHARED_LINKER_FLAGS}) + set(STATIC_LINKER_FLAGS ${INITIAL_CMAKE_STATIC_LINKER_FLAGS}) + set(MODULE_LINKER_FLAGS ${INITIAL_CMAKE_MODULE_LINKER_FLAGS}) + + if(CC_HAS_FEXCEPTIONS) + add_compile_flags("C;CXX" "-fexceptions") + endif() + if(CC_HAS_FCXX_EXCEPTIONS) + add_compile_flags("CXX" "-fcxx-exceptions -frtti") + endif() + + # In C a global variable without a storage specifier (static/extern) and + # without an initialiser is called a ’tentative definition’. The + # language permits multiple tentative definitions in the single + # translation unit; i.e. int foo; int foo; is perfectly ok. GNU + # toolchain goes even further, allowing multiple tentative definitions + # in *different* translation units. Internally, variables introduced via + # tentative definitions are implemented as ‘common’ symbols. Linker + # permits multiple definitions if they are common symbols, and it picks + # one arbitrarily for inclusion in the binary being linked. + # + # -fno-common forces GNU toolchain to behave in a more + # standard-conformant way in respect to tentative definitions and it + # prevents common symbols generation. Since we are a cross-platform + # project it really makes sense. There are toolchains that don’t + # implement GNU style handling of the tentative definitions and there + # are platforms lacking proper support for common symbols (osx). + if(CC_HAS_FNO_COMMON) + add_compile_flags("C;CXX" "-fno-common") + endif() + + if(CC_HAS_GGDB) + add_compile_flags("C;CXX" "-ggdb") + endif() + + if(CC_HAS_WNO_UNKNOWN_PRAGMAS AND NOT HAVE_OPENMP) + add_compile_flags("C;CXX" -Wno-unknown-pragmas) + endif() + + if(CC_HAS_SECTIONS) + add_compile_flags("C;CXX" -ffunction-sections -fdata-sections) + elseif(MSVC) + add_compile_flags("C;CXX" /Gy) + endif() + + # We must set -fno-omit-frame-pointer here, since we rely + # on frame pointer when getting a backtrace, and it must + # be used consistently across all object files. + # The same reasoning applies to -fno-stack-protector switch. + if(ENABLE_BACKTRACE) + if(CC_HAS_FNO_OMIT_FRAME_POINTER) + add_compile_flags("C;CXX" "-fno-omit-frame-pointer") + endif() + endif() + + if(MSVC) + if (MSVC_VERSION LESS 1900) + message(FATAL_ERROR "At least \"Microsoft C/C++ Compiler\" version 19.0.24234.1 (Visual Studio 2015 Update 3) is required.") + endif() + add_compile_flags("CXX" "/Zc:__cplusplus") + add_compile_flags("C;CXX" "/W4") + add_compile_flags("C;CXX" "/utf-8") + else() + if(CC_HAS_WALL) + add_compile_flags("C;CXX" "-Wall") + endif() + if(CC_HAS_WEXTRA) + add_compile_flags("C;CXX" "-Wextra") + endif() + endif() + + if(CMAKE_COMPILER_IS_GNU${CMAKE_PRIMARY_LANG} + AND CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 5) + # G++ bug. http://gcc.gnu.org/bugzilla/show_bug.cgi?id=31488 + add_compile_flags("CXX" "-Wno-invalid-offsetof") + endif() + + add_definitions("-D__STDC_FORMAT_MACROS=1") + add_definitions("-D__STDC_LIMIT_MACROS=1") + add_definitions("-D__STDC_CONSTANT_MACROS=1") + add_definitions("-D_HAS_EXCEPTIONS=1") + + # Only add -Werror if it's a debug build, done by developers. + # Release builds should not cause extra trouble. + if(CC_HAS_WERROR AND (CI OR CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE STREQUAL "Debug")) + if(MSVC) + add_compile_flags("C;CXX" "/WX") + elseif(CMAKE_COMPILER_IS_CLANG) + if (NOT CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 6) + add_compile_flags("C;CXX" "-Werror") + endif() + elseif(CMAKE_COMPILER_IS_GNUCC) + if (NOT CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 6) + add_compile_flags("C;CXX" "-Werror") + endif() + else() + add_compile_flags("C;CXX" "-Werror") + endif() + endif() + + if(HAVE_OPENMP) + add_compile_flags("C;CXX" "-fopenmp") + endif() + + if (ENABLE_ASAN) + add_compile_flags("C;CXX" -fsanitize=address) + endif() + + if(ENABLE_GCOV) + if(NOT HAVE_GCOV) + message(FATAL_ERROR + "ENABLE_GCOV option requested but gcov library is not found") + endif() + + add_compile_flags("C;CXX" "-fprofile-arcs" "-ftest-coverage") + set(EXE_LINKER_FLAGS "${EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") + set(SHARED_LINKER_FLAGS "${SHARED_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") + set(MODULE_LINKER_FLAGS "${MODULE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage") + # add_library(gcov SHARED IMPORTED) + endif() + + if(ENABLE_GPROF) + add_compile_flags("C;CXX" "-pg") + endif() + + if(CMAKE_COMPILER_IS_GNUCC AND LTO_ENABLED) + add_compile_flags("C;CXX" ${GCC_LTO_CFLAGS}) + set(EXE_LINKER_FLAGS "${EXE_LINKER_FLAGS} ${GCC_LTO_CFLAGS} -fverbose-asm -fwhole-program") + set(SHARED_LINKER_FLAGS "${SHARED_LINKER_FLAGS} ${GCC_LTO_CFLAGS} -fverbose-asm") + set(MODULE_LINKER_FLAGS "${MODULE_LINKER_FLAGS} ${GCC_LTO_CFLAGS} -fverbose-asm") + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5) + # Pass the same optimization flags to the linker + set(compile_flags "${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UPPERCASE}}") + set(EXE_LINKER_FLAGS "${EXE_LINKER_FLAGS} ${compile_flags}") + set(SHARED_LINKER_FLAGS "${SHARED_LINKER_FLAGS} ${compile_flags}") + set(MODULE_LINKER_FLAGS "${MODULE_LINKER_FLAGS} ${compile_flags}") + unset(compile_flags) + else() + add_compile_flags("CXX" "-flto-odr-type-merging") + endif() + endif() + + if(MSVC AND LTO_ENABLED) + add_compile_flags("C;CXX" "/GL") + foreach(linkmode IN ITEMS EXE SHARED STATIC MODULE) + set(${linkmode}_LINKER_FLAGS "${${linkmode}_LINKER_FLAGS} /LTCG") + string(REGEX REPLACE "^(.*)(/INCREMENTAL:NO *)(.*)$" "\\1\\3" ${linkmode}_LINKER_FLAGS "${${linkmode}_LINKER_FLAGS}") + string(REGEX REPLACE "^(.*)(/INCREMENTAL:YES *)(.*)$" "\\1\\3" ${linkmode}_LINKER_FLAGS "${${linkmode}_LINKER_FLAGS}") + string(REGEX REPLACE "^(.*)(/INCREMENTAL *)(.*)$" "\\1\\3" ${linkmode}_LINKER_FLAGS "${${linkmode}_LINKER_FLAGS}") + string(STRIP "${${linkmode}_LINKER_FLAGS}" ${linkmode}_LINKER_FLAGS) + foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES ITEMS Release MinSizeRel RelWithDebInfo Debug) + string(TOUPPER "${config}" config_uppercase) + if(DEFINED "CMAKE_${linkmode}_LINKER_FLAGS_${config_uppercase}") + string(REGEX REPLACE "^(.*)(/INCREMENTAL:NO *)(.*)$" "\\1\\3" altered_flags "${CMAKE_${linkmode}_LINKER_FLAGS_${config_uppercase}}") + string(REGEX REPLACE "^(.*)(/INCREMENTAL:YES *)(.*)$" "\\1\\3" altered_flags "${altered_flags}") + string(REGEX REPLACE "^(.*)(/INCREMENTAL *)(.*)$" "\\1\\3" altered_flags "${altered_flags}") + string(STRIP "${altered_flags}" altered_flags) + if(NOT "${altered_flags}" STREQUAL "${CMAKE_${linkmode}_LINKER_FLAGS_${config_uppercase}}") + set(CMAKE_${linkmode}_LINKER_FLAGS_${config_uppercase} "${altered_flags}" CACHE STRING "Altered: '/INCREMENTAL' removed for LTO" FORCE) + endif() + endif() + endforeach(config) + endforeach(linkmode) + unset(linkmode) + + foreach(config IN LISTS CMAKE_CONFIGURATION_TYPES ITEMS Release MinSizeRel RelWithDebInfo) + foreach(lang IN ITEMS C CXX) + string(TOUPPER "${config}" config_uppercase) + if(DEFINED "CMAKE_${lang}_FLAGS_${config_uppercase}") + string(REPLACE "/O2" "/Ox" altered_flags "${CMAKE_${lang}_FLAGS_${config_uppercase}}") + if(NOT "${altered_flags}" STREQUAL "${CMAKE_${lang}_FLAGS_${config_uppercase}}") + set(CMAKE_${lang}_FLAGS_${config_uppercase} "${altered_flags}" CACHE STRING "Altered: '/O2' replaced by '/Ox' for LTO" FORCE) + endif() + endif() + unset(config_uppercase) + endforeach(lang) + endforeach(config) + unset(altered_flags) + unset(lang) + unset(config) + endif() + + if(CMAKE_COMPILER_IS_CLANG AND OSX_ARCHITECTURES) + set(EXE_LINKER_FLAGS "${EXE_LINKER_FLAGS} -Wl,-keep_dwarf_unwind") + set(SHARED_LINKER_FLAGS "${SHARED_LINKER_FLAGS} -Wl,-keep_dwarf_unwind") + set(MODULE_LINKER_FLAGS "${MODULE_LINKER_FLAGS} -Wl,-keep_dwarf_unwind") + endif() + + if(CMAKE_COMPILER_IS_CLANG AND LTO_ENABLED) + if(CMAKE_${CMAKE_PRIMARY_LANG}_COMPILER_VERSION VERSION_LESS 3.9) + set(CLANG_LTO_FLAG "-flto") + else() + set(CLANG_LTO_FLAG "-flto=thin") + endif() + add_compile_flags("C;CXX" ${CLANG_LTO_FLAG}) + set(EXE_LINKER_FLAGS "${EXE_LINKER_FLAGS} ${CLANG_LTO_FLAG} -fverbose-asm -fwhole-program") + set(SHARED_LINKER_FLAGS "${SHARED_LINKER_FLAGS} ${CLANG_LTO_FLAG} -fverbose-asm") + set(MODULE_LINKER_FLAGS "${MODULE_LINKER_FLAGS} ${CLANG_LTO_FLAG} -fverbose-asm") + endif() + + # LY: push C/CXX flags into the cache + set(CMAKE_CXX_FLAGS ${CXX_FLAGS} CACHE STRING "Flags used by the C++ compiler during all build types" FORCE) + set(CMAKE_C_FLAGS ${C_FLAGS} CACHE STRING "Flags used by the C compiler during all build types" FORCE) + set(CMAKE_EXE_LINKER_FLAGS ${EXE_LINKER_FLAGS} CACHE STRING "Flags used by the linker" FORCE) + set(CMAKE_SHARED_LINKER_FLAGS ${SHARED_LINKER_FLAGS} CACHE STRING "Flags used by the linker during the creation of dll's" FORCE) + set(CMAKE_STATIC_LINKER_FLAGS ${STATIC_LINKER_FLAGS} CACHE STRING "Flags used by the linker during the creation of static libraries" FORCE) + set(CMAKE_MODULE_LINKER_FLAGS ${MODULE_LINKER_FLAGS} CACHE STRING "Flags used by the linker during the creation of modules" FORCE) + unset(CXX_FLAGS) + unset(C_FLAGS) + unset(EXE_LINKER_FLAGS) + unset(SHARED_LINKER_FLAGS) + unset(STATIC_LINKER_FLAGS) + unset(MODULE_LINKER_FLAGS) +endmacro(setup_compile_flags) + +# determine library for for std::filesystem +set(LIBCXX_FILESYSTEM "") +if(CMAKE_COMPILER_IS_GNUCXX) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 9.0) + set(LIBCXX_FILESYSTEM "stdc++fs") + endif() +elseif (CMAKE_COMPILER_IS_CLANG) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0) + set(LIBCXX_FILESYSTEM "c++experimental") + else() + set(LIBCXX_FILESYSTEM "stdc++fs") + endif() +endif() + +cmake_policy(POP) diff --git a/contrib/db/libmdbx/cmake/profile.cmake b/contrib/db/libmdbx/cmake/profile.cmake new file mode 100644 index 00000000..6507e8dd --- /dev/null +++ b/contrib/db/libmdbx/cmake/profile.cmake @@ -0,0 +1,45 @@ +## Copyright (c) 2012-2019 Leonid Yuriev . +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## + +cmake_minimum_required(VERSION 3.8.2) +cmake_policy(PUSH) +cmake_policy(VERSION 3.8.2) + +include(CheckLibraryExists) +check_library_exists(gcov __gcov_flush "" HAVE_GCOV) + +option(ENABLE_GCOV + "Enable integration with gcov, a code coverage program" OFF) + +option(ENABLE_GPROF + "Enable integration with gprof, a performance analyzing tool" OFF) + +if(CMAKE_CXX_COMPILER_LOADED) + include(CheckIncludeFileCXX) + check_include_file_cxx(valgrind/memcheck.h HAVE_VALGRIND_MEMCHECK_H) +else() + include(CheckIncludeFile) + check_include_file(valgrind/memcheck.h HAVE_VALGRIND_MEMCHECK_H) +endif() + +option(MDBX_USE_VALGRIND "Enable integration with valgrind, a memory analyzing tool" OFF) +if(MDBX_USE_VALGRIND AND NOT HAVE_VALGRIND_MEMCHECK_H) + message(FATAL_ERROR "MDBX_USE_VALGRIND option is set but valgrind/memcheck.h is not found") +endif() + +option(ENABLE_ASAN + "Enable AddressSanitizer, a fast memory error detector based on compiler instrumentation" OFF) + +cmake_policy(POP) diff --git a/contrib/db/libmdbx/cmake/utils.cmake b/contrib/db/libmdbx/cmake/utils.cmake new file mode 100644 index 00000000..c31f53cd --- /dev/null +++ b/contrib/db/libmdbx/cmake/utils.cmake @@ -0,0 +1,183 @@ +## Copyright (c) 2012-2019 Leonid Yuriev . +## +## Licensed under the Apache License, Version 2.0 (the "License"); +## you may not use this file except in compliance with the License. +## You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## + +cmake_minimum_required(VERSION 3.8.2) +cmake_policy(PUSH) +cmake_policy(VERSION 3.8.2) + +macro(add_compile_flags langs) + foreach(_lang ${langs}) + string(REPLACE ";" " " _flags "${ARGN}") + if(CMAKE_CXX_COMPILER_LOADED AND _lang STREQUAL "CXX") + set("${_lang}_FLAGS" "${${_lang}_FLAGS} ${_flags}") + endif() + if(CMAKE_C_COMPILER_LOADED AND _lang STREQUAL "C") + set("${_lang}_FLAGS" "${${_lang}_FLAGS} ${_flags}") + endif() + endforeach() + unset(_lang) + unset(_flags) +endmacro(add_compile_flags) + +macro(set_source_files_compile_flags) + foreach(file ${ARGN}) + get_filename_component(_file_ext ${file} EXT) + set(_lang "") + if("${_file_ext}" STREQUAL ".m") + set(_lang OBJC) + # CMake believes that Objective C is a flavor of C++, not C, + # and uses g++ compiler for .m files. + # LANGUAGE property forces CMake to use CC for ${file} + set_source_files_properties(${file} PROPERTIES LANGUAGE C) + elseif("${_file_ext}" STREQUAL ".mm") + set(_lang OBJCXX) + endif() + + if(_lang) + get_source_file_property(_flags ${file} COMPILE_FLAGS) + if("${_flags}" STREQUAL "NOTFOUND") + set(_flags "${CMAKE_${_lang}_FLAGS}") + else() + set(_flags "${_flags} ${CMAKE_${_lang}_FLAGS}") + endif() + # message(STATUS "Set (${file} ${_flags}") + set_source_files_properties(${file} PROPERTIES COMPILE_FLAGS + "${_flags}") + endif() + endforeach() + unset(_file_ext) + unset(_lang) +endmacro(set_source_files_compile_flags) + +macro(fetch_version name version_file) + set(${name}_VERSION "") + set(${name}_GIT_DESCRIBE "") + set(${name}_GIT_TIMESTAMP "") + set(${name}_GIT_TREE "") + set(${name}_GIT_COMMIT "") + set(${name}_GIT_REVISION 0) + set(${name}_GIT_VERSION "") + if(GIT) + execute_process(COMMAND ${GIT} describe --tags --long --dirty=-dirty + OUTPUT_VARIABLE ${name}_GIT_DESCRIBE + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE rc) + if(rc OR "${name}_GIT_DESCRIBE" STREQUAL "") + message(FATAL_ERROR "Please fetch tags and/or install latest version of git ('describe --tags --long --dirty' failed)") + endif() + + execute_process(COMMAND ${GIT} show --no-patch --format=%cI HEAD + OUTPUT_VARIABLE ${name}_GIT_TIMESTAMP + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE rc) + if(rc OR "${name}_GIT_TIMESTAMP" STREQUAL "%cI") + execute_process(COMMAND ${GIT} show --no-patch --format=%ci HEAD + OUTPUT_VARIABLE ${name}_GIT_TIMESTAMP + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE rc) + if(rc OR "${name}_GIT_TIMESTAMP" STREQUAL "%ci") + message(FATAL_ERROR "Please install latest version of git ('show --no-patch --format=%cI HEAD' failed)") + endif() + endif() + + execute_process(COMMAND ${GIT} show --no-patch --format=%T HEAD + OUTPUT_VARIABLE ${name}_GIT_TREE + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE rc) + if(rc OR "${name}_GIT_TREE" STREQUAL "") + message(FATAL_ERROR "Please install latest version of git ('show --no-patch --format=%T HEAD' failed)") + endif() + + execute_process(COMMAND ${GIT} show --no-patch --format=%H HEAD + OUTPUT_VARIABLE ${name}_GIT_COMMIT + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE rc) + if(rc OR "${name}_GIT_COMMIT" STREQUAL "") + message(FATAL_ERROR "Please install latest version of git ('show --no-patch --format=%H HEAD' failed)") + endif() + + execute_process(COMMAND ${GIT} rev-list --count --no-merges HEAD + OUTPUT_VARIABLE ${name}_GIT_REVISION + OUTPUT_STRIP_TRAILING_WHITESPACE + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE rc) + if(rc OR "${name}_GIT_REVISION" STREQUAL "") + message(FATAL_ERROR "Please install latest version of git ('rev-list --count --no-merges HEAD' failed)") + endif() + + string(REGEX MATCH "^(v)?([0-9]+)\\.([0-9]+)\\.([0-9]+)(.*)?" git_version_valid "${${name}_GIT_DESCRIBE}") + if(git_version_valid) + string(REGEX REPLACE "^(v)?([0-9]+)\\.([0-9]+)\\.([0-9]+)(.*)?" "\\2;\\3;\\4" ${name}_GIT_VERSION ${${name}_GIT_DESCRIBE}) + else() + string(REGEX MATCH "^(v)?([0-9]+)\\.([0-9]+)(.*)?" git_version_valid "${${name}_GIT_DESCRIBE}") + if(git_version_valid) + string(REGEX REPLACE "^(v)?([0-9]+)\\.([0-9]+)(.*)?" "\\2;\\3;0" ${name}_GIT_VERSION ${${name}_GIT_DESCRIBE}) + else() + message(AUTHOR_WARNING "Bad ${name} version \"${${name}_GIT_DESCRIBE}\"; falling back to 0.0.0 (have you made an initial release?)") + set(${name}_GIT_VERSION "0;0;0") + endif() + endif() + endif() + + if(NOT ${name}_GIT_VERSION OR NOT ${name}_GIT_TIMESTAMP OR NOT ${name}_GIT_REVISION) + message(WARNING "Unable to retrive ${name} version from git.") + set(${name}_GIT_VERSION "0;0;0;0") + set(${name}_GIT_TIMESTAMP "") + set(${name}_GIT_REVISION 0) + + # Try to get version from VERSION file + if(EXISTS "${version_file}") + file(STRINGS "${version_file}" ${name}_VERSION) + endif() + + if(NOT ${name}_VERSION) + message(WARNING "Unable to retrive ${name} version from \"${version_file}\" file.") + set(${name}_VERSION_LIST ${${name}_GIT_VERSION}) + string(REPLACE ";" "." ${name}_VERSION "${${name}_GIT_VERSION}") + else() + string(REPLACE "." ";" ${name}_VERSION_LIST ${${name}_VERSION}) + endif() + + else() + list(APPEND ${name}_GIT_VERSION ${${name}_GIT_REVISION}) + set(${name}_VERSION_LIST ${${name}_GIT_VERSION}) + string(REPLACE ";" "." ${name}_VERSION "${${name}_GIT_VERSION}") + endif() + + list(GET ${name}_VERSION_LIST 0 "${name}_VERSION_MAJOR") + list(GET ${name}_VERSION_LIST 1 "${name}_VERSION_MINOR") + list(GET ${name}_VERSION_LIST 2 "${name}_VERSION_RELEASE") + list(GET ${name}_VERSION_LIST 3 "${name}_VERSION_REVISION") + + set(${name}_VERSION_MAJOR ${${name}_VERSION_MAJOR} PARENT_SCOPE) + set(${name}_VERSION_MINOR ${${name}_VERSION_MINOR} PARENT_SCOPE) + set(${name}_VERSION_RELEASE ${${name}_VERSION_RELEASE} PARENT_SCOPE) + set(${name}_VERSION_REVISION ${${name}_VERSION_REVISION} PARENT_SCOPE) + set(${name}_VERSION ${${name}_VERSION} PARENT_SCOPE) + + set(${name}_GIT_DESCRIBE ${${name}_GIT_DESCRIBE} PARENT_SCOPE) + set(${name}_GIT_TIMESTAMP ${${name}_GIT_TIMESTAMP} PARENT_SCOPE) + set(${name}_GIT_TREE ${${name}_GIT_TREE} PARENT_SCOPE) + set(${name}_GIT_COMMIT ${${name}_GIT_COMMIT} PARENT_SCOPE) + set(${name}_GIT_REVISION ${${name}_GIT_REVISION} PARENT_SCOPE) + set(${name}_GIT_VERSION ${${name}_GIT_VERSION} PARENT_SCOPE) +endmacro(fetch_version) + +cmake_policy(POP) diff --git a/contrib/db/libmdbx/example/CMakeLists.txt b/contrib/db/libmdbx/example/CMakeLists.txt new file mode 100644 index 00000000..d3e56e82 --- /dev/null +++ b/contrib/db/libmdbx/example/CMakeLists.txt @@ -0,0 +1,6 @@ +set(TARGET mdbx_example) +project(${TARGET}) + +add_executable(${TARGET} example-mdbx.c) + +target_link_libraries(${TARGET} mdbx) diff --git a/contrib/db/libmdbx/example/README.md b/contrib/db/libmdbx/example/README.md new file mode 100644 index 00000000..b819cf4a --- /dev/null +++ b/contrib/db/libmdbx/example/README.md @@ -0,0 +1 @@ +See [example-mdbx.c](example-mdbx.c) as an example of using _libmdbx_, and do a line-by-line comparison of it with the [sample-bdb.txt](sample-bdb.txt) file. diff --git a/contrib/db/libmdbx/example/example-mdbx.c b/contrib/db/libmdbx/example/example-mdbx.c new file mode 100644 index 00000000..1d25ef6f --- /dev/null +++ b/contrib/db/libmdbx/example/example-mdbx.c @@ -0,0 +1,112 @@ +/* MDBX usage examle + * + * Do a line-by-line comparison of this and sample-bdb.txt + */ + +/* + * Copyright 2015-2019 Leonid Yuriev . + * Copyright 2017 Ilya Shipitsin . + * Copyright 2012-2015 Howard Chu, Symas Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "mdbx.h" +#include +#include + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + int rc; + MDBX_env *env = NULL; + MDBX_dbi dbi = 0; + MDBX_val key, data; + MDBX_txn *txn = NULL; + MDBX_cursor *cursor = NULL; + char sval[32]; + + rc = mdbx_env_create(&env); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_env_create: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_env_open(env, "./example-db", + MDBX_NOSUBDIR | MDBX_COALESCE | MDBX_LIFORECLAIM, 0664); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_env_open: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + rc = mdbx_txn_begin(env, NULL, 0, &txn); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_txn_begin: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_dbi_open(txn, NULL, 0, &dbi); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_dbi_open: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + key.iov_len = sizeof(int); + key.iov_base = sval; + data.iov_len = sizeof(sval); + data.iov_base = sval; + + sprintf(sval, "%03x %d foo bar", 32, 3141592); + rc = mdbx_put(txn, dbi, &key, &data, 0); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_put: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_txn_commit(txn); + if (rc) { + fprintf(stderr, "mdbx_txn_commit: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + txn = NULL; + + rc = mdbx_txn_begin(env, NULL, MDBX_RDONLY, &txn); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_txn_begin: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + rc = mdbx_cursor_open(txn, dbi, &cursor); + if (rc != MDBX_SUCCESS) { + fprintf(stderr, "mdbx_cursor_open: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + int found = 0; + while ((rc = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) == 0) { + printf("key: %p %.*s, data: %p %.*s\n", key.iov_base, (int)key.iov_len, + (char *)key.iov_base, data.iov_base, (int)data.iov_len, + (char *)data.iov_base); + found += 1; + } + if (rc != MDBX_NOTFOUND || found == 0) { + fprintf(stderr, "mdbx_cursor_get: (%d) %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } else { + rc = MDBX_SUCCESS; + } +bailout: + if (cursor) + mdbx_cursor_close(cursor); + if (txn) + mdbx_txn_abort(txn); + if (dbi) + mdbx_dbi_close(env, dbi); + if (env) + mdbx_env_close(env); + return (rc != MDBX_SUCCESS) ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/example/sample-bdb.txt b/contrib/db/libmdbx/example/sample-bdb.txt new file mode 100644 index 00000000..5c89540a --- /dev/null +++ b/contrib/db/libmdbx/example/sample-bdb.txt @@ -0,0 +1,77 @@ +/* BerkeleyDB toy/sample + * + * Do a line-by-line comparison of this and example-mdbx.c + */ + +/* + * Copyright 2015-2019 Leonid Yuriev . + * Copyright 2012-2015 Howard Chu, Symas Corp. + * Copyright 2015,2016 Peter-Service R&D LLC. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include +#include +#include + +int main(int argc,char * argv[]) +{ + int rc; + DB_ENV *env; + DB *dbi; + DBT key, data; + DB_TXN *txn; + DBC *cursor; + char sval[32], kval[32]; + + /* Note: Most error checking omitted for simplicity */ + +#define FLAGS (DB_INIT_LOCK|DB_INIT_LOG|DB_INIT_TXN|DB_INIT_MPOOL|DB_CREATE|DB_THREAD) + rc = db_env_create(&env, 0); + rc = env->open(env, "./testdb", FLAGS, 0664); + rc = db_create(&dbi, env, 0); + rc = env->txn_begin(env, NULL, &txn, 0); + rc = dbi->open(dbi, txn, "test.bdb", NULL, DB_BTREE, DB_CREATE, 0664); + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + key.size = sizeof(int); + key.data = sval; + data.size = sizeof(sval); + data.data = sval; + + sprintf(sval, "%03x %d foo bar", 32, 3141592); + rc = dbi->put(dbi, txn, &key, &data, 0); + rc = txn->commit(txn, 0); + if (rc) { + fprintf(stderr, "txn->commit: (%d) %s\n", rc, db_strerror(rc)); + goto leave; + } + rc = env->txn_begin(env, NULL, &txn, 0); + rc = dbi->cursor(dbi, txn, &cursor, 0); + key.flags = DB_DBT_USERMEM; + key.data = kval; + key.ulen = sizeof(kval); + data.flags = DB_DBT_USERMEM; + data.data = sval; + data.ulen = sizeof(sval); + while ((rc = cursor->c_get(cursor, &key, &data, DB_NEXT)) == 0) { + printf("key: %p %.*s, data: %p %.*s\n", + key.data, (int) key.size, (char *) key.data, + data.data, (int) data.size, (char *) data.data); + } + rc = cursor->c_close(cursor); + rc = txn->abort(txn); +leave: + rc = dbi->close(dbi, 0); + rc = env->close(env, 0); + return rc; +} diff --git a/contrib/db/libmdbx/mdbx.h b/contrib/db/libmdbx/mdbx.h new file mode 100644 index 00000000..a52335b3 --- /dev/null +++ b/contrib/db/libmdbx/mdbx.h @@ -0,0 +1,3497 @@ +/**** BRIEFLY ****************************************************************** + * + * libmdbx is superior to LMDB (https://bit.ly/26ts7tL) in terms of features + * and reliability, not inferior in performance. In comparison to LMDB, libmdbx + * makes many things just work perfectly, not silently and catastrophically + * break down. libmdbx supports Linux, Windows, MacOS, FreeBSD and other + * systems compliant with POSIX.1-2008. + * + * Look below for API description, for other information (build, embedding and + * amalgamation, improvements over LMDB, benchmarking, etc) please refer to + * README.md at https://abf.io/erthink/libmdbx. + * + * --- + * + * The next version is under active non-public development and will be released + * as MithrilDB and libmithrildb for libraries & packages. Admittedly mythical + * Mithril is resembling silver but being stronger and lighter than steel. + * Therefore MithrilDB is rightly relevant name. + * + * MithrilDB will be radically different from libmdbx by the new database format + * and API based on C++17, as well as the Apache 2.0 License. The goal of this + * revolution is to provide a clearer and robust API, add more features and new + * valuable properties of database. + * + * The Future will (be) Positive. Всё будет хорошо. + * + * + **** INTRODUCTION ************************************************************* + * + * // For the most part, this section is a copy of the corresponding text + * // from LMDB description, but with some edits reflecting the improvements + * // and enhancements were made in MDBX. + * + * MDBX is a Btree-based database management library modeled loosely on the + * BerkeleyDB API, but much simplified. The entire database (aka "environment") + * is exposed in a memory map, and all data fetches return data directly from + * the mapped memory, so no malloc's or memcpy's occur during data fetches. + * As such, the library is extremely simple because it requires no page caching + * layer of its own, and it is extremely high performance and memory-efficient. + * It is also fully transactional with full ACID semantics, and when the memory + * map is read-only, the database integrity cannot be corrupted by stray pointer + * writes from application code. + * + * The library is fully thread-aware and supports concurrent read/write access + * from multiple processes and threads. Data pages use a copy-on-write strategy + * so no active data pages are ever overwritten, which also provides resistance + * to corruption and eliminates the need of any special recovery procedures + * after a system crash. Writes are fully serialized; only one write transaction + * may be active at a time, which guarantees that writers can never deadlock. + * The database structure is multi-versioned so readers run with no locks; + * writers cannot block readers, and readers don't block writers. + * + * Unlike other well-known database mechanisms which use either write-ahead + * transaction logs or append-only data writes, MDBX requires no maintenance + * during operation. Both write-ahead loggers and append-only databases require + * periodic checkpointing and/or compaction of their log or database files + * otherwise they grow without bound. MDBX tracks free pages within the database + * and re-uses them for new write operations, so the database size does not grow + * without bound in normal use. It is worth noting that the "next" version + * libmdbx (MithrilDB) will solve this problem. + * + * The memory map can be used as a read-only or read-write map. It is read-only + * by default as this provides total immunity to corruption. Using read-write + * mode offers much higher write performance, but adds the possibility for stray + * application writes thru pointers to silently corrupt the database. + * Of course if your application code is known to be bug-free (...) then this is + * not an issue. + * + * If this is your first time using a transactional embedded key-value store, + * you may find the "GETTING STARTED" section below to be helpful. + * + * + **** GETTING STARTED ********************************************************** + * + * // This section is based on Bert Hubert's intro "LMDB Semantics", with + * // edits reflecting the improvements and enhancements were made in MDBX. + * // See https://bit.ly/2maejGY for Bert Hubert's original. + * + * Everything starts with an environment, created by mdbx_env_create(). + * Once created, this environment must also be opened with mdbx_env_open(), + * and after use be closed by mdbx_env_close(). At that a non-zero value of the + * last argument "mode" supposes MDBX will create database and directory if ones + * does not exist. In this case the non-zero "mode" argument specifies the file + * mode bits be applied when a new files are created by open() function. + * + * Within that directory, a lock file (aka LCK-file) and a storage file (aka + * DXB-file) will be generated. If you don't want to use a directory, you can + * pass the MDBX_NOSUBDIR option, in which case the path you provided is used + * directly as the DXB-file, and another file with a "-lck" suffix added + * will be used for the LCK-file. + * + * Once the environment is open, a transaction can be created within it using + * mdbx_txn_begin(). Transactions may be read-write or read-only, and read-write + * transactions may be nested. A transaction must only be used by one thread at + * a time. Transactions are always required, even for read-only access. The + * transaction provides a consistent view of the data. + * + * Once a transaction has been created, a database (i.e. key-value space inside + * the environment) can be opened within it using mdbx_dbi_open(). If only one + * database will ever be used in the environment, a NULL can be passed as the + * database name. For named databases, the MDBX_CREATE flag must be used to + * create the database if it doesn't already exist. Also, mdbx_env_set_maxdbs() + * must be called after mdbx_env_create() and before mdbx_env_open() to set the + * maximum number of named databases you want to support. + * + * NOTE: a single transaction can open multiple databases. Generally databases + * should only be opened once, by the first transaction in the process. + * + * Within a transaction, mdbx_get() and mdbx_put() can store single key-value + * pairs if that is all you need to do (but see CURSORS below if you want to do + * more). + * + * A key-value pair is expressed as two MDBX_val structures. This struct that is + * exactly similar to POSIX's struct iovec and has two fields, iov_len and + * iov_base. The data is a void pointer to an array of iov_len bytes. + * (!) The notable difference between MDBX and LMDB is that MDBX support zero + * length keys. + * + * Because MDBX is very efficient (and usually zero-copy), the data returned in + * an MDBX_val structure may be memory-mapped straight from disk. In other words + * look but do not touch (or free() for that matter). Once a transaction is + * closed, the values can no longer be used, so make a copy if you need to keep + * them after that. + * + * + * CURSORS -- To do more powerful things, we must use a cursor. + * + * Within the transaction, a cursor can be created with mdbx_cursor_open(). + * With this cursor we can store/retrieve/delete (multiple) values using + * mdbx_cursor_get(), mdbx_cursor_put(), and mdbx_cursor_del(). + * + * mdbx_cursor_get() positions itself depending on the cursor operation + * requested, and for some operations, on the supplied key. For example, to list + * all key-value pairs in a database, use operation MDBX_FIRST for the first + * call to mdbx_cursor_get(), and MDBX_NEXT on subsequent calls, until the end + * is hit. + * + * To retrieve all keys starting from a specified key value, use MDBX_SET. For + * more cursor operations, see the API description below. + * + * When using mdbx_cursor_put(), either the function will position the cursor + * for you based on the key, or you can use operation MDBX_CURRENT to use the + * current position of the cursor. NOTE that key must then match the current + * position's key. + * + * + * SUMMARIZING THE OPENING + * + * So we have a cursor in a transaction which opened a database in an + * environment which is opened from a filesystem after it was separately + * created. + * + * Or, we create an environment, open it from a filesystem, create a transaction + * within it, open a database within that transaction, and create a cursor + * within all of the above. + * + * Got it? + * + * + * THREADS AND PROCESSES + * + * Do not have open an database twice in the same process at the same time, MDBX + * will track and prevent this. Instead, share the MDBX environment that has + * opened the file across all threads. The reason for this is: + * - When the "Open file description" locks (aka OFD-locks) are not available, + * MDBX uses POSIX locks on files, and these locks have issues if one process + * opens a file multiple times. + * - If a single process opens the same environment multiple times, closing it + * once will remove all the locks held on it, and the other instances will be + * vulnerable to corruption from other processes. + * + For compatibility with LMDB which allows multi-opening, MDBX can be + * configured at runtime by mdbx_setup_debug(MDBX_DBG_LEGACY_MULTIOPEN, ...) + * prior to calling other MDBX funcitons. In this way MDBX will track + * databases opening, detect multi-opening cases and then recover POSIX file + * locks as necessary. However, lock recovery can cause unexpected pauses, + * such as when another process opened the database in exclusive mode before + * the lock was restored - we have to wait until such a process releases the + * database, and so on. + * + * Do not use opened MDBX environment(s) after fork() in a child process(es), + * MDBX will check and prevent this at critical points. Instead, ensure there is + * no open MDBX-instance(s) during fork(), or atleast close it immediately after + * fork() in the child process and reopen if required - for instance by using + * pthread_atfork(). The reason for this is: + * - For competitive consistent reading, MDBX assigns a slot in the shared + * table for each process that interacts with the database. This slot is + * populated with process attributes, including the PID. + * - After fork(), in order to remain connected to a database, the child + * process must have its own such "slot", which can't be assigned in any + * simple and robust way another than the regular. + * - A write transaction from a parent process cannot continue in a child + * process for obvious reasons. + * - Moreover, in a multithreaded process at the fork() moment any number of + * threads could run in critical and/or intermediate sections of MDBX code + * with interaction and/or racing conditions with threads from other + * process(es). For instance: shrinking a database or copying it to a pipe, + * opening or closing environment, begining or finishing a transaction, + * and so on. + * = Therefore, any solution other than simply close database (and reopen if + * necessary) in a child process would be both extreme complicated and so + * fragile. + * + * Also note that a transaction is tied to one thread by default using Thread + * Local Storage. If you want to pass read-only transactions across threads, + * you can use the MDBX_NOTLS option on the environment. Nevertheless, a write + * transaction entirely should only be used in one thread from start to finish. + * MDBX checks this in a reasonable manner and return the MDBX_THREAD_MISMATCH + * error in rules violation. + * + * + * TRANSACTIONS, ROLLBACKS, etc. + * + * To actually get anything done, a transaction must be committed using + * mdbx_txn_commit(). Alternatively, all of a transaction's operations + * can be discarded using mdbx_txn_abort(). + * + * (!) An important difference between MDBX and LMDB is that MDBX required that + * any opened cursors can be reused and must be freed explicitly, regardless + * ones was opened in a read-only or write transaction. The REASON for this is + * eliminates ambiguity which helps to avoid errors such as: use-after-free, + * double-free, i.e. memory corruption and segfaults. + * + * For read-only transactions, obviously there is nothing to commit to storage. + * (!) An another notable difference between MDBX and LMDB is that MDBX make + * handles opened for existing databases immediately available for other + * transactions, regardless this transaction will be aborted or reset. The + * REASON for this is to avoiding the requirement for multiple opening a same + * handles in concurrent read transactions, and tracking of such open but hidden + * handles until the completion of read transactions which opened them. + * + * In addition, as long as a transaction is open, a consistent view of the + * database is kept alive, which requires storage. A read-only transaction that + * no longer requires this consistent view should be terminated (committed or + * aborted) when the view is no longer needed (but see below for an + * optimization). + * + * There can be multiple simultaneously active read-only transactions but only + * one that can write. Once a single read-write transaction is opened, all + * further attempts to begin one will block until the first one is committed or + * aborted. This has no effect on read-only transactions, however, and they may + * continue to be opened at any time. + * + * + * DUPLICATE KEYS + * + * mdbx_get() and mdbx_put() respectively have no and only some support or + * multiple key-value pairs with identical keys. If there are multiple values + * for a key, mdbx_get() will only return the first value. + * + * When multiple values for one key are required, pass the MDBX_DUPSORT flag to + * mdbx_dbi_open(). In an MDBX_DUPSORT database, by default mdbx_put() will not + * replace the value for a key if the key existed already. Instead it will add + * the new value to the key. In addition, mdbx_del() will pay attention to the + * value field too, allowing for specific values of a key to be deleted. + * + * Finally, additional cursor operations become available for traversing through + * and retrieving duplicate values. + * + * + * SOME OPTIMIZATION + * + * If you frequently begin and abort read-only transactions, as an optimization, + * it is possible to only reset and renew a transaction. + * + * mdbx_txn_reset() releases any old copies of data kept around for a read-only + * transaction. To reuse this reset transaction, call mdbx_txn_renew() on it. + * Any cursors in this transaction can also be renewed using mdbx_cursor_renew() + * or freed by mdbx_cursor_close(). + * + * To permanently free a transaction, reset or not, use mdbx_txn_abort(). + * + * + * CLEANING UP + * + * Any created cursors must be closed using mdbx_cursor_close(). It is advisable + * to repeat: + * (!) An important difference between MDBX and LMDB is that MDBX required that + * any opened cursors can be reused and must be freed explicitly, regardless + * ones was opened in a read-only or write transaction. The REASON for this is + * eliminates ambiguity which helps to avoid errors such as: use-after-free, + * double-free, i.e. memory corruption and segfaults. + * + * It is very rarely necessary to close a database handle, and in general they + * should just be left open. When you close a handle, it immediately becomes + * unavailable for all transactions in the environment. Therefore, you should + * avoid closing the handle while at least one transaction is using it. + * + * + * THE FULL API + * + * The full MDBX documentation lists further details below, + * like how to: + * + * - configure database size and automatic size management + * - drop and clean a database + * - detect and report errors + * - optimize (bulk) loading speed + * - (temporarily) reduce robustness to gain even more speed + * - gather statistics about the database + * - define custom sort orders + * - estimate size of range query result + * - double perfomance by LIFO reclaiming on storages with write-back + * - use sequences and canary markers + * - use lack-of-space callback (aka OOM-KICK) + * - use exclusive mode + * + * + **** RESTRICTIONS & CAVEATS *************************************************** + * in addition to those listed for some functions. + * + * - Troubleshooting the LCK-file. + * 1. A broken LCK-file can cause sync issues, including appearance of + * wrong/inconsistent data for readers. When database opened in the + * cooperative read-write mode the LCK-file requires to be mapped to + * memory in read-write access. In this case it is always possible for + * stray/malfunctioned application could writes thru pointers to + * silently corrupt the LCK-file. + * + * Unfortunately, there is no any portable way to prevent such + * corruption, since the LCK-file is updated concurrently by + * multiple processes in a lock-free manner and any locking is + * unwise due to a large overhead. + * + * The "next" version of libmdbx (MithrilDB) will solve this issue. + * + * Workaround: Just make all programs using the database close it; + * the LCK-file is always reset on first open. + * + * 2. Stale reader transactions left behind by an aborted program cause + * further writes to grow the database quickly, and stale locks can + * block further operation. + * MDBX checks for stale readers while opening environment and before + * growth the database. But in some cases, this may not be enough. + * + * Workaround: Check for stale readers periodically, using the + * mdbx_reader_check() function or the mdbx_stat tool. + * + * 3. Stale writers will be cleared automatically by MDBX on supprted + * platforms. But this is platform-specific, especially of + * implementation of shared POSIX-mutexes and support for robust + * mutexes. For instance there are no known issues on Linux, OSX, + * Windows and FreeBSD. + * + * Workaround: Otherwise just make all programs using the database + * close it; the LCK-file is always reset on first open + * of the environment. + * + * - Do not use MDBX databases on remote filesystems, even between processes + * on the same host. This breaks file locks on some platforms, possibly + * memory map sync, and certainly sync between programs on different hosts. + * + * On the other hand, MDBX support the exclusive database operation over + * a network, and cooperative read-only access to the database placed on + * a read-only network shares. + * + * - Do not use opened MDBX_env instance(s) in a child processes after fork(). + * It would be insane to call fork() and any MDBX-functions simultaneously + * from multiple threads. The best way is to prevent the presence of open + * MDBX-instances during fork(). + * + * The MDBX_TXN_CHECKPID build-time option, which is ON by default on + * non-Windows platforms (i.e. where fork() is available), enables PID + * checking at a few critical points. But this does not give any guarantees, + * but only allows you to detect such errors a little sooner. Depending on + * the platform, you should expect an application crash and/or database + * corruption in such cases. + * + * On the other hand, MDBX allow calling mdbx_close_env() in such cases to + * release resources, but no more and in general this is a wrong way. + * + * - There is no pure read-only mode in a normal explicitly way, since + * readers need write access to LCK-file to be ones visible for writer. + * MDBX always tries to open/create LCK-file for read-write, but switches + * to without-LCK mode on appropriate errors (EROFS, EACCESS, EPERM) + * if the read-only mode was requested by the MDBX_RDONLY flag which is + * described below. + * + * The "next" version of libmdbx (MithrilDB) will solve this issue. + * + * - A thread can only use one transaction at a time, plus any nested + * read-write transactions in the non-writemap mode. Each transaction + * belongs to one thread. The MDBX_NOTLS flag changes this for read-only + * transactions. See below. + * + * - Do not have open an MDBX database twice in the same process at the same + * time. By default MDBX prevent this in most cases by tracking databases + * opening and return MDBX_BUSY if anyone LCK-file is already open. + * + * The reason for this is that when the "Open file description" locks (aka + * OFD-locks) are not available, MDBX uses POSIX locks on files, and these + * locks have issues if one process opens a file multiple times. If a single + * process opens the same environment multiple times, closing it once will + * remove all the locks held on it, and the other instances will be + * vulnerable to corruption from other processes. + * + * For compatibility with LMDB which allows multi-opening, MDBX can be + * configured at runtime by mdbx_setup_debug(MDBX_DBG_LEGACY_MULTIOPEN, ...) + * prior to calling other MDBX funcitons. In this way MDBX will track + * databases opening, detect multi-opening cases and then recover POSIX file + * locks as necessary. However, lock recovery can cause unexpected pauses, + * such as when another process opened the database in exclusive mode before + * the lock was restored - we have to wait until such a process releases the + * database, and so on. + * + * - Avoid long-lived transactions, especially in the scenarios with a high + * rate of write transactions. Read transactions prevent reuse of pages + * freed by newer write transactions, thus the database can grow quickly. + * Write transactions prevent other write transactions, since writes are + * serialized. + * + * Understanding the problem of long-lived read transactions requires some + * explanation, but can be difficult for quick perception. So is is + * reasonable to simplify this as follows: + * 1. Garbage collection problem exists in all databases one way or + * another, e.g. VACUUM in PostgreSQL. But in _libmdbx_ it's even more + * discernible because of high transaction rate and intentional + * internals simplification in favor of performance. + * + * 2. MDBX employs Multiversion concurrency control on the Copy-on-Write + * basis, that allows multiple readers runs in parallel with a write + * transaction without blocking. An each write transaction needs free + * pages to put the changed data, that pages will be placed in the new + * b-tree snapshot at commit. MDBX efficiently recycling pages from + * previous created unused snapshots, BUT this is impossible if anyone + * a read transaction use such snapshot. + * + * 3. Thus massive altering of data during a parallel long read operation + * will increase the process's work set and may exhaust entire free + * database space. + * + * A good example of long readers is a hot backup to the slow destination + * or debugging of a client application while retaining an active read + * transaction. LMDB this results in MAP_FULL error and subsequent write + * performance degradation. + * + * MDBX mostly solve "long-lived" readers issue by the lack-of-space callback + * which allow to aborts long readers, and by the MDBX_LIFORECLAIM mode which + * addresses subsequent performance degradation. + * The "next" version of libmdbx (MithrilDB) will completely solve this. + * + * - Avoid suspending a process with active transactions. These would then be + * "long-lived" as above. + * + * The "next" version of libmdbx (MithrilDB) will solve this issue. + * + * - Avoid aborting a process with an active read-only transaction in scenaries + * with high rate of write transactions. The transaction becomes "long-lived" + * as above until a check for stale readers is performed or the LCK-file is + * reset, since the process may not remove it from the lockfile. This does + * not apply to write transactions if the system clears stale writers, see + * above. + * + * - An MDBX database configuration will often reserve considerable unused + * memory address space and maybe file size for future growth. This does + * not use actual memory or disk space, but users may need to understand + * the difference so they won't be scared off. + * + * - The Write Amplification Factor. + * TBD. + * + **** LICENSE AND COPYRUSTING ************************************************** + * + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + * + * --- + * + * This code is derived from "LMDB engine" written by + * Howard Chu (Symas Corporation), which itself derived from btree.c + * written by Martin Hedenfalk. + * + * --- + * + * Portions Copyright 2011-2015 Howard Chu, Symas Corp. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + * + * --- + * + * Portions Copyright (c) 2009, 2010 Martin Hedenfalk + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + **** ACKNOWLEDGEMENTS ********************************************************* + * + * Howard Chu (Symas Corporation) - the author of LMDB, + * from which originated the MDBX in 2015. + * + * Martin Hedenfalk - the author of `btree.c` code, + * which was used for begin development of LMDB. + * + ******************************************************************************/ + +#pragma once +#ifndef LIBMDBX_H +#define LIBMDBX_H + +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable : 4548) /* expression before comma has no effect; \ + expected expression with side - effect */ +#pragma warning(disable : 4530) /* C++ exception handler used, but unwind \ + * semantics are not enabled. Specify /EHsc */ +#pragma warning(disable : 4577) /* 'noexcept' used with no exception handling \ + * mode specified; termination on exception is \ + * not guaranteed. Specify /EHsc */ +#endif /* _MSC_VER (warnings) */ + +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) + +#include +#include +#ifndef __mode_t_defined +typedef unsigned short mode_t; +#endif +typedef HANDLE mdbx_filehandle_t; +typedef DWORD mdbx_pid_t; +typedef DWORD mdbx_tid_t; +#define MDBX_ENODATA ERROR_HANDLE_EOF +#define MDBX_EINVAL ERROR_INVALID_PARAMETER +#define MDBX_EACCESS ERROR_ACCESS_DENIED +#define MDBX_ENOMEM ERROR_OUTOFMEMORY +#define MDBX_EROFS ERROR_FILE_READ_ONLY +#define MDBX_ENOSYS ERROR_NOT_SUPPORTED +#define MDBX_EIO ERROR_WRITE_FAULT +#define MDBX_EPERM ERROR_INVALID_FUNCTION +#define MDBX_EINTR ERROR_CANCELLED +#define MDBX_ENOFILE ERROR_FILE_NOT_FOUND + +#else + +#include /* for error codes */ +#include /* for pthread_t */ +#include /* for pid_t */ +#include /* for truct iovec */ +#define HAVE_STRUCT_IOVEC 1 +typedef int mdbx_filehandle_t; +typedef pid_t mdbx_pid_t; +typedef pthread_t mdbx_tid_t; +#ifdef ENODATA +#define MDBX_ENODATA ENODATA +#else +#define MDBX_ENODATA -1 +#endif +#define MDBX_EINVAL EINVAL +#define MDBX_EACCESS EACCES +#define MDBX_ENOMEM ENOMEM +#define MDBX_EROFS EROFS +#define MDBX_ENOSYS ENOSYS +#define MDBX_EIO EIO +#define MDBX_EPERM EPERM +#define MDBX_EINTR EINTR +#define MDBX_ENOFILE ENOENT + +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +/*----------------------------------------------------------------------------*/ + +#ifndef __has_attribute +#define __has_attribute(x) (0) +#endif + +#ifndef __dll_export +#if defined(_WIN32) || defined(__CYGWIN__) +#if defined(__GNUC__) || __has_attribute(dllexport) +#define __dll_export __attribute__((dllexport)) +#elif defined(_MSC_VER) +#define __dll_export __declspec(dllexport) +#else +#define __dll_export +#endif +#elif defined(__GNUC__) || __has_attribute(__visibility__) +#define __dll_export __attribute__((__visibility__("default"))) +#else +#define __dll_export +#endif +#endif /* __dll_export */ + +#ifndef __dll_import +#if defined(_WIN32) || defined(__CYGWIN__) +#if defined(__GNUC__) || __has_attribute(dllimport) +#define __dll_import __attribute__((dllimport)) +#elif defined(_MSC_VER) +#define __dll_import __declspec(dllimport) +#else +#define __dll_import +#endif +#else +#define __dll_import +#endif +#endif /* __dll_import */ + +/*----------------------------------------------------------------------------*/ + +#define MDBX_VERSION_MAJOR 0 +#define MDBX_VERSION_MINOR 3 + +#ifndef LIBMDBX_API +#if defined(LIBMDBX_EXPORTS) +#define LIBMDBX_API __dll_export +#elif defined(LIBMDBX_IMPORTS) +#define LIBMDBX_API __dll_import +#else +#define LIBMDBX_API +#endif +#endif /* LIBMDBX_API */ + +#ifdef __cplusplus +extern "C" { +#endif + +/**** MDBX version information ************************************************/ + +#if defined(LIBMDBX_IMPORTS) +#define LIBMDBX_VERINFO_API __dll_import +#else +#define LIBMDBX_VERINFO_API __dll_export +#endif /* LIBMDBX_VERINFO_API */ + +typedef struct mdbx_version_info { + uint8_t major; + uint8_t minor; + uint16_t release; + uint32_t revision; + struct /* source info from git */ { + const char *datetime /* committer date, strict ISO-8601 format */; + const char *tree /* commit hash (hexadecimal digits) */; + const char *commit /* tree hash, i.e. digest of the source code */; + const char *describe /* git-describe string */; + } git; + const char *sourcery /* sourcery anchor for pinning */; +} mdbx_version_info; +extern LIBMDBX_VERINFO_API const mdbx_version_info mdbx_version; + +/* MDBX build information. + * WARNING: Some strings could be NULL in case no corresponding information was + * provided at build time (i.e. flags). */ +typedef struct mdbx_build_info { + const char *datetime /* build timestamp (ISO-8601 or __DATE__ __TIME__) */; + const char *target /* cpu/arch-system-config triplet */; + const char *options /* mdbx-related options */; + const char *compiler /* compiler */; + const char *flags /* CFLAGS */; +} mdbx_build_info; +extern LIBMDBX_VERINFO_API const mdbx_build_info mdbx_build; + +#if defined(_WIN32) || defined(_WIN64) +#if !MDBX_BUILD_SHARED_LIBRARY + +/* MDBX internally uses global and thread local storage destructors to + * automatically (de)initialization, releasing reader lock table slots + * and so on. + * + * If MDBX builded as a DLL this is done out-of-the-box by DllEntry() function, + * which called automatically by Windows core with passing corresponding reason + * argument. + * + * Otherwise, if MDBX was builded not as a DLL, some black magic + * may be required depending of Windows version: + * - Modern Windows versions, including Windows Vista and later, provides + * support for "TLS Directory" (e.g .CRT$XL[A-Z] sections in executable + * or dll file). In this case, MDBX capable of doing all automatically, + * and you do not need to call mdbx_dll_handler(). + * - Obsolete versions of Windows, prior to Windows Vista, REQUIRES calling + * mdbx_dll_handler() manually from corresponding DllMain() or WinMain() + * of your DLL or application. + * - This behavior is under control of the MODX_CONFIG_MANUAL_TLS_CALLBACK + * option, which is determined by default according to the target version + * of Windows at build time. + * But you may override MODX_CONFIG_MANUAL_TLS_CALLBACK in special cases. + * + * Therefore, building MDBX as a DLL is recommended for all version of Windows. + * So, if you doubt, just build MDBX as the separate DLL and don't worry. */ + +#ifndef MDBX_CONFIG_MANUAL_TLS_CALLBACK +#if defined(_WIN32_WINNT_VISTA) && WINVER >= _WIN32_WINNT_VISTA +/* As described above mdbx_dll_handler() is NOT needed forWindows Vista + * and later. */ +#define MDBX_CONFIG_MANUAL_TLS_CALLBACK 0 +#else +/* As described above mdbx_dll_handler() IS REQUIRED for Windows versions + * prior to Windows Vista. */ +#define MDBX_CONFIG_MANUAL_TLS_CALLBACK 1 +#endif +#endif /* MDBX_CONFIG_MANUAL_TLS_CALLBACK */ + +#if MDBX_CONFIG_MANUAL_TLS_CALLBACK +void LIBMDBX_API NTAPI mdbx_dll_handler(PVOID module, DWORD reason, + PVOID reserved); +#endif /* MDBX_CONFIG_MANUAL_TLS_CALLBACK */ +#endif /* !MDBX_BUILD_SHARED_LIBRARY */ +#endif /* Windows */ + +/**** OPACITY STRUCTURES ******************************************************/ + +/* Opaque structure for a database environment. + * + * An environment supports multiple key-value databases (aka key-value spaces + * or tables), all residing in the same shared-memory map. */ +typedef struct MDBX_env MDBX_env; + +/* Opaque structure for a transaction handle. + * + * All database operations require a transaction handle. Transactions may be + * read-only or read-write. */ +typedef struct MDBX_txn MDBX_txn; + +/* A handle for an individual database (key-value spaces) in the environment. + * Zero handle is used internally (hidden Garbage Collection DB). + * So, any valid DBI-handle great than 0 and less than or equal MDBX_MAX_DBI. */ +typedef uint32_t MDBX_dbi; +#define MDBX_MAX_DBI UINT32_C(32765) + +/* Opaque structure for navigating through a database */ +typedef struct MDBX_cursor MDBX_cursor; + +/* Generic structure used for passing keys and data in and out of the database. + * + * Values returned from the database are valid only until a subsequent + * update operation, or the end of the transaction. Do not modify or + * free them, they commonly point into the database itself. + * + * Key sizes must be between 0 and mdbx_env_get_maxkeysize() inclusive. + * The same applies to data sizes in databases with the MDBX_DUPSORT flag. + * Other data items can in theory be from 0 to 0x7fffffff bytes long. + * + * (!) The notable difference between MDBX and LMDB is that MDBX support zero + * length keys. */ +#ifndef HAVE_STRUCT_IOVEC +struct iovec { + void *iov_base /* pointer to some data */; + size_t iov_len /* the length of data in bytes */; +}; +#define HAVE_STRUCT_IOVEC +#endif /* HAVE_STRUCT_IOVEC */ + +typedef struct iovec MDBX_val; + +/* The maximum size of a data item. + * MDBX only store a 32 bit value for node sizes. */ +#define MDBX_MAXDATASIZE INT32_MAX + +/**** DEBUG & LOGGING ********************************************************** + * Logging and runtime debug flags. + * + * NOTE: Most of debug feature enabled only when libmdbx builded with + * MDBX_DEBUG options. + */ + +/* Log level (requires build libmdbx with MDBX_DEBUG) */ +#define MDBX_LOG_FATAL 0 /* critical conditions, i.e. assertion failures */ +#define MDBX_LOG_ERROR 1 /* error conditions */ +#define MDBX_LOG_WARN 2 /* warning conditions */ +#define MDBX_LOG_NOTICE 3 /* normal but significant condition */ +#define MDBX_LOG_VERBOSE 4 /* verbose informational */ +#define MDBX_LOG_DEBUG 5 /* debug-level messages */ +#define MDBX_LOG_TRACE 6 /* trace debug-level messages */ +#define MDBX_LOG_EXTRA 7 /* extra debug-level messages (dump pgno lists) */ + +/* Runtime debug flags. + * + * MDBX_DBG_DUMP and MDBX_DBG_LEGACY_MULTIOPEN always have an effect, + * but MDBX_DBG_ASSERT, MDBX_DBG_AUDIT and MDBX_DBG_JITTER only if libmdbx + * builded with MDBX_DEBUG. */ + +#define MDBX_DBG_ASSERT 1 /* Enable assertion checks */ +#define MDBX_DBG_AUDIT 2 /* Enable pages usage audit at commit transactions */ +#define MDBX_DBG_JITTER 4 /* Enable small random delays in critical points */ +#define MDBX_DBG_DUMP 8 /* Include or not database(s) in coredump files */ +#define MDBX_DBG_LEGACY_MULTIOPEN 16 /* Enable multi-opening environment(s) */ + +/* A debug-logger callback function, + * called before printing the message and aborting. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] msg The assertion message, not including newline. */ +typedef void MDBX_debug_func(int loglevel, const char *function, int line, + const char *msg, va_list args); + +/* FIXME: Complete description */ +LIBMDBX_API int mdbx_setup_debug(int loglevel, int flags, + MDBX_debug_func *logger); + +/* A callback function for most MDBX assert() failures, + * called before printing the message and aborting. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] msg The assertion message, not including newline. */ +typedef void MDBX_assert_func(const MDBX_env *env, const char *msg, + const char *function, unsigned line); + +/* Set or reset the assert() callback of the environment. + * + * Does nothing if libmdbx was built with MDBX_DEBUG=0 or with NDEBUG, + * and will return MDBX_ENOSYS in such case. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] func An MDBX_assert_func function, or 0. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_set_assert(MDBX_env *env, MDBX_assert_func *func); + +/* FIXME: Complete description */ +LIBMDBX_API const char *mdbx_dump_val(const MDBX_val *key, char *const buf, + const size_t bufsize); + +/**** THE FILES **************************************************************** + * At the file system level, the environment corresponds to a pair of files. */ + +/* The name of the lock file in the environment */ +#define MDBX_LOCKNAME "/mdbx.lck" +/* The name of the data file in the environment */ +#define MDBX_DATANAME "/mdbx.dat" + +/* The suffix of the lock file when MDBX_NOSUBDIR is used */ +#define MDBX_LOCK_SUFFIX "-lck" + +/**** ENVIRONMENT FLAGS *******************************************************/ + +/* MDBX_NOSUBDIR = no environment directory. + * + * By default, MDBX creates its environment in a directory whose pathname is + * given in path, and creates its data and lock files under that directory. + * With this option, path is used as-is for the database main data file. + * The database lock file is the path with "-lck" appended. + * + * - with MDBX_NOSUBDIR = in a filesystem we have the pair of MDBX-files which + * names derived from given pathname by appending predefined suffixes. + * + * - without MDBX_NOSUBDIR = in a filesystem we have the MDBX-directory with + * given pathname, within that a pair of MDBX-files with predefined names. + * + * This flag affects only at environment opening and can't be changed after. */ +#define MDBX_NOSUBDIR 0x4000u + +/* MDBX_RDONLY = read only mode. + * + * Open the environment in read-only mode. No write operations will be allowed. + * MDBX will still modify the lock file - except on read-only filesystems, + * where MDBX does not use locks. + * + * - with MDBX_RDONLY = open environment in read-only mode. + * MDBX supports pure read-only mode (i.e. without opening LCK-file) only + * when environment directory and/or both files are not writable (and the + * LCK-file may be missing). In such case allowing file(s) to be placed + * on a network read-only share. + * + * - without MDBX_RDONLY = open environment in read-write mode. + * + * This flag affects only at environment opening but can't be changed after. */ +#define MDBX_RDONLY 0x20000u + +/* MDBX_EXCLUSIVE = open environment in exclusive/monopolistic mode. + * + * MDBX_EXCLUSIVE flag can be used as a replacement for MDB_NOLOCK, which don't + * supported by MDBX. In this way, you can get the minimal overhead, but with + * the correct multi-process and mutli-thread locking. + * + * - with MDBX_EXCLUSIVE = open environment in exclusive/monopolistic mode + * or return MDBX_BUSY if environment already used by other process. + * The main feature of the exclusive mode is the ability to open the + * environment placed on a network share. + * + * - without MDBX_EXCLUSIVE = open environment in cooperative mode, + * i.e. for multi-process access/interaction/cooperation. + * The main requirements of the cooperative mode are: + * 1. data files MUST be placed in the LOCAL file system, + * but NOT on a network share. + * 2. environment MUST be opened only by LOCAL processes, + * but NOT over a network. + * 3. OS kernel (i.e. file system and memory mapping implementation) and + * all processes that open the given environment MUST be running + * in the physically single RAM with cache-coherency. The only + * exception for cache-consistency requirement is Linux on MIPS + * architecture, but this case has not been tested for a long time). + + * This flag affects only at environment opening but can't be changed after. */ +#define MDBX_EXCLUSIVE 0x400000u + +/* MDBX_WRITEMAP = map data into memory with write permission. + * + * Use a writeable memory map unless MDBX_RDONLY is set. This uses fewer mallocs + * and requires much less work for tracking database pages, but loses protection + * from application bugs like wild pointer writes and other bad updates into the + * database. This may be slightly faster for DBs that fit entirely in RAM, but + * is slower for DBs larger than RAM. Also adds the possibility for stray + * application writes thru pointers to silently corrupt the database. + * Incompatible with nested transactions. + * + * NOTE: The MDBX_WRITEMAP mode is incompatible with nested transactions, since + * this is unreasonable. I.e. nested transactions requires mallocation of + * database pages and more work for tracking ones, which neuters a + * performance boost caused by the MDBX_WRITEMAP mode. + * + * NOTE: MDBX don't allow to mix processes with and without MDBX_WRITEMAP on + * the same environment. In such case MDBX_INCOMPATIBLE will be generated. + * + * - with MDBX_WRITEMAP = all data will be mapped into memory in the read-write + * mode. This offers a significant performance benefit, since the data will + * be modified directly in mapped memory and then flushed to disk by + * single system call, without any memory management nor copying. + * (!) On the other hand, MDBX_WRITEMAP adds the possibility for stray + * application writes thru pointers to silently corrupt the database. + * Moreover, MDBX_WRITEMAP disallows nested write transactions. + * + * - without MDBX_WRITEMAP = data will be mapped into memory in the read-only + * mode. This requires stocking all modified database pages in memory and + * then writing them to disk through file operations. + * + * This flag affects only at environment opening but can't be changed after. */ +#define MDBX_WRITEMAP 0x80000u + +/* MDBX_NOTLS = tie reader locktable slots to read-only transactions instead + * of to threads. + * + * Don't use Thread-Local Storage, instead tie reader locktable slots to + * MDBX_txn objects instead of to threads. So, mdbx_txn_reset() keeps the slot + * reserved for the MDBX_txn object. A thread may use parallel read-only + * transactions. And a read-only transaction may span threads if you + * synchronizes its use. + * + * Applications that multiplex many user threads over individual OS threads need + * this option. Such an application must also serialize the write transactions + * in an OS thread, since MDBX's write locking is unaware of the user threads. + * + * NOTE: Regardless to MDBX_NOTLS flag a write transaction entirely should + * always be used in one thread from start to finish. MDBX checks this in a + * reasonable manner and return the MDBX_THREAD_MISMATCH error in rules + * violation. + * + * This flag affects only at environment opening but can't be changed after. */ +#define MDBX_NOTLS 0x200000u + +/* MDBX_NORDAHEAD = don't do readahead. + * + * Turn off readahead. Most operating systems perform readahead on read requests + * by default. This option turns it off if the OS supports it. Turning it off + * may help random read performance when the DB is larger than RAM and system + * RAM is full. + * + * NOTE: The mdbx_is_readahead_reasonable() function allows to quickly find out + * whether to use readahead or not based on the size of the data and the + * amount of available memory. + * + * This flag affects only at environment opening and can't be changed after. */ +#define MDBX_NORDAHEAD 0x800000u + +/* MDBX_NOMEMINIT = don't initialize malloc'd memory before writing to datafile. + * + * Don't initialize malloc'd memory before writing to unused spaces in the data + * file. By default, memory for pages written to the data file is obtained using + * malloc. While these pages may be reused in subsequent transactions, freshly + * malloc'd pages will be initialized to zeroes before use. This avoids + * persisting leftover data from other code (that used the heap and subsequently + * freed the memory) into the data file. + * + * Note that many other system libraries may allocate and free memory from the + * heap for arbitrary uses. E.g., stdio may use the heap for file I/O buffers. + * This initialization step has a modest performance cost so some applications + * may want to disable it using this flag. This option can be a problem for + * applications which handle sensitive data like passwords, and it makes memory + * checkers like Valgrind noisy. This flag is not needed with MDBX_WRITEMAP, + * which writes directly to the mmap instead of using malloc for pages. The + * initialization is also skipped if MDBX_RESERVE is used; the caller is + * expected to overwrite all of the memory that was reserved in that case. + * + * This flag may be changed at any time using mdbx_env_set_flags(). */ +#define MDBX_NOMEMINIT 0x1000000u + +/* MDBX_COALESCE = aims to coalesce a Garbage Collection items. + * + * With MDBX_COALESCE flag MDBX will aims to coalesce items while recycling + * a Garbage Collection. Technically, when possible short lists of pages will + * be combined into longer ones, but to fit on one database page. As a result, + * there will be fewer items in Garbage Collection and a page lists are longer, + * which slightly increases the likelihood of returning pages to Unallocated + * space and reducing the database file. + * + * This flag may be changed at any time using mdbx_env_set_flags(). */ +#define MDBX_COALESCE 0x2000000u + +/* MDBX_LIFORECLAIM = LIFO policy for recycling a Garbage Collection items. + * + * MDBX_LIFORECLAIM flag turns on LIFO policy for recycling a Garbage + * Collection items, instead of FIFO by default. On systems with a disk + * write-back cache, this can significantly increase write performance, up to + * several times in a best case scenario. + * + * LIFO recycling policy means that for reuse pages will be taken which became + * unused the lastest (i.e. just now or most recently). Therefore the loop of + * database pages circulation becomes as short as possible. In other words, the + * number of pages, that are overwritten in memory and on disk during a series + * of write transactions, will be as small as possible. Thus creates ideal + * conditions for the efficient operation of the disk write-back cache. + * + * MDBX_LIFORECLAIM is compatible with all no-sync flags (i.e. MDBX_NOMETASYNC, + * MDBX_NOSYNC, MDBX_UTTERLY_NOSYNC, MDBX_MAPASYNC), but gives no noticeable + * impact in combination with MDB_NOSYNC and MDX_MAPASYNC. Because MDBX will + * not reused paged from the last "steady" MVCC-snapshot and later, i.e. the + * loop length of database pages circulation will be mostly defined by frequency + * of calling mdbx_env_sync() rather than LIFO and FIFO difference. + * + * This flag may be changed at any time using mdbx_env_set_flags(). */ +#define MDBX_LIFORECLAIM 0x4000000u + +/* Debuging option, fill/perturb released pages. */ +#define MDBX_PAGEPERTURB 0x8000000u + +/**** SYNC MODES *************************************************************** + * (!!!) Using any combination of MDBX_NOSYNC, MDBX_NOMETASYNC, MDBX_MAPASYNC + * and especially MDBX_UTTERLY_NOSYNC is always a deal to reduce durability + * for gain write performance. You must know exactly what you are doing and + * what risks you are taking! + * + * NOTE for LMDB users: MDBX_NOSYNC is NOT similar to LMDB_NOSYNC, but + * MDBX_UTTERLY_NOSYNC is exactly match LMDB_NOSYNC. + * See details below. + * + * THE SCENE: + * - The DAT-file contains several MVCC-snapshots of B-tree at same time, + * each of those B-tree has its own root page. + * - Each of meta pages at the beginning of the DAT file contains a pointer + * to the root page of B-tree which is the result of the particular + * transaction, and a number of this transaction. + * - For data durability, MDBX must first write all MVCC-snapshot data pages + * and ensure that are written to the disk, then update a meta page with + * the new transaction number and a pointer to the corresponding new root + * page, and flush any buffers yet again. + * - Thus during commit a I/O buffers should be flushed to the disk twice; + * i.e. fdatasync(), FlushFileBuffers() or similar syscall should be called + * twice for each commit. This is very expensive for performance, but + * guaranteed durability even on unexpected system failure or power outage. + * Of course, provided that the operating system and the underlying hardware + * (e.g. disk) work correctly. + * + * TRADE-OFF: By skipping some stages described above, you can significantly + * benefit in speed, while partially or completely losing in the guarantee of + * data durability and/or consistency in the event of system or power failure. + * Moreover, if for any reason disk write order is not preserved, then at moment + * of a system crash, a meta-page with a pointer to the new B-tree may be + * written to disk, while the itself B-tree not yet. In that case, the database + * will be corrupted! + * + * + * MDBX_NOMETASYNC = don't sync the meta-page after commit. + * + * Flush system buffers to disk only once per transaction, omit the + * metadata flush. Defer that until the system flushes files to disk, + * or next non-MDBX_RDONLY commit or mdbx_env_sync(). Depending on the + * platform and hardware, with MDBX_NOMETASYNC you may get a doubling of + * write performance. + * + * This trade-off maintains database integrity, but a system crash may + * undo the last committed transaction. I.e. it preserves the ACI + * (atomicity, consistency, isolation) but not D (durability) database + * property. + * + * MDBX_NOMETASYNC flag may be changed at any time using + * mdbx_env_set_flags() or by passing to mdbx_txn_begin() for particular + * write transaction. + * + * + * MDBX_UTTERLY_NOSYNC = don't sync anything and wipe previous steady commits. + * + * Don't flush system buffers to disk when committing a transaction. This + * optimization means a system crash can corrupt the database, if buffers + * are not yet flushed to disk. Depending on the platform and hardware, + * with MDBX_UTTERLY_NOSYNC you may get a multiple increase of write + * performance, even 100 times or more. + * + * If the filesystem preserves write order (which is rare and never + * provided unless explicitly noted) and the MDBX_WRITEMAP and + * MDBX_LIFORECLAIM flags are not used, then a system crash can't corrupt + * the database, but you can lose the last transactions, if at least one + * buffer is not yet flushed to disk. The risk is governed by how often the + * system flushes dirty buffers to disk and how often mdbx_env_sync() is + * called. So, transactions exhibit ACI (atomicity, consistency, isolation) + * properties and only lose D (durability). I.e. database integrity is + * maintained, but a system crash may undo the final transactions. + * + * Otherwise, if the filesystem not preserves write order (which is + * typically) or MDBX_WRITEMAP or MDBX_LIFORECLAIM flags are used, you + * should expect the corrupted database after a system crash. + * + * So, most important thing about MDBX_UTTERLY_NOSYNC: + * - a system crash immediately after commit the write transaction + * high likely lead to database corruption. + * - successful completion of mdbx_env_sync(force = true) after one or + * more commited transactions guarantees consystency and durability. + * - BUT by committing two or more transactions you back database into a + * weak state, in which a system crash may lead to database corruption! + * In case single transaction after mdbx_env_sync, you may lose + * transaction itself, but not a whole database. + * + * Nevertheless, MDBX_UTTERLY_NOSYNC provides ACID in case of a application + * crash, and therefore may be very useful in scenarios where data + * durability is not required over a system failure (e.g for short-lived + * data), or if you can ignore such risk. + * + * MDBX_UTTERLY_NOSYNC flag may be changed at any time using + * mdbx_env_set_flags(), but don't has effect if passed to mdbx_txn_begin() + * for particular write transaction. + * + * + * MDBX_NOSYNC = don't sync anything but keep previous steady commits. + * + * Like MDBX_UTTERLY_NOSYNC the MDBX_NOSYNC flag similarly disable flush + * system buffers to disk when committing a transaction. But there is a + * huge difference in how are recycled the MVCC snapshots corresponding + * to previous "steady" transactions (see below). + * + * Depending on the platform and hardware, with MDBX_NOSYNC you may get + * a multiple increase of write performance, even 10 times or more. + * NOTE that (MDBX_NOSYNC | MDBX_WRITEMAP) leaves the system with no hint + * for when to write transactions to disk. Therefore the (MDBX_MAPASYNC | + * MDBX_WRITEMAP) may be preferable, but without MDBX_NOSYNC because + * the (MDBX_MAPASYNC | MDBX_NOSYNC) actually gives MDBX_UTTERLY_NOSYNC. + * + * In contrast to MDBX_UTTERLY_NOSYNC mode, with MDBX_NOSYNC flag MDBX will + * keeps untouched pages within B-tree of the last transaction "steady" + * which was synced to disk completely. This has big implications for both + * data durability and (unfortunately) performance: + * - a system crash can't corrupt the database, but you will lose the + * last transactions; because MDBX will rollback to last steady commit + * since it kept explicitly. + * - the last steady transaction makes an effect similar to "long-lived" + * read transaction (see above in the "RESTRICTIONS & CAVEATS" section) + * since prevents reuse of pages freed by newer write transactions, + * thus the any data changes will be placed in newly allocated pages. + * - to avoid rapid database growth, the system will sync data and issue + * a steady commit-point to resume reuse pages, each time there is + * insufficient space and before increasing the size of the file on + * disk. + * + * In other words, with MDBX_NOSYNC flag MDBX insures you from the whole + * database corruption, at the cost increasing database size and/or number + * of disk IOPS. So, MDBX_NOSYNC flag could be used with mdbx_env_synv() + * as alternatively for batch committing or nested transaction (in some + * cases). As well, auto-sync feature exposed by mdbx_env_set_syncbytes() + * and mdbx_env_set_syncperiod() functions could be very usefull with + * MDBX_NOSYNC flag. + * + * The number and volume of of disk IOPS with MDBX_NOSYNC flag will + * exactly the as without any no-sync flags. However, you should expect + * a larger process's work set (https://bit.ly/2kA2tFX) and significantly + * worse a locality of reference (https://bit.ly/2mbYq2J), due to the + * more intensive allocation of previously unused pages and increase the + * size of the database. + * + * MDBX_NOSYNC flag may be changed at any time using + * mdbx_env_set_flags() or by passing to mdbx_txn_begin() for particular + * write transaction. + * + * + * MDBX_MAPASYNC = use asynchronous msync when MDBX_WRITEMAP is used. + * + * MDBX_MAPASYNC meaningful and give effect only in conjunction + * with MDBX_WRITEMAP or MDBX_NOSYNC: + * - with MDBX_NOSYNC actually gives MDBX_UTTERLY_NOSYNC, which + * wipe previous steady commits for reuse pages as described above. + * - with MDBX_WRITEMAP but without MDBX_NOSYNC instructs MDBX to use + * asynchronous mmap-flushes to disk as described below. + * - with both MDBX_WRITEMAP and MDBX_NOSYNC you get the both effects. + * + * Asynchronous mmap-flushes means that actually all writes will scheduled + * and performed by operation system on it own manner, i.e. unordered. + * MDBX itself just notify operating system that it would be nice to write + * data to disk, but no more. + * + * With MDBX_MAPASYNC flag, but without MDBX_UTTERLY_NOSYNC (i.e. without + * OR'ing with MDBX_NOSYNC) MDBX will keeps untouched pages within B-tree + * of the last transaction "steady" which was synced to disk completely. + * So, this makes exactly the same "long-lived" impact and the same + * consequences as described above for MDBX_NOSYNC flag. + * + * Depending on the platform and hardware, with combination of + * MDBX_WRITEMAP and MDBX_MAPASYNC you may get a multiple increase of write + * performance, even 25 times or more. MDBX_MAPASYNC flag may be changed at + * any time using mdbx_env_set_flags() or by passing to mdbx_txn_begin() + * for particular write transaction. + */ + +/* Don't sync meta-page after commit, + * see description in the "SYNC MODES" section above. */ +#define MDBX_NOMETASYNC 0x40000u + +/* Don't sync anything but keep previous steady commits, + * see description in the "SYNC MODES" section above. + * + * (!) don't combine this flag with MDBX_MAPASYNC + * since you will got MDBX_UTTERLY_NOSYNC in that way (see below) */ +#define MDBX_NOSYNC 0x10000u + +/* Use asynchronous msync when MDBX_WRITEMAP is used, + * see description in the "SYNC MODES" section above. + * + * (!) don't combine this flag with MDBX_NOSYNC + * since you will got MDBX_UTTERLY_NOSYNC in that way (see below) */ +#define MDBX_MAPASYNC 0x100000u + +/* Don't sync anything and wipe previous steady commits, + * see description in the "SYNC MODES" section above. */ +#define MDBX_UTTERLY_NOSYNC (MDBX_NOSYNC | MDBX_MAPASYNC) + +/**** DATABASE FLAGS **********************************************************/ +/* Use reverse string keys */ +#define MDBX_REVERSEKEY 0x02u +/* Use sorted duplicates */ +#define MDBX_DUPSORT 0x04u +/* Numeric keys in native byte order, either uint32_t or uint64_t. + * The keys must all be of the same size. */ +#define MDBX_INTEGERKEY 0x08u +/* With MDBX_DUPSORT, sorted dup items have fixed size */ +#define MDBX_DUPFIXED 0x10u +/* With MDBX_DUPSORT, dups are MDBX_INTEGERKEY-style integers */ +#define MDBX_INTEGERDUP 0x20u +/* With MDBX_DUPSORT, use reverse string dups */ +#define MDBX_REVERSEDUP 0x40u +/* Create DB if not already existing */ +#define MDBX_CREATE 0x40000u + +/**** DATA UPDATE FLAGS *******************************************************/ +/* For put: Don't write if the key already exists. */ +#define MDBX_NOOVERWRITE 0x10u +/* Only for MDBX_DUPSORT + * For put: don't write if the key and data pair already exist. + * For mdbx_cursor_del: remove all duplicate data items. */ +#define MDBX_NODUPDATA 0x20u +/* For mdbx_cursor_put: overwrite the current key/data pair + * MDBX allows this flag for mdbx_put() for explicit overwrite/update without + * insertion. */ +#define MDBX_CURRENT 0x40u +/* For put: Just reserve space for data, don't copy it. Return a + * pointer to the reserved space. */ +#define MDBX_RESERVE 0x10000u +/* Data is being appended, don't split full pages. */ +#define MDBX_APPEND 0x20000u +/* Duplicate data is being appended, don't split full pages. */ +#define MDBX_APPENDDUP 0x40000u +/* Store multiple data items in one call. Only for MDBX_DUPFIXED. */ +#define MDBX_MULTIPLE 0x80000u + +/**** TRANSACTION FLAGS *******************************************************/ +/* Do not block when starting a write transaction */ +#define MDBX_TRYTXN 0x10000000u + +/**** ENVIRONMENT COPY FLAGS **************************************************/ +/* Compacting: Omit free space from copy, and renumber all pages sequentially */ +#define MDBX_CP_COMPACT 1u + +/**** CURSOR OPERATIONS ******************************************************** + * + * This is the set of all operations for retrieving data + * using a cursor. */ +typedef enum MDBX_cursor_op { + MDBX_FIRST, /* Position at first key/data item */ + MDBX_FIRST_DUP, /* MDBX_DUPSORT-only: Position at first data item + * of current key. */ + MDBX_GET_BOTH, /* MDBX_DUPSORT-only: Position at key/data pair. */ + MDBX_GET_BOTH_RANGE, /* MDBX_DUPSORT-only: position at key, nearest data. */ + MDBX_GET_CURRENT, /* Return key/data at current cursor position */ + MDBX_GET_MULTIPLE, /* MDBX_DUPFIXED-only: Return up to a page of duplicate + * data items from current cursor position. + * Move cursor to prepare for MDBX_NEXT_MULTIPLE. */ + MDBX_LAST, /* Position at last key/data item */ + MDBX_LAST_DUP, /* MDBX_DUPSORT-only: Position at last data item + * of current key. */ + MDBX_NEXT, /* Position at next data item */ + MDBX_NEXT_DUP, /* MDBX_DUPSORT-only: Position at next data item + * of current key. */ + MDBX_NEXT_MULTIPLE, /* MDBX_DUPFIXED-only: Return up to a page of + * duplicate data items from next cursor position. + * Move cursor to prepare for MDBX_NEXT_MULTIPLE. */ + MDBX_NEXT_NODUP, /* Position at first data item of next key */ + MDBX_PREV, /* Position at previous data item */ + MDBX_PREV_DUP, /* MDBX_DUPSORT-only: Position at previous data item + * of current key. */ + MDBX_PREV_NODUP, /* Position at last data item of previous key */ + MDBX_SET, /* Position at specified key */ + MDBX_SET_KEY, /* Position at specified key, return both key and data */ + MDBX_SET_RANGE, /* Position at first key greater than or equal to + * specified key. */ + MDBX_PREV_MULTIPLE /* MDBX_DUPFIXED-only: Position at previous page and + * return up to a page of duplicate data items. */ +} MDBX_cursor_op; + +/**** ERRORS & RETURN CODES **************************************************** + * BerkeleyDB uses -30800 to -30999, we'll go under them */ + +/* Successful result */ +#define MDBX_SUCCESS 0 +#define MDBX_RESULT_FALSE MDBX_SUCCESS +/* Successful result with special meaning or a flag */ +#define MDBX_RESULT_TRUE (-1) + +/* key/data pair already exists */ +#define MDBX_KEYEXIST (-30799) +/* key/data pair not found (EOF) */ +#define MDBX_NOTFOUND (-30798) +/* Requested page not found - this usually indicates corruption */ +#define MDBX_PAGE_NOTFOUND (-30797) +/* Database is corrupted (page was wrong type and so on) */ +#define MDBX_CORRUPTED (-30796) +/* Environment had fatal error (i.e. update of meta page failed and so on) */ +#define MDBX_PANIC (-30795) +/* DB file version mismatch with libmdbx */ +#define MDBX_VERSION_MISMATCH (-30794) +/* File is not a valid MDBX file */ +#define MDBX_INVALID (-30793) +/* Environment mapsize reached */ +#define MDBX_MAP_FULL (-30792) +/* Environment maxdbs reached */ +#define MDBX_DBS_FULL (-30791) +/* Environment maxreaders reached */ +#define MDBX_READERS_FULL (-30790) +/* Txn has too many dirty pages */ +#define MDBX_TXN_FULL (-30788) +/* Cursor stack too deep - internal error */ +#define MDBX_CURSOR_FULL (-30787) +/* Page has not enough space - internal error */ +#define MDBX_PAGE_FULL (-30786) +/* Database contents grew beyond environment mapsize */ +#define MDBX_MAP_RESIZED (-30785) +/* Operation and DB incompatible, or DB type changed. This can mean: + * - The operation expects an MDBX_DUPSORT / MDBX_DUPFIXED database. + * - Opening a named DB when the unnamed DB has MDBX_DUPSORT/MDBX_INTEGERKEY. + * - Accessing a data record as a database, or vice versa. + * - The database was dropped and recreated with different flags. */ +#define MDBX_INCOMPATIBLE (-30784) +/* Invalid reuse of reader locktable slot */ +#define MDBX_BAD_RSLOT (-30783) +/* Transaction must abort, has a child, or is invalid */ +#define MDBX_BAD_TXN (-30782) +/* Unsupported size of key/DB name/data, or wrong DUPFIXED size */ +#define MDBX_BAD_VALSIZE (-30781) +/* The specified DBI was changed unexpectedly */ +#define MDBX_BAD_DBI (-30780) +/* Unexpected problem - txn should abort */ +#define MDBX_PROBLEM (-30779) +/* Another write transaction is running or environment is already used while + * opening with MDBX_EXCLUSIVE flag */ +#define MDBX_BUSY (-30778) +/* The last defined error code */ +#define MDBX_LAST_ERRCODE MDBX_BUSY + +/* The mdbx_put() or mdbx_replace() was called for key, + * that has more that one associated value. */ +#define MDBX_EMULTIVAL (-30421) + +/* Bad signature of a runtime object(s), this can mean: + * - memory corruption or double-free; + * - ABI version mismatch (rare case); */ +#define MDBX_EBADSIGN (-30420) + +/* Database should be recovered, but this could NOT be done automatically + * right now (e.g. in readonly mode and so forth). */ +#define MDBX_WANNA_RECOVERY (-30419) + +/* The given key value is mismatched to the current cursor position, + * when mdbx_cursor_put() called with MDBX_CURRENT option. */ +#define MDBX_EKEYMISMATCH (-30418) + +/* Database is too large for current system, + * e.g. could NOT be mapped into RAM. */ +#define MDBX_TOO_LARGE (-30417) + +/* A thread has attempted to use a not owned object, + * e.g. a transaction that started by another thread. */ +#define MDBX_THREAD_MISMATCH (-30416) + +/**** FUNCTIONS & RELATED STRUCTURES ******************************************/ + +/* Return a string describing a given error code. + * + * This function is a superset of the ANSI C X3.159-1989 (ANSI C) strerror(3) + * function. If the error code is greater than or equal to 0, then the string + * returned by the system function strerror(3) is returned. If the error code + * is less than 0, an error string corresponding to the MDBX library error is + * returned. See errors for a list of MDBX-specific error codes. + * + * mdbx_strerror() - is NOT thread-safe because may share common internal + * buffer for system maessages. The returned string must + * NOT be modified by the application, but MAY be modified + * by a subsequent call to mdbx_strerror(), strerror() and + * other related functions. + * + * mdbx_strerror_r() - is thread-safe since uses user-supplied buffer where + * appropriate. The returned string must NOT be modified + * by the application, since it may be pointer to internal + * constatn string. However, there is no restriction if the + * returned string points to the supplied buffer. + * + * [in] err The error code. + * + * Returns "error message" The description of the error. */ +LIBMDBX_API const char *mdbx_strerror(int errnum); +LIBMDBX_API const char *mdbx_strerror_r(int errnum, char *buf, size_t buflen); + +#if defined(_WIN32) || defined(_WIN64) +/* Bit of Windows' madness. The similar functions but returns Windows + * error-messages in the OEM-encoding for console utilities. */ +LIBMDBX_API const char *mdbx_strerror_ANSI2OEM(int errnum); +LIBMDBX_API const char *mdbx_strerror_r_ANSI2OEM(int errnum, char *buf, + size_t buflen); +#endif /* Bit of Windows' madness */ + +/* Create an MDBX environment instance. + * + * This function allocates memory for a MDBX_env structure. To release + * the allocated memory and discard the handle, call mdbx_env_close(). + * Before the handle may be used, it must be opened using mdbx_env_open(). + * + * Various other options may also need to be set before opening the handle, + * e.g. mdbx_env_set_geometry(), mdbx_env_set_maxreaders(), + * mdbx_env_set_maxdbs(), depending on usage requirements. + * + * [out] env The address where the new handle will be stored. + * + * Returns a non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_create(MDBX_env **penv); + +/* Open an environment instance. + * + * Indifferently this function will fails or not, the mdbx_env_close() must be + * called later to discard the MDBX_env handle and release associated resources. + * + * [in] env An environment handle returned by mdbx_env_create() + * [in] pathname The directory in which the database files reside. + * This directory must already exist and be writable. + * [in] flags Special options for this environment. This parameter + * must be set to 0 or by bitwise OR'ing together one + * or more of the values described above in the + * "ENVIRONMENT FLAGS" and "SYNC MODES" sections. + * + * Flags set by mdbx_env_set_flags() are also used: + * - MDBX_NOSUBDIR, MDBX_RDONLY, MDBX_EXCLUSIVE, MDBX_WRITEMAP, MDBX_NOTLS, + * MDBX_NORDAHEAD, MDBX_NOMEMINIT, MDBX_COALESCE, MDBX_LIFORECLAIM. + * See "ENVIRONMENT FLAGS" section above. + * + * - MDBX_NOMETASYNC, MDBX_NOSYNC, MDBX_UTTERLY_NOSYNC, MDBX_MAPASYNC. + * See "SYNC MODES" section above. + * + * NOTE: MDB_NOLOCK flag don't supported by MDBX, + * try use MDBX_EXCLUSIVE as a replacement. + * + * NOTE: MDBX don't allow to mix processes with different MDBX_WRITEMAP, + * MDBX_NOSYNC, MDBX_NOMETASYNC, MDBX_MAPASYNC flags onthe same + * environment. In such case MDBX_INCOMPATIBLE will be returned. + * + * If the database is already exist and parameters specified early by + * mdbx_env_set_geometry() are incompatible (i.e. for instance, different page + * size) then mdbx_env_open() will return MDBX_INCOMPATIBLE error. + * + * [in] mode The UNIX permissions to set on created files. Zero value means + * to open existing, but do not create. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_VERSION_MISMATCH = the version of the MDBX library doesn't match the + * version that created the database environment. + * - MDBX_INVALID = the environment file headers are corrupted. + * - MDBX_ENOENT = the directory specified by the path parameter + * doesn't exist. + * - MDBX_EACCES = the user didn't have permission to access + * the environment files. + * - MDBX_EAGAIN = the environment was locked by another process. + * - MDBX_BUSY = MDBX_EXCLUSIVE flag was specified and the + * environment is in use by another process, + * or the current process tries to open environment + * more than once. + * - MDBX_INCOMPATIBLE = Environment is already opened by another process, + * but with different set of MDBX_WRITEMAP, + * MDBX_NOSYNC, MDBX_NOMETASYNC, MDBX_MAPASYNC + * flags. + * Or if the database is already exist and + * parameters specified early by + * mdbx_env_set_geometry() are incompatible (i.e. + * for instance, different page size). + * - MDBX_WANNA_RECOVERY = MDBX_RDONLY flag was specified but read-write + * access is required to rollback inconsistent state + * after a system crash. + * - MDBX_TOO_LARGE = Database is too large for this process, i.e. + * 32-bit process tries to open >4Gb database. */ +LIBMDBX_API int mdbx_env_open(MDBX_env *env, const char *pathname, + unsigned flags, mode_t mode); + +/* Copy an MDBX environment to the specified path, with options. + * + * This function may be used to make a backup of an existing environment. + * No lockfile is created, since it gets recreated at need. + * NOTE: This call can trigger significant file size growth if run in + * parallel with write transactions, because it employs a read-only + * transaction. See long-lived transactions under "Caveats" section. + * + * [in] env An environment handle returned by mdbx_env_create(). It must + * have already been opened successfully. + * [in] dest The directory in which the copy will reside. This directory + * must already exist and be writable but must otherwise be empty. + * [in] flags Special options for this operation. This parameter must be set + * to 0 or by bitwise OR'ing together one or more of the values + * described here: + * + * - MDBX_CP_COMPACT + * Perform compaction while copying: omit free pages and sequentially + * renumber all pages in output. This option consumes little bit more + * CPU for processing, but may running quickly than the default, on + * account skipping free pages. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_copy(MDBX_env *env, const char *dest, unsigned flags); + +/* Copy an MDBX environment to the specified file descriptor, + * with options. + * + * This function may be used to make a backup of an existing environment. + * No lockfile is created, since it gets recreated at need. See + * mdbx_env_copy() for further details. + * + * NOTE: This call can trigger significant file size growth if run in + * parallel with write transactions, because it employs a read-only + * transaction. See long-lived transactions under "Caveats" section. + * + * NOTE: Fails if the environment has suffered a page leak and the destination + * file descriptor is associated with a pipe, socket, or FIFO. + * + * [in] env An environment handle returned by mdbx_env_create(). It must + * have already been opened successfully. + * [in] fd The filedescriptor to write the copy to. It must have already + * been opened for Write access. + * [in] flags Special options for this operation. See mdbx_env_copy() for + * options. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_copy2fd(MDBX_env *env, mdbx_filehandle_t fd, + unsigned flags); + +/* Statistics for a database in the environment */ +typedef struct MDBX_stat { + uint32_t ms_psize; /* Size of a database page. + * This is the same for all databases. */ + uint32_t ms_depth; /* Depth (height) of the B-tree */ + uint64_t ms_branch_pages; /* Number of internal (non-leaf) pages */ + uint64_t ms_leaf_pages; /* Number of leaf pages */ + uint64_t ms_overflow_pages; /* Number of overflow pages */ + uint64_t ms_entries; /* Number of data items */ + uint64_t ms_mod_txnid; /* Transaction ID of commited last modification */ +} MDBX_stat; + +/* Return statistics about the MDBX environment. + * + * At least one of env or txn argument must be non-null. If txn is passed + * non-null then stat will be filled accordingly to the given transaction. + * Otherwise, if txn is null, then stat will be populated by a snapshot from the + * last committed write transaction, and at next time, other information can be + * returned. + * + * Legacy mdbx_env_stat() correspond to calling mdbx_env_stat_ex() with the null + * txn argument. + * + * [in] env An environment handle returned by mdbx_env_create() + * [in] txn A transaction handle returned by mdbx_txn_begin() + * [out] stat The address of an MDBX_stat structure where the statistics + * will be copied + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_stat_ex(const MDBX_env *env, const MDBX_txn *txn, + MDBX_stat *stat, size_t bytes); +LIBMDBX_API int mdbx_env_stat(MDBX_env *env, MDBX_stat *stat, size_t bytes); + +/* Information about the environment */ +typedef struct MDBX_envinfo { + struct { + uint64_t lower; /* lower limit for datafile size */ + uint64_t upper; /* upper limit for datafile size */ + uint64_t current; /* current datafile size */ + uint64_t shrink; /* shrink threshold for datafile */ + uint64_t grow; /* growth step for datafile */ + } mi_geo; + uint64_t mi_mapsize; /* Size of the data memory map */ + uint64_t mi_last_pgno; /* ID of the last used page */ + uint64_t mi_recent_txnid; /* ID of the last committed transaction */ + uint64_t mi_latter_reader_txnid; /* ID of the last reader transaction */ + uint64_t mi_self_latter_reader_txnid; /* ID of the last reader transaction of + caller process */ + uint64_t mi_meta0_txnid, mi_meta0_sign; + uint64_t mi_meta1_txnid, mi_meta1_sign; + uint64_t mi_meta2_txnid, mi_meta2_sign; + uint32_t mi_maxreaders; /* max reader slots in the environment */ + uint32_t mi_numreaders; /* max reader slots used in the environment */ + uint32_t mi_dxb_pagesize; /* database pagesize */ + uint32_t mi_sys_pagesize; /* system pagesize */ + + uint64_t + mi_bootid[2]; /* A mostly unique ID that is regenerated on each boot. + As such it can be used to identify the local + machine's current boot. MDBX uses such when open + the database to determine whether rollback required + to the last steady sync point or not. I.e. if current + bootid is differ from the value within a database then + the system was rebooted and all changes since last steady + sync must be reverted for data integrity. Zeros mean that + no relevant information is available from the system. */ + uint64_t mi_unsync_volume; /* bytes not explicitly synchronized to disk */ + uint64_t mi_autosync_threshold; /* current auto-sync threshold, see + mdbx_env_set_syncbytes(). */ + uint32_t mi_since_sync_seconds16dot16; /* time since the last steady sync in + 1/65536 of second */ + uint32_t mi_autosync_period_seconds16dot16 /* current auto-sync period in + 1/65536 of second, see + mdbx_env_set_syncperiod(). */ + ; + uint32_t mi_since_reader_check_seconds16dot16; /* time since the last readers + check in 1/65536 of second, + see mdbx_reader_check(). */ + uint32_t mi_mode; /* current environment mode, the same as + mdbx_env_get_flags() returns. */ +} MDBX_envinfo; + +/* Return information about the MDBX environment. + * + * At least one of env or txn argument must be non-null. If txn is passed + * non-null then stat will be filled accordingly to the given transaction. + * Otherwise, if txn is null, then stat will be populated by a snapshot from the + * last committed write transaction, and at next time, other information can be + * returned. + * + * Legacy mdbx_env_info() correspond to calling mdbx_env_info_ex() with the null + * txn argument. + + * [in] env An environment handle returned by mdbx_env_create() + * [in] txn A transaction handle returned by mdbx_txn_begin() + * [out] stat The address of an MDBX_envinfo structure + * where the information will be copied + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_info_ex(const MDBX_env *env, const MDBX_txn *txn, + MDBX_envinfo *info, size_t bytes); +LIBMDBX_API int mdbx_env_info(MDBX_env *env, MDBX_envinfo *info, size_t bytes); + +/* Flush the environment data buffers to disk. + * + * Unless the environment was opened with no-sync flags (MDBX_NOMETASYNC, + * MDBX_NOSYNC, MDBX_UTTERLY_NOSYNC and MDBX_MAPASYNC), then data is always + * written an flushed to disk when mdbx_txn_commit() is called. Otherwise + * mdbx_env_sync() may be called to manually write and flush unsynced data to + * disk. + * + * Besides, mdbx_env_sync_ex() with argument force=false may be used to + * provide polling mode for lazy/asynchronous sync in conjunction with + * mdbx_env_set_syncbytes() and/or mdbx_env_set_syncperiod(). + * + * The mdbx_env_sync() is shortcut to calling mdbx_env_sync_ex() with + * try force=true and nonblock=false arguments. + * + * The mdbx_env_sync_poll() is shortcut to calling mdbx_env_sync_ex() with + * the force=false and nonblock=true arguments. + * + * NOTE: This call is not valid if the environment was opened with MDBX_RDONLY. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] force If non-zero, force a flush. Otherwise, if force is zero, then + * will run in polling mode, i.e. it will check the thresholds + * that were set mdbx_env_set_syncbytes() and/or + * mdbx_env_set_syncperiod() and perform flush If at least one + * of the thresholds is reached. + * [in] nonblock Don't wait if write transaction is running by other thread. + * + * Returns A non-zero error value on failure and MDBX_RESULT_TRUE or 0 on + * success. The MDBX_RESULT_TRUE means no data pending for flush to disk, + * and 0 otherwise. Some possible errors are: + * - MDBX_EACCES = the environment is read-only. + * - MDBX_BUSY = the environment is used by other thread and nonblock=true. + * - MDBX_EINVAL = an invalid parameter was specified. + * - MDBX_EIO = an error occurred during synchronization. */ +LIBMDBX_API int mdbx_env_sync_ex(MDBX_env *env, int force, int nonblock); +LIBMDBX_API int mdbx_env_sync(MDBX_env *env); +LIBMDBX_API int mdbx_env_sync_poll(MDBX_env *env); + +/* Sets threshold to force flush the data buffers to disk, even of MDBX_NOSYNC, + * MDBX_NOMETASYNC and MDBX_MAPASYNC flags in the environment. The threshold + * value affects all processes which operates with given environment until the + * last process close environment or a new value will be settled. + * + * Data is always written to disk when mdbx_txn_commit() is called, but the + * operating system may keep it buffered. MDBX always flushes the OS buffers + * upon commit as well, unless the environment was opened with MDBX_NOSYNC, + * MDBX_MAPASYNC or in part MDBX_NOMETASYNC. + * + * The default is 0, than mean no any threshold checked, and no additional + * flush will be made. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] threshold The size in bytes of summary changes when a synchronous + * flush would be made. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_set_syncbytes(MDBX_env *env, size_t threshold); + +/* Sets relative period since the last unsteay commit to force flush the data + * buffers to disk, even of MDBX_NOSYNC, MDBX_NOMETASYNC and MDBX_MAPASYNC flags + * in the environment. The relative period value affects all processes which + * operates with given environment until the last process close environment or a + * new value will be settled. + * + * Data is always written to disk when mdbx_txn_commit() is called, but the + * operating system may keep it buffered. MDBX always flushes the OS buffers + * upon commit as well, unless the environment was opened with MDBX_NOSYNC, + * MDBX_MAPASYNC or in part MDBX_NOMETASYNC. + * + * Settled period don't checked asynchronously, but only by the + * mdbx_txn_commit() and mdbx_env_sync() functions. Therefore, in cases where + * transactions are committed infrequently and/or irregularly, polling by + * mdbx_env_sync() may be a reasonable solution to timeout enforcement. + * + * The default is 0, than mean no any timeout checked, and no additional + * flush will be made. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] seconds_16dot16 The period in 1/65536 of second when a synchronous + * flush would be made since the last unsteay commit. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_set_syncperiod(MDBX_env *env, + unsigned seconds_16dot16); + +/* Close the environment and release the memory map. + * + * Only a single thread may call this function. All transactions, databases, + * and cursors must already be closed before calling this function. Attempts + * to use any such handles after calling this function will cause a SIGSEGV. + * The environment handle will be freed and must not be used again after this + * call. + * + * Legacy mdbx_env_close() correspond to calling mdbx_env_close_ex() with the + * argument dont_sync=false. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] dont_sync A dont'sync flag, if non-zero the last checkpoint (meta-page + * update) will be kept "as is" and may be still "weak" in the + * NOSYNC/MAPASYNC modes. Such "weak" checkpoint will be + * ignored on opening next time, and transactions since the + * last non-weak checkpoint (meta-page update) will rolledback + * for consistency guarantee. + * + * Returns A non-zero error value on failure and 0 on success. + * Some possible errors are: + * - MDBX_BUSY = The write transaction is running by other thread, in such + * case MDBX_env instance has NOT be destroyed not released! + * NOTE: if any OTHER error code was returned then given + * MDBX_env instance has been destroyed and released. + * - MDBX_PANIC = If mdbx_env_close_ex() was called in the child process + * after fork(). In this case MDBX_PANIC is a expecte, + * i.e. MDBX_env instance was freed in proper manner. + * - MDBX_EIO = an error occurred during synchronization. */ +LIBMDBX_API int mdbx_env_close_ex(MDBX_env *env, int dont_sync); +LIBMDBX_API int mdbx_env_close(MDBX_env *env); + +/* Set environment flags. + * + * This may be used to set some flags in addition to those from + * mdbx_env_open(), or to unset these flags. + * + * NOTE: In contrast to LMDB, the MDBX serialize threads via mutex while + * changing the flags. Therefore this function will be blocked while a write + * transaction running by other thread, or MDBX_BUSY will be returned if + * function called within a write transaction. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] flags The flags to change, bitwise OR'ed together. + * [in] onoff A non-zero value sets the flags, zero clears them. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_env_set_flags(MDBX_env *env, unsigned flags, int onoff); + +/* Get environment flags. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [out] flags The address of an integer to store the flags. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_env_get_flags(MDBX_env *env, unsigned *flags); + +/* Return the path that was used in mdbx_env_open(). + * + * [in] env An environment handle returned by mdbx_env_create() + * [out] dest Address of a string pointer to contain the path. + * This is the actual string in the environment, not a copy. + * It should not be altered in any way. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_env_get_path(MDBX_env *env, const char **dest); + +/* Return the file descriptor for the given environment. + * + * NOTE: All MDBX file descriptors have FD_CLOEXEC and + * could't be used after exec() and or fork(). + * + * [in] env An environment handle returned by mdbx_env_create(). + * [out] fd Address of a int to contain the descriptor. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_env_get_fd(MDBX_env *env, mdbx_filehandle_t *fd); + +/* Set all size-related parameters of environment, including page size and the + * min/max size of the memory map. + * + * In contrast to LMDB, the MDBX provide automatic size management of an + * database according the given parameters, including shrinking and resizing + * on the fly. From user point of view all of these just working. Nevertheless, + * it is reasonable to know some details in order to make optimal decisions when + * choosing parameters. + * + * Both mdbx_env_info_ex() and legacy mdbx_env_info() are inapplicable to + * read-only opened environment. + * + * Both mdbx_env_info_ex() and legacy mdbx_env_info() could be called either + * before or after mdbx_env_open(), either within the write transaction running + * by current thread or not: + * + * - In case mdbx_env_info_ex() or legacy mdbx_env_info() was called BEFORE + * mdbx_env_open(), i.e. for closed environment, then the specified + * parameters will be used for new database creation, or will be appliend + * during openeing if database exists and no other process using it. + * + * If the database is already exist, opened with MDBX_EXCLUSIVE or not used + * by any other process, and parameters specified by mdbx_env_set_geometry() + * are incompatible (i.e. for instance, different page size) then + * mdbx_env_open() will return MDBX_INCOMPATIBLE error. + * + * In another way, if database will opened read-only or will used by other + * process during calling mdbx_env_open() that specified parameters will + * silently discarded (open the database with MDBX_EXCLUSIVE flag to avoid + * this). + * + * - In case mdbx_env_info_ex() or legacy mdbx_env_info() was called after + * mdbx_env_open() WITHIN the write transaction running by current thread, + * then specified parameters will be appliad as a part of write transaction, + * i.e. will not be visible to any others processes until the current write + * transaction has been committed by the current process. However, if + * transaction will be aborted, then the database file will be reverted to + * the previous size not immediately, but when a next transaction will be + * committed or when the database will be opened next time. + * + * - In case mdbx_env_info_ex() or legacy mdbx_env_info() was called after + * mdbx_env_open() but OUTSIDE a write transaction, then MDBX will execute + * internal pseudo-transaction to apply new parameters (but only if anything + * has been changed), and changes be visible to any others processes + * immediatelly after succesfull competeion of function. + * + * Essentially a concept of "automatic size management" is simple and useful: + * - There are the lower and upper bound of the database file size; + * - There is the growth step by which the database file will be increased, + * in case of lack of space. + * - There is the threshold for unused space, beyond which the database file + * will be shrunk. + * - The size of the memory map is also the maximum size of the database. + * - MDBX will automatically manage both the size of the database and the size + * of memory map, according to the given parameters. + * + * So, there some considerations about choosing these parameters: + * - The lower bound allows you to prevent database shrinking below some + * rational size to avoid unnecessary resizing costs. + * - The upper bound allows you to prevent database growth above some rational + * size. Besides, the upper bound defines the linear address space + * reservation in each process that opens the database. Therefore changing + * the upper bound is costly and may be required reopening environment in + * case of MDBX_MAP_RESIZED errors, and so on. Therefore, this value should + * be chosen reasonable as large as possible, to accommodate future growth + * of the database. + * - The growth step must be greater than zero to allow the database to grow, + * but also reasonable not too small, since increasing the size by little + * steps will result a large overhead. + * - The shrink threshold must be greater than zero to allow the database + * to shrink but also reasonable not too small (to avoid extra overhead) and + * not less than growth step to avoid up-and-down flouncing. + * - The current size (i.e. size_now argument) is an auxiliary parameter for + * simulation legacy mdbx_env_set_mapsize() and as workaround Windows issues + * (see below). + * + * Unfortunately, Windows has is a several issues + * with resizing of memory-mapped file: + * - Windows unable shrinking a memory-mapped file (i.e memory-mapped section) + * in any way except unmapping file entirely and then map again. Moreover, + * it is impossible in any way if a memory-mapped file is used more than + * one process. + * - Windows does not provide the usual API to augment a memory-mapped file + * (that is, a memory-mapped partition), but only by using "Native API" + * in an undocumented way. + * MDBX bypasses all Windows issues, but at a cost: + * - Ability to resize database on the fly requires an additional lock + * and release SlimReadWriteLock during each read-only transaction. + * - During resize all in-process threads should be paused and then resumed. + * - Shrinking of database file is performed only when it used by single + * process, i.e. when a database closes by the last process or opened + * by the first. + * = Therefore, the size_now argument may be useful to set database size + * by the first process which open a database, and thus avoid expensive + * remapping further. + * + * For create a new database with particular parameters, including the page + * size, mdbx_env_set_geometry() should be called after mdbx_env_create() and + * before mdbx_env_open(). Once the database is created, the page size cannot be + * changed. If you do not specify all or some of the parameters, the + * corresponding default values will be used. For instance, the default for + * database size is 10485760 bytes. + * + * If the mapsize is increased by another process, MDBX silently and + * transparently adopt these changes at next transaction start. However, + * mdbx_txn_begin() will return MDBX_MAP_RESIZED if new mapping size could not + * be applied for current process (for instance if address space is busy). + * Therefore, in the case of MDBX_MAP_RESIZED error you need close and reopen + * the environment to resolve error. + * + * NOTE: Actual values may be different than your have specified because of + * rounding to specified database page size, the system page size and/or the + * size of the system virtual memory management unit. You can get actual values + * by mdbx_env_sync_ex() or see by using the tool "mdbx_chk" with the "-v" + * option. + * + * Legacy mdbx_env_set_mapsize() correspond to calling mdbx_env_set_geometry() + * with the arguments size_lower, size_now, size_upper equal to the size + * and -1 (i.e. default) for all other parameters. + * + * [in] env An environment handle returned by mdbx_env_create() + * + * [in] size_lower The lower bound of database sive in bytes. + * Zero value means "minimal acceptable", + * and negative means "keep current or use default". + * + * [in] size_now The size in bytes to setup the database size for now. + * Zero value means "minimal acceptable", + * and negative means "keep current or use default". + * So, it is recommended always pass -1 in this argument + * except some special cases. + * + * [in] size_upper The upper bound of database sive in bytes. + * Zero value means "minimal acceptable", + * and negative means "keep current or use default". + * It is recommended to avoid change upper bound while + * database is used by other processes or threaded + * (i.e. just pass -1 in this argument except absolutely + * necessity). Otherwise you must be ready for + * MDBX_MAP_RESIZED error(s), unexpected pauses during + * remapping and/or system errors like "addtress busy", + * and so on. In other words, there is no way to handle + * a growth of the upper bound robustly because there may + * be a lack of appropriate system resources (which are + * extremely volatile in a multi-process multi-threaded + * environment). + * + * [in] growth_step The growth step in bytes, must be greater than zero + * to allow the database to grow. + * Negative value means "keep current or use default". + * + * [in] shrink_threshold The shrink threshold in bytes, must be greater than + * zero to allow the database to shrink. + * Negative value means "keep current or use default". + * + * [in] pagesize The database page size for new database creation + * or -1 otherwise. Must be power of 2 in the range + * between MDBX_MIN_PAGESIZE and MDBX_MAX_PAGESIZE. + * Zero value means "minimal acceptable", + * and negative means "keep current or use default". + * + * Returns A non-zero error value on failure and 0 on success, + * some possible errors are: + * - MDBX_EINVAL = An invalid parameter was specified, + * or the environment has an active write transaction. + * - MDBX_EPERM = specific for Windows: Shrinking was disabled before and + * now it wanna be enabled, but there are reading threads + * that don't use the additional SRWL (that is required to + * avoid Windows issues). + * - MDBX_EACCESS = The environment opened in read-only. + * - MDBX_MAP_FULL = Specified size smaller than the space already + * consumed by the environment. + * - MDBX_TOO_LARGE = Specified size is too large, i.e. too many pages for + * given size, or a 32-bit process requests too much bytes + * for the 32-bit address space. */ +LIBMDBX_API int mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, + intptr_t size_now, intptr_t size_upper, + intptr_t growth_step, + intptr_t shrink_threshold, + intptr_t pagesize); +LIBMDBX_API int mdbx_env_set_mapsize(MDBX_env *env, size_t size); + +/* Find out whether to use readahead or not, based on the given database size + * and the amount of available memory. + * + * [in] volume The expected database size in bytes. + * [in] redundancy Additional reserve or overload in case of negative value. + * + * Returns: + * - MDBX_RESULT_TRUE = readahead is reasonable. + * - MDBX_RESULT_FALSE = readahead is NOT reasonable, i.e. MDBX_NORDAHEAD + * is useful to open environment by mdbx_env_open(). + * - Otherwise the error code. */ +LIBMDBX_API int mdbx_is_readahead_reasonable(size_t volume, + intptr_t redundancy); + +/* The minimal database page size in bytes. */ +#define MDBX_MIN_PAGESIZE 256 +__inline intptr_t mdbx_limits_pgsize_min(void) { return MDBX_MIN_PAGESIZE; } + +/* The maximal database page size in bytes. */ +#define MDBX_MAX_PAGESIZE 65536 +__inline intptr_t mdbx_limits_pgsize_max(void) { return MDBX_MAX_PAGESIZE; } + +/* Returns minimal database size in bytes for given page size, + * or the negative error code. */ +LIBMDBX_API intptr_t mdbx_limits_dbsize_min(intptr_t pagesize); + +/* Returns maximal database size in bytes for given page size, + * or the negative error code. */ +LIBMDBX_API intptr_t mdbx_limits_dbsize_max(intptr_t pagesize); + +/* Returns maximal key size in bytes for given page size, + * or the negative error code. */ +LIBMDBX_API intptr_t mdbx_limits_keysize_max(intptr_t pagesize); + +/* Returns maximal write transaction size (i.e. limit for summary volume of + * dirty pages) in bytes for given page size, or the negative error code. */ +LIBMDBX_API intptr_t mdbx_limits_txnsize_max(intptr_t pagesize); + +/* Set the maximum number of threads/reader slots for the environment. + * + * This defines the number of slots in the lock table that is used to track + * readers in the the environment. The default is 119 for 4K system page size. + * Starting a read-only transaction normally ties a lock table slot to the + * current thread until the environment closes or the thread exits. If + * MDBX_NOTLS is in use, mdbx_txn_begin() instead ties the slot to the + * MDBX_txn object until it or the MDBX_env object is destroyed. + * This function may only be called after mdbx_env_create() and before + * mdbx_env_open(). + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] readers The maximum number of reader lock table slots. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. + * - MDBX_EPERM = the environment is already open. */ +LIBMDBX_API int mdbx_env_set_maxreaders(MDBX_env *env, unsigned readers); + +/* Get the maximum number of threads/reader slots for the environment. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [out] readers Address of an integer to store the number of readers. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_env_get_maxreaders(MDBX_env *env, unsigned *readers); + +/* Set the maximum number of named databases for the environment. + * + * This function is only needed if multiple databases will be used in the + * environment. Simpler applications that use the environment as a single + * unnamed database can ignore this option. + * This function may only be called after mdbx_env_create() and before + * mdbx_env_open(). + * + * Currently a moderate number of slots are cheap but a huge number gets + * expensive: 7-120 words per transaction, and every mdbx_dbi_open() + * does a linear search of the opened slots. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] dbs The maximum number of databases. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. + * - MDBX_EPERM = the environment is already open. */ +LIBMDBX_API int mdbx_env_set_maxdbs(MDBX_env *env, MDBX_dbi dbs); + +/* Get the maximum size of keys and MDBX_DUPSORT data we can write. + * + * [in] env An environment handle returned by mdbx_env_create(). + * + * Returns The maximum size of a key we can write. */ +LIBMDBX_API int mdbx_env_get_maxkeysize(MDBX_env *env); + +/* Set application information associated with the MDBX_env. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] ctx An arbitrary pointer for whatever the application needs. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_set_userctx(MDBX_env *env, void *ctx); + +/* Get the application information associated with the MDBX_env. + * + * [in] env An environment handle returned by mdbx_env_create() + * Returns The pointer set by mdbx_env_set_userctx(). */ +LIBMDBX_API void *mdbx_env_get_userctx(MDBX_env *env); + +/* Create a transaction for use with the environment. + * + * The transaction handle may be discarded using mdbx_txn_abort() + * or mdbx_txn_commit(). + * + * NOTE: A transaction and its cursors must only be used by a single thread, + * and a thread may only have a single transaction at a time. If MDBX_NOTLS is + * in use, this does not apply to read-only transactions. + * + * NOTE: Cursors may not span transactions. + * + * [in] env An environment handle returned by mdbx_env_create() + * [in] parent If this parameter is non-NULL, the new transaction will be + * a nested transaction, with the transaction indicated by parent + * as its parent. Transactions may be nested to any level. + * A parent transaction and its cursors may not issue any other + * operations than mdbx_txn_commit and mdbx_txn_abort while it + * has active child transactions. + * [in] flags Special options for this transaction. This parameter + * must be set to 0 or by bitwise OR'ing together one or more + * of the values described here. + * + * - MDBX_RDONLY + * This transaction will not perform any write operations. + * + * - MDBX_TRYTXN + * Do not block when starting a write transaction. + * + * - MDBX_NOSYNC, MDBX_NOMETASYNC or MDBX_MAPASYNC + * Do not sync data to disk corresponding to MDBX_NOMETASYNC + * or MDBX_NOSYNC description (see abobe). + * + * [out] txn Address where the new MDBX_txn handle will be stored + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_PANIC = a fatal error occurred earlier and the environment + * must be shut down. + * - MDBX_MAP_RESIZED = another process wrote data beyond this MDBX_env's + * mapsize and this environment's map must be resized + * as well. See mdbx_env_set_mapsize(). + * - MDBX_READERS_FULL = a read-only transaction was requested and the reader + * lock table is full. See mdbx_env_set_maxreaders(). + * - MDBX_ENOMEM = out of memory. + * - MDBX_BUSY = the write transaction is already started by the + * current thread. */ +LIBMDBX_API int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, unsigned flags, + MDBX_txn **txn); + +/* Information about the transaction */ +typedef struct MDBX_txn_info { + uint64_t txn_id; /* The ID of the transaction. For a READ-ONLY transaction, + this corresponds to the snapshot being read. */ + + uint64_t + txn_reader_lag; /* For READ-ONLY transaction: the lag from a recent + MVCC-snapshot, i.e. the number of committed + transaction since read transaction started. + For WRITE transaction (provided if scan_rlt=true): the + lag of the oldest reader from current transaction (i.e. + atleast 1 if any reader running). */ + + uint64_t txn_space_used; /* Used space by this transaction, i.e. corresponding + to the last used database page. */ + + uint64_t txn_space_limit_soft; /* Current size of database file. */ + + uint64_t + txn_space_limit_hard; /* Upper bound for size the database file, + i.e. the value "size_upper" argument of the + approriate call of mdbx_env_set_geometry(). */ + + uint64_t txn_space_retired; /* For READ-ONLY transaction: The total size of + the database pages that were retired by + committed write transactions after the reader's + MVCC-snapshot, i.e. the space which would be + freed after the Reader releases the + MVCC-snapshot for reuse by completion read + transaction. + For WRITE transaction: The summarized size of + the database pages that were retired for now + due Copy-On-Write during this transaction. */ + + uint64_t + txn_space_leftover; /* For READ-ONLY transaction: the space available for + writer(s) and that must be exhausted for reason to + call the OOM-killer for this read transaction. + For WRITE transaction: the space inside transaction + that left to MDBX_TXN_FULL error. */ + + uint64_t txn_space_dirty; /* For READ-ONLY transaction (provided if + scan_rlt=true): The space that actually become + available for reuse when only this transaction + will be finished. + For WRITE transaction: The summarized size of the + dirty database pages that generated during this + transaction. */ +} MDBX_txn_info; + +/* Return information about the MDBX transaction. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [out] stat The address of an MDBX_txn_info structure + * where the information will be copied. + * [in[ scan_rlt The boolean flag controls the scan of the read lock table to + * provide complete information. Such scan is relatively + * expensive and you can avoid it if corresponding fields are + * not needed (see description of MDBX_txn_info above). + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_txn_info(MDBX_txn *txn, MDBX_txn_info *info, int scan_rlt); + +/* Returns the transaction's MDBX_env. + * + * [in] txn A transaction handle returned by mdbx_txn_begin() */ +LIBMDBX_API MDBX_env *mdbx_txn_env(MDBX_txn *txn); + +/* Return the transaction's flags. + * + * This returns the flags associated with this transaction. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * + * Returns A transaction flags, valid if input is an valid transaction, + * otherwise -1. */ +LIBMDBX_API int mdbx_txn_flags(MDBX_txn *txn); + +/* Return the transaction's ID. + * + * This returns the identifier associated with this transaction. For a read-only + * transaction, this corresponds to the snapshot being read; concurrent readers + * will frequently have the same transaction ID. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * + * Returns A transaction ID, valid if input is an active transaction, + * otherwise 0. */ +LIBMDBX_API uint64_t mdbx_txn_id(MDBX_txn *txn); + +/* Commit all the operations of a transaction into the database. + * + * The transaction handle is freed. It and its cursors must not be used again + * after this call, except with mdbx_cursor_renew() and mdbx_cursor_close(). + * + * A cursor must be closed explicitly always, before or after its transaction + * ends. It can be reused with mdbx_cursor_renew() before finally closing it. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. + * - MDBX_ENOSPC = no more disk space. + * - MDBX_EIO = a low-level I/O error occurred while writing. + * - MDBX_ENOMEM = out of memory. */ +LIBMDBX_API int mdbx_txn_commit(MDBX_txn *txn); + +/* Abandon all the operations of the transaction instead of saving them. + * + * The transaction handle is freed. It and its cursors must not be used again + * after this call, except with mdbx_cursor_renew() and mdbx_cursor_close(). + * + * A cursor must be closed explicitly always, before or after its transaction + * ends. It can be reused with mdbx_cursor_renew() before finally closing it. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_txn_abort(MDBX_txn *txn); + +/* Reset a read-only transaction. + * + * Abort the read-only transaction like mdbx_txn_abort(), but keep the + * transaction handle. Therefore mdbx_txn_renew() may reuse the handle. This + * saves allocation overhead if the process will start a new read-only + * transaction soon, and also locking overhead if MDBX_NOTLS is in use. The + * reader table lock is released, but the table slot stays tied to its thread or + * MDBX_txn. Use mdbx_txn_abort() to discard a reset handle, and to free its + * lock table slot if MDBX_NOTLS is in use. + * + * Cursors opened within the transaction must not be used again after this call, + * except with mdbx_cursor_renew() and mdbx_cursor_close(). + * + * Reader locks generally don't interfere with writers, but they keep old + * versions of database pages allocated. Thus they prevent the old pages from + * being reused when writers commit new data, and so under heavy load the + * database size may grow much more rapidly than otherwise. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_txn_reset(MDBX_txn *txn); + +/* Renew a read-only transaction. + * + * This acquires a new reader lock for a transaction handle that had been + * released by mdbx_txn_reset(). It must be called before a reset transaction + * may be used again. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_PANIC = a fatal error occurred earlier and the environment + * must be shut down. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_txn_renew(MDBX_txn *txn); + +/* The fours integers markers (aka "canary") associated with the environment. + * + * The `x`, `y` and `z` values could be set by mdbx_canary_put(), while the 'v' + * will be always set to the transaction number. Updated values becomes visible + * outside the current transaction only after it was committed. Current values + * could be retrieved by mdbx_canary_get(). */ +typedef struct mdbx_canary { + uint64_t x, y, z, v; +} mdbx_canary; + +/* Set integers markers (aka "canary") associated with the environment. + * + * [in] txn A transaction handle returned by mdbx_txn_begin() + * [in] canary A optional pointer to mdbx_canary structure for `x`, `y` + * and `z` values from. + * - If canary is NOT NULL then the `x`, `y` and `z` values will be + * updated from given canary argument, but the 'v' be always set + * to the current transaction number if at least one `x`, `y` or + * `z` values have changed (i.e. if `x`, `y` and `z` have the same + * values as currently present then nothing will be changes or + * updated). + * - if canary is NULL then the `v` value will be explicitly update + * to the current transaction number without changes `x`, `y` nor + * `z`. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_canary_put(MDBX_txn *txn, const mdbx_canary *canary); + +/* Returns fours integers markers (aka "canary") associated with the + * environment. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] canary The address of an mdbx_canary structure where the information + * will be copied. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_canary_get(MDBX_txn *txn, mdbx_canary *canary); + +/* A callback function used to compare two keys in a database */ +typedef int(MDBX_cmp_func)(const MDBX_val *a, const MDBX_val *b); + +/* Open a database in the environment. + * + * A database handle denotes the name and parameters of a database, + * independently of whether such a database exists. The database handle may be + * discarded by calling mdbx_dbi_close(). The old database handle is returned if + * the database was already open. The handle may only be closed once. + * + * (!) A notable difference between MDBX and LMDB is that MDBX make handles + * opened for existing databases immediately available for other transactions, + * regardless this transaction will be aborted or reset. The REASON for this is + * to avoiding the requirement for multiple opening a same handles in concurrent + * read transactions, and tracking of such open but hidden handles until the + * completion of read transactions which opened them. + * + * Nevertheless, the handle for the NEWLY CREATED database will be invisible for + * other transactions until the this write transaction is successfully + * committed. If the write transaction is aborted the handle will be closed + * automatically. After a successful commit the such handle will reside in the + * shared environment, and may be used by other transactions. + * + * In contrast to LMDB, the MDBX allow this function to be called from multiple + * concurrent transactions or threads in the same process. + * + * Legacy mdbx_dbi_open() correspond to calling mdbx_dbi_open_ex() with the null + * keycmp and datacmp arguments. + * + * To use named database (with name != NULL), mdbx_env_set_maxdbs() + * must be called before opening the environment. Table names are + * keys in the internal unnamed database, and may be read but not written. + * + * [in] txn transaction handle returned by mdbx_txn_begin(). + * [in] name The name of the database to open. If only a single + * database is needed in the environment, this value may be NULL. + * [in] flags Special options for this database. This parameter must be set + * to 0 or by bitwise OR'ing together one or more of the values + * described here: + * - MDBX_REVERSEKEY + * Keys are strings to be compared in reverse order, from the end + * of the strings to the beginning. By default, Keys are treated as + * strings and compared from beginning to end. + * - MDBX_DUPSORT + * Duplicate keys may be used in the database. Or, from another point of + * view, keys may have multiple data items, stored in sorted order. By + * default keys must be unique and may have only a single data item. + * - MDBX_INTEGERKEY + * Keys are binary integers in native byte order, either uin32_t or + * uint64_t, and will be sorted as such. The keys must all be of the + * same size. + * - MDBX_DUPFIXED + * This flag may only be used in combination with MDBX_DUPSORT. This + * option tells the library that the data items for this database are + * all the same size, which allows further optimizations in storage and + * retrieval. When all data items are the same size, the MDBX_GET_MULTIPLE, + * MDBX_NEXT_MULTIPLE and MDBX_PREV_MULTIPLE cursor operations may be used + * to retrieve multiple items at once. + * - MDBX_INTEGERDUP + * This option specifies that duplicate data items are binary integers, + * similar to MDBX_INTEGERKEY keys. + * - MDBX_REVERSEDUP + * This option specifies that duplicate data items should be compared as + * strings in reverse order (the comparison is performed in the direction + * from the last byte to the first). + * - MDBX_CREATE + * Create the named database if it doesn't exist. This option is not + * allowed in a read-only transaction or a read-only environment. + * + * [in] keycmp Optional custom key comparison function for a database. + * [in] datacmp Optional custom data comparison function for a database, takes + * effect only if database was opened with the MDB_DUPSORT flag. + * [out] dbi Address where the new MDBX_dbi handle will be stored. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = the specified database doesn't exist in the + * environment and MDBX_CREATE was not specified. + * - MDBX_DBS_FULL = too many databases have been opened. + * See mdbx_env_set_maxdbs(). + * - MDBX_INCOMPATIBLE = Database is incompatible with given flags, + * i.e. the passed flags is different with which the + * database was created, or the database was already + * opened with a different comparison function(s). */ +LIBMDBX_API int mdbx_dbi_open_ex(MDBX_txn *txn, const char *name, + unsigned flags, MDBX_dbi *dbi, + MDBX_cmp_func *keycmp, MDBX_cmp_func *datacmp); +LIBMDBX_API int mdbx_dbi_open(MDBX_txn *txn, const char *name, unsigned flags, + MDBX_dbi *dbi); + +/* Retrieve statistics for a database. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [out] stat The address of an MDBX_stat structure where the statistics + * will be copied. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_dbi_stat(MDBX_txn *txn, MDBX_dbi dbi, MDBX_stat *stat, + size_t bytes); + +/* Retrieve the DB flags and status for a database handle. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [out] flags Address where the flags will be returned. + * [out] state Address where the state will be returned. + * + * Legacy mdbx_dbi_flags() correspond to calling mdbx_dbi_flags_ex() with + * discarding result from the last argument. + * + * Returns A non-zero error value on failure and 0 on success. */ +#define MDBX_TBL_DIRTY 0x01 /* DB was written in this txn */ +#define MDBX_TBL_STALE 0x02 /* Named-DB record is older than txnID */ +#define MDBX_TBL_FRESH 0x04 /* Named-DB handle opened in this txn */ +#define MDBX_TBL_CREAT 0x08 /* Named-DB handle created in this txn */ +LIBMDBX_API int mdbx_dbi_flags_ex(MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags, + unsigned *state); +LIBMDBX_API int mdbx_dbi_flags(MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags); + +/* Close a database handle. Normally unnecessary. + * + * NOTE: Use with care. + * This call is synchronized via mutex with mdbx_dbi_close(), but NOT with + * other transactions running by other threads. The "next" version of libmdbx + * (MithrilDB) will solve this issue. + * + * Handles should only be closed if no other threads are going to reference + * the database handle or one of its cursors any further. Do not close a handle + * if an existing transaction has modified its database. Doing so can cause + * misbehavior from database corruption to errors like MDBX_BAD_VALSIZE (since + * the DB name is gone). + * + * Closing a database handle is not necessary, but lets mdbx_dbi_open() reuse + * the handle value. Usually it's better to set a bigger mdbx_env_set_maxdbs(), + * unless that value would be large. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_dbi_close(MDBX_env *env, MDBX_dbi dbi); + +/* Empty or delete and close a database. + * + * See mdbx_dbi_close() for restrictions about closing the DB handle. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] del 0 to empty the DB, 1 to delete it from the environment + * and close the DB handle. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, int del); + +/* Get items from a database. + * + * This function retrieves key/data pairs from the database. The address + * and length of the data associated with the specified key are returned + * in the structure to which data refers. + * If the database supports duplicate keys (MDBX_DUPSORT) then the + * first data item for the key will be returned. Retrieval of other + * items requires the use of mdbx_cursor_get(). + * + * NOTE: The memory pointed to by the returned values is owned by the + * database. The caller need not dispose of the memory, and may not + * modify it in any way. For values returned in a read-only transaction + * any modification attempts will cause a SIGSEGV. + * + * NOTE: Values returned from the database are valid only until a + * subsequent update operation, or the end of the transaction. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to search for in the database. + * [in,out] data The data corresponding to the key. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = the key was not in the database. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data); + +/* Get items from a database and optionaly number of data items for a given key. + * + * Briefly this function does the same as mdbx_get() with a few differences: + * 1. If values_count is NOT NULL, then returns the count + * of multi-values/duplicates for a given key. + * 2. Updates BOTH the key and the data for pointing to the actual key-value + * pair inside the database. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in,out] key The key to search for in the database. + * [in,out] data The data corresponding to the key. + * [out] values_count The optional address to return number of values + * associated with given key, i.e. + * = 0 - in case MDBX_NOTFOUND error; + * = 1 - exactly for databases WITHOUT MDBX_DUPSORT; + * >= 1 for databases WITH MDBX_DUPSORT. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = the key was not in the database. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_get_ex(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data, size_t *values_count); + +/* Get nearest items from a database. + * + * Briefly this function does the same as mdbx_get() with a few differences: + * 1. Return nearest (i.e. equal or great due comparison function) key-value + * pair, but not only exactly matching with the key. + * 2. On success return MDBX_SUCCESS if key found exactly, + * and MDBX_RESULT_TRUE otherwise. Moreover, for databases with MDBX_DUPSORT + * flag the data argument also will be used to match over + * multi-value/duplicates, and MDBX_SUCCESS will be returned only when BOTH + * the key and the data match exactly. + * 3. Updates BOTH the key and the data for pointing to the actual key-value + * pair inside the database. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in,out] key The key to search for in the database. + * [in,out] data The data corresponding to the key. + * + * Returns A non-zero error value on failure and MDBX_RESULT_TRUE (0) or + * MDBX_RESULT_TRUE on success (as described above). + * Some possible errors are: + * - MDBX_NOTFOUND = the key was not in the database. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_get_nearest(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data); + +/* Store items into a database. + * + * This function stores key/data pairs in the database. The default behavior + * is to enter the new key/data pair, replacing any previously existing key + * if duplicates are disallowed, or adding a duplicate data item if + * duplicates are allowed (MDBX_DUPSORT). + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to store in the database. + * [in,out] data The data to store. + * [in] flags Special options for this operation. This parameter must be + * set to 0 or by bitwise OR'ing together one or more of the + * values described here. + * + * - MDBX_NODUPDATA + * Enter the new key/data pair only if it does not already appear + * in the database. This flag may only be specified if the database + * was opened with MDBX_DUPSORT. The function will return MDBX_KEYEXIST + * if the key/data pair already appears in the database. + * + * - MDBX_NOOVERWRITE + * Enter the new key/data pair only if the key does not already appear + * in the database. The function will return MDBX_KEYEXIST if the key + * already appears in the database, even if the database supports + * duplicates (MDBX_DUPSORT). The data parameter will be set to point + * to the existing item. + * + * - MDBX_CURRENT + * Update an single existing entry, but not add new ones. The function + * will return MDBX_NOTFOUND if the given key not exist in the database. + * Or the MDBX_EMULTIVAL in case duplicates for the given key. + * + * - MDBX_RESERVE + * Reserve space for data of the given size, but don't copy the given + * data. Instead, return a pointer to the reserved space, which the + * caller can fill in later - before the next update operation or the + * transaction ends. This saves an extra memcpy if the data is being + * generated later. MDBX does nothing else with this memory, the caller + * is expected to modify all of the space requested. This flag must not + * be specified if the database was opened with MDBX_DUPSORT. + * + * - MDBX_APPEND + * Append the given key/data pair to the end of the database. This option + * allows fast bulk loading when keys are already known to be in the + * correct order. Loading unsorted keys with this flag will cause + * a MDBX_EKEYMISMATCH error. + * + * - MDBX_APPENDDUP + * As above, but for sorted dup data. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_KEYEXIST + * - MDBX_MAP_FULL = the database is full, see mdbx_env_set_mapsize(). + * - MDBX_TXN_FULL = the transaction has too many dirty pages. + * - MDBX_EACCES = an attempt was made to write in a read-only transaction. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data, unsigned flags); + +/* Replace items in a database. + * + * This function allows to update or delete an existing value at the same time + * as the previous value is retrieved. If the argument new_data equal is NULL + * zero, the removal is performed, otherwise the update/insert. + * + * The current value may be in an already changed (aka dirty) page. In this + * case, the page will be overwritten during the update, and the old value will + * be lost. Therefore, an additional buffer must be passed via old_data argument + * initially to copy the old value. If the buffer passed in is too small, the + * function will return MDBX_RESULT_TRUE (-1) by setting iov_len field pointed + * by old_data argument to the appropriate value, without performing any + * changes. + * + * For databases with non-unique keys (i.e. with MDBX_DUPSORT flag), another use + * case is also possible, when by old_data argument selects a specific item from + * multi-value/duplicates with the same key for deletion or update. To select + * this scenario in flags should simultaneously specify MDBX_CURRENT and + * MDBX_NOOVERWRITE. This combination is chosen because it makes no sense, and + * thus allows you to identify the request of such a scenario. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to store in the database. + * [in,out] new_data The data to store, if NULL then deletion will be + * performed. + * [in,out] old_data The buffer for retrieve previous value as describe + * above. + * [in] flags Special options for this operation. This parameter must + * be set to 0 or by bitwise OR'ing together one or more of + * the values described in mdbx_put() description above, + * and additionally (MDBX_CURRENT | MDBX_NOOVERWRITE) + * combination for selection particular item from + * multi-value/duplicates. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *new_data, MDBX_val *old_data, + unsigned flags); + +/* Delete items from a database. + * + * This function removes key/data pairs from the database. + * + * NOTE: The data parameter is NOT ignored regardless the database does + * support sorted duplicate data items or not. If the data parameter + * is non-NULL only the matching data item will be deleted. + * + * This function will return MDBX_NOTFOUND if the specified key/data + * pair is not in the database. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to delete from the database. + * [in] data The data to delete. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EACCES = an attempt was made to write in a read-only transaction. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data); + +/* Create a cursor handle. + * + * A cursor is associated with a specific transaction and database. A cursor + * cannot be used when its database handle is closed. Nor when its transaction + * has ended, except with mdbx_cursor_renew(). Also it can be discarded with + * mdbx_cursor_close(). + * + * A cursor must be closed explicitly always, before or after its transaction + * ends. It can be reused with mdbx_cursor_renew() before finally closing it. + * + * NOTE: In contrast to LMDB, the MDBX required that any opened cursors can be + * reused and must be freed explicitly, regardless ones was opened in a + * read-only or write transaction. The REASON for this is eliminates ambiguity + * which helps to avoid errors such as: use-after-free, double-free, i.e. memory + * corruption and segfaults. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [out] cursor Address where the new MDBX_cursor handle will be stored. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, + MDBX_cursor **cursor); + +/* Close a cursor handle. + * + * The cursor handle will be freed and must not be used again after this call, + * but its transaction may still be live. + * + * NOTE: In contrast to LMDB, the MDBX required that any opened cursors can be + * reused and must be freed explicitly, regardless ones was opened in a + * read-only or write transaction. The REASON for this is eliminates ambiguity + * which helps to avoid errors such as: use-after-free, double-free, i.e. memory + * corruption and segfaults. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). */ +LIBMDBX_API void mdbx_cursor_close(MDBX_cursor *cursor); + +/* Renew a cursor handle. + * + * A cursor is associated with a specific transaction and database. The cursor + * may be associated with a new transaction, and referencing the same database + * handle as it was created with. This may be done whether the previous + * transaction is live or dead. + * + * NOTE: In contrast to LMDB, the MDBX allow any cursor to be re-used by using + * mdbx_cursor_renew(), to avoid unnecessary malloc/free overhead until it freed + * by mdbx_cursor_close(). + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *cursor); + +/* Return the cursor's transaction handle. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). */ +LIBMDBX_API MDBX_txn *mdbx_cursor_txn(MDBX_cursor *cursor); + +/* Return the cursor's database handle. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). */ +LIBMDBX_API MDBX_dbi mdbx_cursor_dbi(MDBX_cursor *cursor); + +/* Retrieve by cursor. + * + * This function retrieves key/data pairs from the database. The address and + * length of the key are returned in the object to which key refers (except + * for the case of the MDBX_SET option, in which the key object is unchanged), + * and the address and length of the data are returned in the object to which + * data refers. See mdbx_get() for restrictions on using the output values. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * [in,out] key The key for a retrieved item. + * [in,out] data The data of a retrieved item. + * [in] op A cursor operation MDBX_cursor_op. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = no matching key found. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_get(MDBX_cursor *cursor, MDBX_val *key, + MDBX_val *data, MDBX_cursor_op op); + +/* Store by cursor. + * + * This function stores key/data pairs into the database. The cursor is + * positioned at the new item, or on failure usually near it. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * [in] key The key operated on. + * [in] data The data operated on. + * [in] flags Options for this operation. This parameter + * must be set to 0 or one of the values described here: + * + * - MDBX_CURRENT + * Replace the item at the current cursor position. The key parameter + * must still be provided, and must match it, otherwise the function + * return MDBX_EKEYMISMATCH. + * + * NOTE: MDBX unlike LMDB allows you to change the size of the data and + * automatically handles reordering for sorted duplicates (MDBX_DUPSORT). + * + * - MDBX_NODUPDATA + * Enter the new key/data pair only if it does not already appear in the + * database. This flag may only be specified if the database was opened + * with MDBX_DUPSORT. The function will return MDBX_KEYEXIST if the + * key/data pair already appears in the database. + * + * - MDBX_NOOVERWRITE + * Enter the new key/data pair only if the key does not already appear + * in the database. The function will return MDBX_KEYEXIST if the key + * already appears in the database, even if the database supports + * duplicates (MDBX_DUPSORT). + * + * - MDBX_RESERVE + * Reserve space for data of the given size, but don't copy the given + * data. Instead, return a pointer to the reserved space, which the + * caller can fill in later - before the next update operation or the + * transaction ends. This saves an extra memcpy if the data is being + * generated later. This flag must not be specified if the database + * was opened with MDBX_DUPSORT. + * + * - MDBX_APPEND + * Append the given key/data pair to the end of the database. No key + * comparisons are performed. This option allows fast bulk loading when + * keys are already known to be in the correct order. Loading unsorted + * keys with this flag will cause a MDBX_KEYEXIST error. + * + * - MDBX_APPENDDUP + * As above, but for sorted dup data. + * + * - MDBX_MULTIPLE + * Store multiple contiguous data elements in a single request. This flag + * may only be specified if the database was opened with MDBX_DUPFIXED. + * The data argument must be an array of two MDBX_vals. The iov_len of the + * first MDBX_val must be the size of a single data element. The iov_base + * of the first MDBX_val must point to the beginning of the array of + * contiguous data elements. The iov_len of the second MDBX_val must be + * the count of the number of data elements to store. On return this + * field will be set to the count of the number of elements actually + * written. The iov_base of the second MDBX_val is unused. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EKEYMISMATCH + * - MDBX_MAP_FULL = the database is full, see mdbx_env_set_mapsize(). + * - MDBX_TXN_FULL = the transaction has too many dirty pages. + * - MDBX_EACCES = an attempt was made to write in a read-only transaction. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_put(MDBX_cursor *cursor, MDBX_val *key, + MDBX_val *data, unsigned flags); + +/* Delete current key/data pair. + * + * This function deletes the key/data pair to which the cursor refers. This does + * not invalidate the cursor, so operations such as MDBX_NEXT can still be used + * on it. Both MDBX_NEXT and MDBX_GET_CURRENT will return the same record after + * this operation. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * [in] flags Options for this operation. This parameter must be set to 0 + * or one of the values described here. + * + * - MDBX_NODUPDATA + * Delete all of the data items for the current key. This flag may only + * be specified if the database was opened with MDBX_DUPSORT. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EACCES = an attempt was made to write in a read-only transaction. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_del(MDBX_cursor *cursor, unsigned flags); + +/* Return count of duplicates for current key. + * + * This call is valid for all databases, but reasonable only for that support + * sorted duplicate data items MDBX_DUPSORT. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * [out] countp Address where the count will be stored. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EINVAL = cursor is not initialized, or an invalid parameter + * was specified. */ +LIBMDBX_API int mdbx_cursor_count(MDBX_cursor *cursor, size_t *countp); + +/* Determines whether the cursor is pointed to a key-value pair or not, + * i.e. was not positioned or points to the end of data. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * + * Returns: + * - MDBX_RESULT_TRUE = no more data available or cursor not positioned; + * - MDBX_RESULT_FALSE = data available; + * - Otherwise the error code. */ +LIBMDBX_API int mdbx_cursor_eof(MDBX_cursor *mc); + +/* Determines whether the cursor is pointed to the first key-value pair or not. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * + * Returns: + * - MDBX_RESULT_TRUE = cursor positioned to the first key-value pair. + * - MDBX_RESULT_FALSE = cursor NOT positioned to the first key-value pair. + * - Otherwise the error code. */ +LIBMDBX_API int mdbx_cursor_on_first(MDBX_cursor *mc); + +/* Determines whether the cursor is pointed to the last key-value pair or not. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * + * Returns: + * - MDBX_RESULT_TRUE = cursor positioned to the last key-value pair. + * - MDBX_RESULT_FALSE = cursor NOT positioned to the last key-value pair. + * - Otherwise the error code. */ +LIBMDBX_API int mdbx_cursor_on_last(MDBX_cursor *mc); + +/* Estimates the distance between cursors as a number of elements. The results + * of such estimation can be used to build and/or optimize query execution + * plans. + * + * This function performs a rough estimate based only on b-tree pages that are + * common for the both cursor's stacks. + * + * NOTE: The result varies greatly depending on the filling of specific pages + * and the overall balance of the b-tree: + * + * 1. The number of items is estimated by analyzing the height and fullness of + * the b-tree. The accuracy of the result directly depends on the balance of the + * b-tree, which in turn is determined by the history of previous insert/delete + * operations and the nature of the data (i.e. variability of keys length and so + * on). Therefore, the accuracy of the estimation can vary greatly in a + * particular situation. + * + * 2. To understand the potential spread of results, you should consider a + * possible situations basing on the general criteria for splitting and merging + * b-tree pages: + * - the page is split into two when there is no space for added data; + * - two pages merge if the result fits in half a page; + * - thus, the b-tree can consist of an arbitrary combination of pages filled + * both completely and only 1/4. Therefore, in the worst case, the result + * can diverge 4 times for each level of the b-tree excepting the first and + * the last. + * + * 3. In practice, the probability of extreme cases of the above situation is + * close to zero and in most cases the error does not exceed a few percent. On + * the other hand, it's just a chance you shouldn't overestimate. + * + * Both cursors must be initialized for the same database and the same + * transaction. + * + * [in] first The first cursor for estimation. + * [in] last The second cursor for estimation. + * [out] distance_items A pointer to store estimated distance value, + * i.e. *distance_items = distance(first, last). + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_estimate_distance(const MDBX_cursor *first, + const MDBX_cursor *last, + ptrdiff_t *distance_items); + +/* Estimates the move distance, i.e. between the current cursor position and + * next position after the specified move-operation with given key and data. + * The results of such estimation can be used to build and/or optimize query + * execution plans. Current cursor position and state are preserved. + * + * Please see notes on accuracy of the result in mdbx_estimate_distance() + * description above. + * + * [in] cursor Cursor for estimation. + * [in,out] key The key for a retrieved item. + * [in,out] data The data of a retrieved item. + * [in] op A cursor operation MDBX_cursor_op. + * [out] distance_items A pointer to store estimated move distance + * as the number of elements. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_estimate_move(const MDBX_cursor *cursor, MDBX_val *key, + MDBX_val *data, MDBX_cursor_op move_op, + ptrdiff_t *distance_items); + +/* Estimates the size of a range as a number of elements. The results + * of such estimation can be used to build and/or optimize query execution + * plans. + * + * Please see notes on accuracy of the result in mdbx_estimate_distance() + * description above. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] begin_key The key of range beginning or NULL for explicit FIRST. + * [in] begin_data Optional additional data to seeking among sorted + * duplicates. Only for MDBX_DUPSORT, NULL otherwise. + * [in] end_key The key of range ending or NULL for explicit LAST. + * [in] end_data Optional additional data to seeking among sorted + * duplicates. Only for MDBX_DUPSORT, NULL otherwise. + * [out] distance_items A pointer to store range estimation result. + * + * Returns A non-zero error value on failure and 0 on success. */ +#define MDBX_EPSILON ((MDBX_val *)((ptrdiff_t)-1)) +LIBMDBX_API int mdbx_estimate_range(MDBX_txn *txn, MDBX_dbi dbi, + MDBX_val *begin_key, MDBX_val *begin_data, + MDBX_val *end_key, MDBX_val *end_data, + ptrdiff_t *size_items); + +/* Determines whether the given address is on a dirty database page of the + * transaction or not. Ultimately, this allows to avoid copy data from non-dirty + * pages. + * + * "Dirty" pages are those that have already been changed during a write + * transaction. Accordingly, any further changes may result in such pages being + * overwritten. Therefore, all functions libmdbx performing changes inside the + * database as arguments should NOT get pointers to data in those pages. In + * turn, "not dirty" pages before modification will be copied. + * + * In other words, data from dirty pages must either be copied before being + * passed as arguments for further processing or rejected at the argument + * validation stage. Thus, mdbx_is_dirty() allows you to get rid of unnecessary + * copying, and perform a more complete check of the arguments. + * + * NOTE: The address passed must point to the beginning of the data. This is the + * only way to ensure that the actual page header is physically located in the + * same memory page, including for multi-pages with long data. + * + * NOTE: In rare cases the function may return a false positive answer + * (DBX_RESULT_TRUE when data is NOT on a dirty page), but never a false + * negative if the arguments are correct. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] ptr The address of data to check. + * + * Returns: + * - MDBX_RESULT_TRUE = given address is on the dirty page. + * - MDBX_RESULT_FALSE = given address is NOT on the dirty page. + * - Otherwise the error code. */ +LIBMDBX_API int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr); + +/* Sequence generation for a database. + * + * The function allows to create a linear sequence of unique positive integers + * for each database. The function can be called for a read transaction to + * retrieve the current sequence value, and the increment must be zero. + * Sequence changes become visible outside the current write transaction after + * it is committed, and discarded on abort. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [out] result The optional address where the value of sequence before the + * change will be stored. + * [in] increment Value to increase the sequence, + * must be 0 for read-only transactions. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_RESULT_TRUE = Increasing the sequence has resulted in an overflow + * and therefore cannot be executed. */ +LIBMDBX_API int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result, + uint64_t increment); + +/* Compare two data items according to a particular database. + * + * This returns a comparison as if the two data items were keys in the + * specified database. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] a The first item to compare. + * [in] b The second item to compare. + * + * Returns < 0 if a < b, 0 if a == b, > 0 if a > b */ +LIBMDBX_API int mdbx_cmp(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, + const MDBX_val *b); + +/* Compare two data items according to a particular database. + * + * This returns a comparison as if the two items were data items of the + * specified database. The database must have the MDBX_DUPSORT flag. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] a The first item to compare. + * [in] b The second item to compare. + * + * Returns < 0 if a < b, 0 if a == b, > 0 if a > b */ +LIBMDBX_API int mdbx_dcmp(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, + const MDBX_val *b); + +/* A callback function used to enumerate the reader lock table. + * + * [in] ctx An arbitrary context pointer for the callback. + * [in] num The serial number during enumeration, starting from 1. + * [in] slot The reader lock table slot number. + * [in] txnid The ID of the transaction being read, + * i.e. the MVCC-snaphot number. + * [in] lag The lag from a recent MVCC-snapshot, i.e. the number of + * committed transaction since read transaction started. + * [in] pid The reader process ID. + * [in] thread The reader thread ID. + * [in] bytes_used The number of last used page in the MVCC-snapshot which + * being read, i.e. database file can't shrinked beyond this. + * [in] bytes_retired The total size of the database pages that were retired by + * committed write transactions after the reader's + * MVCC-snapshot, i.e. the space which would be freed after + * the Reader releases the MVCC-snapshot for reuse by + * completion read transaction. + * + * Returns < 0 on failure, >= 0 on success. */ +typedef int(MDBX_reader_list_func)(void *ctx, int num, int slot, mdbx_pid_t pid, + mdbx_tid_t thread, uint64_t txnid, + uint64_t lag, size_t bytes_used, + size_t bytes_retained); + +/* Enumarete the entries in the reader lock table. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] func A MDBX_reader_list_func function. + * [in] ctx An arbitrary context pointer for the enumeration function. + * + * Returns A non-zero error value on failure and 0 on success, + * or MDBX_RESULT_TRUE (-1) if the reader lock table is empty. */ +LIBMDBX_API int mdbx_reader_list(MDBX_env *env, MDBX_reader_list_func *func, + void *ctx); + +/* Check for stale entries in the reader lock table. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [out] dead Number of stale slots that were cleared. + * + * Returns A non-zero error value on failure and 0 on success, + * or MDBX_RESULT_TRUE (-1) if a dead reader(s) found or mutex was recovered. */ +LIBMDBX_API int mdbx_reader_check(MDBX_env *env, int *dead); + +/* Returns a lag of the reading for the given transaction. + * + * Returns an information for estimate how much given read-only + * transaction is lagging relative the to actual head. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [out] percent Percentage of page allocation in the database. + * + * Returns Number of transactions committed after the given was started for + * read, or negative value on failure. */ +LIBMDBX_API int mdbx_txn_straggler(MDBX_txn *txn, int *percent); + +/* A lack-of-space callback function to resolve issues with a laggard readers. + * + * Read transactions prevent reuse of pages freed by newer write transactions, + * thus the database can grow quickly. This callback will be called when there + * is not enough space in the database (ie. before increasing the database size + * or before MDBX_MAP_FULL error) and thus can be used to resolve issues with + * a "long-lived" read transactions. + * + * Depending on the arguments and needs, your implementation may wait, terminate + * a process or thread that is performing a long read, or perform some other + * action. In doing so it is important that the returned code always corresponds + * to the performed action. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] pid A pid of the reader process. + * [in] tid A thread_id of the reader thread. + * [in] txn A transaction number on which stalled. + * [in] gap A lag from the last commited txn. + * [in] space A space that actually become available for reuse after this + * reader finished. The callback function can take this value into + * account to evaluate the impact that a long-running transaction + * has. + * [in] retry A retry number starting from 0. if callback has returned 0 + * at least once, then at end of current OOM-handler loop callback + * will be called additionally with negative value to notify about + * the end of loop. The callback function can use this value to + * implement timeout logic while waiting for readers. + * + * The RETURN CODE determines the further actions libmdbx and must match the + * action which was executed by the callback: + * + * -2 or less = An error condition and the reader was not killed. + * + * -1 = The callback was unable to solve the problem and agreed + * on MDBX_MAP_FULL error, libmdbx should increase the + * database size or return MDBX_MAP_FULL error. + * + * 0 (zero) = The callback solved the problem or just waited for + * a while, libmdbx should rescan the reader lock table and + * retry. This also includes a situation when corresponding + * transaction terminated in normal way by mdbx_txn_abort() + * or mdbx_txn_reset(), and my be restarted. I.e. reader + * slot don't needed to be cleaned from transaction. + * + * 1 = Transaction aborted asynchronous and reader slot should + * be cleared immediately, i.e. read transaction will not + * continue but mdbx_txn_abort() or mdbx_txn_reset() will + * be called later. + * + * 2 or great = The reader process was terminated or killed, and libmdbx + * should entirely reset reader registration. */ +typedef int(MDBX_oom_func)(MDBX_env *env, mdbx_pid_t pid, mdbx_tid_t tid, + uint64_t txn, unsigned gap, size_t space, int retry); + +/* Set the OOM callback. + * + * The callback will only be triggered on lack of space to resolve issues with + * lagging reader(s) (i.e. to kill it) for resume reuse pages from the garbage + * collector. + * + * [in] env An environment handle returned by mdbx_env_create(). + * [in] oom_func A MDBX_oom_func function or NULL to disable. + * + * Returns A non-zero error value on failure and 0 on success. */ +LIBMDBX_API int mdbx_env_set_oomfunc(MDBX_env *env, MDBX_oom_func *oom_func); + +/* Get the current oom_func callback. + * + * [in] env An environment handle returned by mdbx_env_create(). + * + * Returns A MDBX_oom_func function or NULL if disabled. */ +LIBMDBX_API MDBX_oom_func *mdbx_env_get_oomfunc(MDBX_env *env); + +/**** B-tree Traversal ********************************************************* + * This is internal API for mdbx_chk tool. You should avoid to use it, except + * some extremal special cases. */ + +/* Page types for traverse the b-tree. */ +typedef enum { + MDBX_page_void, + MDBX_page_meta, + MDBX_page_large, + MDBX_page_branch, + MDBX_page_leaf, + MDBX_page_dupfixed_leaf, + MDBX_subpage_leaf, + MDBX_subpage_dupfixed_leaf +} MDBX_page_type_t; + +#define MDBX_PGWALK_MAIN ((const char *)((ptrdiff_t)0)) +#define MDBX_PGWALK_GC ((const char *)((ptrdiff_t)-1)) +#define MDBX_PGWALK_META ((const char *)((ptrdiff_t)-2)) + +/* Callback function for traverse the b-tree. */ +typedef int +MDBX_pgvisitor_func(const uint64_t pgno, const unsigned number, void *const ctx, + const int deep, const char *const dbi, + const size_t page_size, const MDBX_page_type_t type, + const size_t nentries, const size_t payload_bytes, + const size_t header_bytes, const size_t unused_bytes); + +/* B-tree traversal function. */ +LIBMDBX_API int mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor, + void *ctx); + +/**** Attribute support functions for Nexenta *********************************/ +#ifdef MDBX_NEXENTA_ATTRS +typedef uint_fast64_t mdbx_attr_t; + +/* Store by cursor with attribute. + * + * This function stores key/data pairs into the database. The cursor is + * positioned at the new item, or on failure usually near it. + * + * NOTE: Internally based on MDBX_RESERVE feature, + * therefore doesn't support MDBX_DUPSORT. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open() + * [in] key The key operated on. + * [in] data The data operated on. + * [in] attr The attribute. + * [in] flags Options for this operation. This parameter must be set to 0 + * or one of the values described here: + * + * - MDBX_CURRENT + * Replace the item at the current cursor position. The key parameter + * must still be provided, and must match it, otherwise the function + * return MDBX_EKEYMISMATCH. + * + * - MDBX_APPEND + * Append the given key/data pair to the end of the database. No key + * comparisons are performed. This option allows fast bulk loading when + * keys are already known to be in the correct order. Loading unsorted + * keys with this flag will cause a MDBX_KEYEXIST error. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_EKEYMISMATCH + * - MDBX_MAP_FULL = the database is full, see mdbx_env_set_mapsize(). + * - MDBX_TXN_FULL = the transaction has too many dirty pages. + * - MDBX_EACCES = an attempt was made to write in a read-only transaction. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_put_attr(MDBX_cursor *cursor, MDBX_val *key, + MDBX_val *data, mdbx_attr_t attr, + unsigned flags); + +/* Store items and attributes into a database. + * + * This function stores key/data pairs in the database. The default behavior + * is to enter the new key/data pair, replacing any previously existing key + * if duplicates are disallowed. + * + * NOTE: Internally based on MDBX_RESERVE feature, + * therefore doesn't support MDBX_DUPSORT. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to store in the database. + * [in] attr The attribute to store in the database. + * [in,out] data The data to store. + * [in] flags Special options for this operation. This parameter must be + * set to 0 or by bitwise OR'ing together one or more of the + * values described here: + * + * - MDBX_NOOVERWRITE + * Enter the new key/data pair only if the key does not already appear + * in the database. The function will return MDBX_KEYEXIST if the key + * already appears in the database. The data parameter will be set to + * point to the existing item. + * + * - MDBX_CURRENT + * Update an single existing entry, but not add new ones. The function + * will return MDBX_NOTFOUND if the given key not exist in the database. + * Or the MDBX_EMULTIVAL in case duplicates for the given key. + * + * - MDBX_APPEND + * Append the given key/data pair to the end of the database. This option + * allows fast bulk loading when keys are already known to be in the + * correct order. Loading unsorted keys with this flag will cause + * a MDBX_EKEYMISMATCH error. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_KEYEXIST + * - MDBX_MAP_FULL = the database is full, see mdbx_env_set_mapsize(). + * - MDBX_TXN_FULL = the transaction has too many dirty pages. + * - MDBX_EACCES = an attempt was made to write in a read-only transaction. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_put_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data, mdbx_attr_t attr, unsigned flags); + +/* Set items attribute from a database. + * + * This function stores key/data pairs attribute to the database. + * + * NOTE: Internally based on MDBX_RESERVE feature, + * therefore doesn't support MDBX_DUPSORT. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to search for in the database. + * [in] data The data to be stored or NULL to save previous value. + * [in] attr The attribute to be stored. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = the key-value pair was not in the database. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_set_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data, mdbx_attr_t attr); + +/* Get items attribute from a database cursor. + * + * This function retrieves key/data pairs from the database. The address and + * length of the key are returned in the object to which key refers (except + * for the case of the MDBX_SET option, in which the key object is unchanged), + * and the address and length of the data are returned in the object to which + * data refers. See mdbx_get() for restrictions on using the output values. + * + * [in] cursor A cursor handle returned by mdbx_cursor_open(). + * [in,out] key The key for a retrieved item. + * [in,out] data The data of a retrieved item. + * [in] op A cursor operation MDBX_cursor_op. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = no matching key found. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_cursor_get_attr(MDBX_cursor *mc, MDBX_val *key, + MDBX_val *data, mdbx_attr_t *attrptr, + MDBX_cursor_op op); + +/* Get items attribute from a database. + * + * This function retrieves key/data pairs from the database. The address + * and length of the data associated with the specified key are returned + * in the structure to which data refers. + * If the database supports duplicate keys (MDBX_DUPSORT) then the + * first data item for the key will be returned. Retrieval of other + * items requires the use of mdbx_cursor_get(). + * + * NOTE: The memory pointed to by the returned values is owned by the + * database. The caller need not dispose of the memory, and may not + * modify it in any way. For values returned in a read-only transaction + * any modification attempts will cause a SIGSEGV. + * + * NOTE: Values returned from the database are valid only until a + * subsequent update operation, or the end of the transaction. + * + * [in] txn A transaction handle returned by mdbx_txn_begin(). + * [in] dbi A database handle returned by mdbx_dbi_open(). + * [in] key The key to search for in the database. + * [in,out] data The data corresponding to the key. + * + * Returns A non-zero error value on failure and 0 on success, some + * possible errors are: + * - MDBX_NOTFOUND = the key was not in the database. + * - MDBX_EINVAL = an invalid parameter was specified. */ +LIBMDBX_API int mdbx_get_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data, mdbx_attr_t *attrptr); +#endif /* MDBX_NEXENTA_ATTRS */ + +/******************************************************************************* + * LY: temporary workaround for Elbrus's memcmp() bug. */ +#ifndef __GLIBC_PREREQ +#if defined(__GLIBC__) && defined(__GLIBC_MINOR__) +#define __GLIBC_PREREQ(maj, min) \ + ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min)) +#else +#define __GLIBC_PREREQ(maj, min) (0) +#endif +#endif /* __GLIBC_PREREQ */ +#if defined(__e2k__) && !__GLIBC_PREREQ(2, 24) +LIBMDBX_API int mdbx_e2k_memcmp_bug_workaround(const void *s1, const void *s2, + size_t n); +LIBMDBX_API int mdbx_e2k_strcmp_bug_workaround(const char *s1, const char *s2); +LIBMDBX_API int mdbx_e2k_strncmp_bug_workaround(const char *s1, const char *s2, + size_t n); +LIBMDBX_API size_t mdbx_e2k_strlen_bug_workaround(const char *s); +LIBMDBX_API size_t mdbx_e2k_strnlen_bug_workaround(const char *s, + size_t maxlen); +#include +#include +#undef memcmp +#define memcmp mdbx_e2k_memcmp_bug_workaround +#undef bcmp +#define bcmp mdbx_e2k_memcmp_bug_workaround +#undef strcmp +#define strcmp mdbx_e2k_strcmp_bug_workaround +#undef strncmp +#define strncmp mdbx_e2k_strncmp_bug_workaround +#undef strlen +#define strlen mdbx_e2k_strlen_bug_workaround +#undef strnlen +#define strnlen mdbx_e2k_strnlen_bug_workaround + +#endif /* Elbrus's memcmp() bug. */ + +#ifdef __cplusplus +} +#endif + +#endif /* LIBMDBX_H */ diff --git a/contrib/db/libmdbx/packages/rpm/CMakeLists.txt b/contrib/db/libmdbx/packages/rpm/CMakeLists.txt new file mode 100644 index 00000000..5949e9f0 --- /dev/null +++ b/contrib/db/libmdbx/packages/rpm/CMakeLists.txt @@ -0,0 +1,184 @@ +cmake_minimum_required(VERSION 2.8.7) +set(TARGET mdbx) +project(${TARGET}) + +set(MDBX_VERSION_MAJOR 0) +set(MDBX_VERSION_MINOR 3) +set(MDBX_VERSION_RELEASE 1) +set(MDBX_VERSION_REVISION 0) + +set(MDBX_VERSION_STRING ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR}.${MDBX_VERSION_RELEASE}) + +enable_language(C) +enable_language(CXX) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED on) + +add_definitions(-DNDEBUG=1 -DMDBX_DEBUG=0 -DLIBMDBX_EXPORTS=1 -D_GNU_SOURCE=1) + +find_package(Threads REQUIRED) + +get_directory_property(hasParent PARENT_DIRECTORY) +if(hasParent) + set(STANDALONE_BUILD 0) +else() + set(STANDALONE_BUILD 1) + enable_testing() + + if (CMAKE_C_COMPILER_ID MATCHES GNU) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g3") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wextra") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ffunction-sections") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu11") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread") + endif() + + if (CMAKE_CXX_COMPILER_ID MATCHES GNU) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -W") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpointer-arith") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-sign-compare") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat-security") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wwrite-strings") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmax-errors=20") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wunused-function -Wunused-variable -Wunused-value -Wmissing-declarations") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-field-initializers") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wcast-qual") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -finline-functions-called-once") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-packed-bitfield-compat") + + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O3 -g3") + endif() + + if (COVERAGE) + if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + message(FATAL_ERROR "Coverage requires -DCMAKE_BUILD_TYPE=Debug Current value=${CMAKE_BUILD_TYPE}") + endif() + + message(STATUS "Setting coverage compiler flags") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -ggdb3 -O0 --coverage -fprofile-arcs -ftest-coverage") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -ggdb3 -O0 --coverage -fprofile-arcs -ftest-coverage") + add_definitions(-DCOVERAGE_TEST) + endif() + + if (NOT TRAVIS) + set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address -fsanitize=leak -fstack-protector-strong -static-libasan") + endif() +endif() + +set(${TARGET}_SRC + mdbx.h + src/bits.h + src/defs.h + src/lck-linux.c + src/mdbx.c + src/osal.c + src/osal.h + src/version.c + ) + +add_library(${TARGET}_STATIC STATIC + ${${TARGET}_SRC} + ) + +add_library(${TARGET} ALIAS ${TARGET}_STATIC) + +add_library(${TARGET}_SHARED SHARED + ${${TARGET}_SRC} + ) + +set_target_properties(${TARGET}_SHARED PROPERTIES + VERSION ${MDBX_VERSION_STRING} + SOVERSION ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR} + OUTPUT_NAME ${TARGET} + CLEAN_DIRECT_OUTPUT 1 + ) + +set_target_properties(${TARGET}_STATIC PROPERTIES + VERSION ${MDBX_VERSION_STRING} + SOVERSION ${MDBX_VERSION_MAJOR}.${MDBX_VERSION_MINOR} + OUTPUT_NAME ${TARGET} + CLEAN_DIRECT_OUTPUT 1 + ) + +target_include_directories(${TARGET}_STATIC PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(${TARGET}_SHARED PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(${TARGET}_STATIC ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(${TARGET}_SHARED ${CMAKE_THREAD_LIBS_INIT}) +if(UNIX AND NOT APPLE) + target_link_libraries(${TARGET}_STATIC rt) + target_link_libraries(${TARGET}_SHARED rt) +endif() + +install(TARGETS ${TARGET}_STATIC DESTINATION ${CMAKE_INSTALL_PREFIX}/lib64 COMPONENT mdbx) +install(TARGETS ${TARGET}_SHARED DESTINATION ${CMAKE_INSTALL_PREFIX}/lib64 COMPONENT mdbx) +install(FILES mdbx.h DESTINATION ${CMAKE_INSTALL_PREFIX}/include COMPONENT mdbx-devel) + +add_subdirectory(src/tools) +add_subdirectory(test) +add_subdirectory(test/pcrf) +add_subdirectory(tutorial) + +############################################################################## + +set(CPACK_GENERATOR "RPM") +set(CPACK_RPM_COMPONENT_INSTALL ON) + +# Version +if (NOT "$ENV{BUILD_NUMBER}" STREQUAL "") + set(CPACK_PACKAGE_RELEASE $ENV{BUILD_NUMBER}) +else() + if (NOT "$ENV{CI_PIPELINE_ID}" STREQUAL "") + set(CPACK_PACKAGE_RELEASE $ENV{CI_PIPELINE_ID}) + else() + set(CPACK_PACKAGE_RELEASE 1) + endif() +endif() +set(CPACK_RPM_PACKAGE_RELEASE ${CPACK_PACKAGE_RELEASE}) + +set(CPACK_PACKAGE_VERSION ${MDBX_VERSION_STRING}) +set(CPACK_PACKAGE_VERSION_FULL ${CPACK_PACKAGE_VERSION}-${CPACK_PACKAGE_RELEASE}) + +set(CPACK_RPM_mdbx-devel_PACKAGE_REQUIRES "mdbx = ${CPACK_PACKAGE_VERSION}") + +set(CPACK_RPM_SPEC_INSTALL_POST "/bin/true") +set(CPACK_RPM_mdbx_PACKAGE_NAME mdbx) +set(CPACK_RPM_mdbx-devel_PACKAGE_NAME mdbx-devel) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The revised and extended descendant of Symas LMDB") + +set(CPACK_PACKAGE_VENDOR "???") +set(CPACK_PACKAGE_CONTACT "Vladimir Romanov") +set(CPACK_PACKAGE_RELOCATABLE false) +set(CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64") +set(CPACK_RPM_PACKAGE_REQUIRES "") +set(CPACK_RPM_PACKAGE_GROUP "Applications/Database") + +set(CPACK_RPM_mdbx_FILE_NAME "${CPACK_RPM_mdbx_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_FULL}.${CPACK_RPM_PACKAGE_ARCHITECTURE}.rpm") +set(CPACK_RPM_mdbx-devel_FILE_NAME "${CPACK_RPM_mdbx-devel_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION_FULL}.${CPACK_RPM_PACKAGE_ARCHITECTURE}.rpm") + +set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION + /usr/local + /usr/local/bin + /usr/local/lib64 + /usr/local/include + /usr/local/man + /usr/local/man/man1 + ) + +include(CPack) diff --git a/contrib/db/libmdbx/packages/rpm/build.sh b/contrib/db/libmdbx/packages/rpm/build.sh new file mode 100644 index 00000000..51708822 --- /dev/null +++ b/contrib/db/libmdbx/packages/rpm/build.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -e +CONFIG=$1 + +if [[ -z "${CONFIG}" ]]; then + CONFIG=Debug +fi +if [[ -r /opt/rh/devtoolset-6/enable ]]; then + source /opt/rh/devtoolset-6/enable +fi +#rm -f -r build || true +mkdir -p cmake-build-${CONFIG} +pushd cmake-build-${CONFIG} &> /dev/null +if [[ ! -r Makefile ]]; then + cmake .. -DCMAKE_BUILD_TYPE=${CONFIG} +fi +make -j8 || exit 1 +popd &> /dev/null diff --git a/contrib/db/libmdbx/packages/rpm/package.sh b/contrib/db/libmdbx/packages/rpm/package.sh new file mode 100644 index 00000000..d7f9ab29 --- /dev/null +++ b/contrib/db/libmdbx/packages/rpm/package.sh @@ -0,0 +1,25 @@ +#!/bin/bash +set -e + +CONFIG=$1 + +if [[ -z "${CONFIG}" ]]; then + CONFIG=Debug +fi + +DIRNAME=`dirname ${BASH_SOURCE[0]}` +DIRNAME=`readlink --canonicalize ${DIRNAME}` + +if [[ -r /opt/rh/devtoolset-6/enable ]]; then + source /opt/rh/devtoolset-6/enable +fi + +mkdir -p cmake-build-${CONFIG} +pushd cmake-build-${CONFIG} &> /dev/null +if [[ ! -r Makefile ]]; then + cmake .. -DCMAKE_BUILD_TYPE=${CONFIG} +fi +rm -f *.rpm +make -j8 package || exit 1 +rm -f *-Unspecified.rpm +popd &> /dev/null diff --git a/contrib/db/libmdbx/src/CMakeLists.txt b/contrib/db/libmdbx/src/CMakeLists.txt new file mode 100644 index 00000000..04aead5f --- /dev/null +++ b/contrib/db/libmdbx/src/CMakeLists.txt @@ -0,0 +1,225 @@ +## +## Copyright 2019 Leonid Yuriev +## and other libmdbx authors: please see AUTHORS file. +## All rights reserved. +## +## Redistribution and use in source and binary forms, with or without +## modification, are permitted only as authorized by the OpenLDAP +## Public License. +## +## A copy of this license is available in the file LICENSE in the +## top-level directory of the distribution or, alternatively, at +## . +## + +# Get version +fetch_version(MDBX "${CMAKE_CURRENT_SOURCE_DIR}/../VERSION") +message(STATUS "libmdbx version is ${MDBX_VERSION}") + +if(MDBX_ALLOY_MODE) + set(LIBMDBX_SOURCES alloy.c) +else() + if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(LIBMDBX_OSAL windows) + else() + set(LIBMDBX_OSAL posix) + endif() + set(LIBMDBX_SOURCES + elements/defs.h elements/internals.h elements/osal.h + elements/core.c elements/osal.c elements/lck-${LIBMDBX_OSAL}.c) +endif() +list(APPEND LIBMDBX_SOURCES ../mdbx.h + "${CMAKE_CURRENT_SOURCE_DIR}/elements/version.c" + "${CMAKE_CURRENT_SOURCE_DIR}/elements/config.h") + +if(MDBX_BUILD_SHARED_LIBRARY) + add_library(mdbx SHARED ${LIBMDBX_SOURCES}) + target_compile_definitions(mdbx PRIVATE LIBMDBX_EXPORTS INTERFACE LIBMDBX_IMPORTS) + set(MDBX_LIBDEP_MODE PRIVATE) +else() + add_library(mdbx STATIC ${LIBMDBX_SOURCES}) + set(MDBX_LIBDEP_MODE PUBLIC) +endif() + +if(CC_HAS_VISIBILITY AND (LTO_ENABLED OR INTERPROCEDURAL_OPTIMIZATION)) + set_target_properties(mdbx PROPERTIES LINK_FLAGS "-fvisibility=hidden") +endif() + +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + if(MSVC) + if(NOT MSVC_LIB_EXE) + # Find lib.exe + get_filename_component(CL_NAME ${CMAKE_C_COMPILER} NAME) + string(REPLACE cl.exe lib.exe MSVC_LIB_EXE ${CL_NAME}) + find_program(MSVC_LIB_EXE ${MSVC_LIB_EXE}) + endif() + if(MSVC_LIB_EXE) + message(STATUS "Found MSVC's lib tool: ${MSVC_LIB_EXE}") + set(MDBX_NTDLL_EXTRA_IMPLIB ${CMAKE_CURRENT_BINARY_DIR}/mdbx_ntdll_extra.lib) + add_custom_command(OUTPUT ${MDBX_NTDLL_EXTRA_IMPLIB} + COMMENT "Create extra-import-library for ntdll.dll" + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/elements/ntdll.def" + COMMAND ${MSVC_LIB_EXE} /def:"${CMAKE_CURRENT_SOURCE_DIR}/elements/ntdll.def" /out:"${MDBX_NTDLL_EXTRA_IMPLIB}" ${INITIAL_CMAKE_STATIC_LINKER_FLAGS}) + else() + message(SEND_ERROR "MSVC's lib tool not found") + endif() + elseif(MINGW OR MINGW64) + if(NOT DLLTOOL) + # Find dlltool + get_filename_component(GCC_NAME ${CMAKE_C_COMPILER} NAME) + string(REPLACE gcc dlltool DLLTOOL_NAME ${GCC_NAME}) + find_program(DLLTOOL NAMES ${DLLTOOL_NAME}) + endif() + if(DLLTOOL) + message(STATUS "Found dlltool: ${DLLTOOL}") + set(MDBX_NTDLL_EXTRA_IMPLIB "${CMAKE_CURRENT_BINARY_DIR}/mdbx_ntdll_extra.a") + add_custom_command(OUTPUT ${MDBX_NTDLL_EXTRA_IMPLIB} + COMMENT "Create extra-import-library for ntdll.dll" + MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/elements/ntdll.def" + COMMAND ${DLLTOOL} -d "${CMAKE_CURRENT_SOURCE_DIR}/elements/ntdll.def" -l "${MDBX_NTDLL_EXTRA_IMPLIB}") + else() + message(SEND_ERROR "dlltool not found") + endif() + endif() +endif() + +target_link_libraries(mdbx ${MDBX_LIBDEP_MODE} ${CMAKE_THREAD_LIBS_INIT}) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + target_link_libraries(mdbx ${MDBX_LIBDEP_MODE} ntdll.lib) + if(MDBX_NTDLL_EXTRA_IMPLIB) + # LY: Sometimes Cmake requires a nightmarish magic for simple things. + # 1) create a target out of the library compilation result + add_custom_target(ntdll_extra_target DEPENDS ${MDBX_NTDLL_EXTRA_IMPLIB}) + # 2) create an library target out of the library compilation result + add_library(ntdll_extra STATIC IMPORTED GLOBAL) + add_dependencies(ntdll_extra ntdll_extra_target) + # 3) specify where the library is (and where to find the headers) + set_target_properties(ntdll_extra + PROPERTIES + IMPORTED_LOCATION ${MDBX_NTDLL_EXTRA_IMPLIB}) + target_link_libraries(mdbx ${MDBX_LIBDEP_MODE} ntdll_extra) + endif() +endif() + +set_target_properties(mdbx PROPERTIES + INTERPROCEDURAL_OPTIMIZATION $ + C_STANDARD ${MDBX_C_STANDARD} C_STANDARD_REQUIRED ON + PUBLIC_HEADER "../mdbx.h") + +if(CC_HAS_FASTMATH) + target_compile_options(mdbx PRIVATE "-ffast-math") +endif() +if(BUILD_FOR_NATIVE_CPU AND CC_HAS_ARCH_NATIVE) + target_compile_options(mdbx PUBLIC "-march=native") +endif() +if(CC_HAS_VISIBILITY) + target_compile_options(mdbx PRIVATE "-fvisibility=hidden") +endif() + +install(TARGETS mdbx + LIBRARY DESTINATION lib COMPONENT runtime + RUNTIME DESTINATION bin COMPONENT runtime + ARCHIVE DESTINATION lib/static COMPONENT devel + PUBLIC_HEADER DESTINATION include + INCLUDES DESTINATION include COMPONENT devel) + +################################################################################ +# +# library build info (used in library version output) +# +set(MDBX_BUILD_FLAGS "") + +# append cmake's build-type flags and defines +if(NOT CMAKE_CONFIGURATION_TYPES) + list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_FLAGS_${CMAKE_BUILD_TYPE_UPPERCASE}}) + list(APPEND MDBX_BUILD_FLAGS ${CMAKE_C_DEFINES_${CMAKE_BUILD_TYPE_UPPERCASE}}) +endif() + +# append linker dll's options +if(LIBMDBX_TYPE STREQUAL "SHARED") + list(APPEND MDBX_BUILD_FLAGS ${CMAKE_SHARED_LINKER_FLAGS}) +endif() + +# get definitions +get_target_property(defs_list mdbx COMPILE_DEFINITIONS) +if(defs_list) + list(APPEND MDBX_BUILD_FLAGS ${defs_list}) +endif() + +# get target compile options +get_target_property(options_list mdbx COMPILE_OPTIONS) +if(options_list) + list(APPEND MDBX_BUILD_FLAGS ${options_list}) +endif() + +list(REMOVE_DUPLICATES MDBX_BUILD_FLAGS) +string(REPLACE ";" " " MDBX_BUILD_FLAGS "${MDBX_BUILD_FLAGS}") +if(CMAKE_CONFIGURATION_TYPES) + # add dynamic part via per-configuration define + message(STATUS "MDBX Compile Flags: ${MDBX_BUILD_FLAGS} ") + add_definitions(-DMDBX_BUILD_FLAGS_CONFIG="$<$:${CMAKE_C_FLAGS_DEBUG} ${CMAKE_C_DEFINES_DEBUG}>$<$:${CMAKE_C_FLAGS_RELEASE} ${CMAKE_C_DEFINES_RELEASE}>$<$:${CMAKE_C_FLAGS_RELWITHDEBINFO} ${CMAKE_C_DEFINES_RELWITHDEBINFO}>$<$:${CMAKE_C_FLAGS_MINSIZEREL} ${CMAKE_C_DEFINES_MINSIZEREL}>") +else() + message(STATUS "MDBX Compile Flags: ${MDBX_BUILD_FLAGS}") +endif() + +# get compiler info +execute_process(COMMAND sh -c "${CMAKE_C_COMPILER} --version | head -1" + OUTPUT_VARIABLE MDBX_BUILD_COMPILER + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE rc) +if(rc OR NOT MDBX_BUILD_COMPILER) + string(STRIP "${CMAKE_C_COMPILER_ID}-${CMAKE_C_COMPILER_VERSION}" MDBX_BUILD_COMPILER) +endif() + +# make a build-target triplet +if(CMAKE_C_COMPILER_TARGET) + set(MDBX_BUILD_TARGET "${CMAKE_C_COMPILER_TARGET}") +elseif(CMAKE_C_PLATFORM_ID AND NOT CMAKE_C_PLATFORM_ID STREQUAL CMAKE_SYSTEM_NAME) + string(STRIP "${CMAKE_C_PLATFORM_ID}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +elseif(CMAKE_LIBRARY_ARCHITECTURE) + string(STRIP "${CMAKE_LIBRARY_ARCHITECTURE}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +elseif(CMAKE_GENERATOR_PLATFORM AND NOT CMAKE_C_PLATFORM_ID STREQUAL CMAKE_SYSTEM_NAME) + string(STRIP "${CMAKE_GENERATOR_PLATFORM}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +elseif(CMAKE_SYSTEM_ARCH) + string(STRIP "${CMAKE_SYSTEM_ARCH}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +else() + string(STRIP "${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}" MDBX_BUILD_TARGET) +endif() +if(CMAKE_CONFIGURATION_TYPES) + add_definitions(-DMDBX_BUILD_CONFIG="$") +else() + set(MDBX_BUILD_CONFIG ${CMAKE_BUILD_TYPE}) +endif() + +# options +string(TIMESTAMP MDBX_BUILD_TIMESTAMP UTC) +set(options VERSION C_COMPILER CXX_COMPILER) +foreach(item IN LISTS options) + if(DEFINED ${item}) + set(value "${${item}}") + elseif(DEFINED MDBX_${item}) + set(item MDBX_${item}) + set(value "${${item}}") + elseif(DEFINED CMAKE_${item}) + set(item CMAKE_${item}) + set(value "${${item}}") + else() + set(value "undefined") + endif() + message(STATUS "${item}: ${value}") +endforeach(item) + +# generate version and config files +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/elements/version.c.in" + "${CMAKE_CURRENT_SOURCE_DIR}/elements/version.c" ESCAPE_QUOTES) + +file(SHA256 "${CMAKE_CURRENT_SOURCE_DIR}/elements/version.c" MDBX_SOURCERY_DIGEST) +string(MAKE_C_IDENTIFIER "${MDBX_GIT_DESCRIBE}" MDBX_SOURCERY_SUFFIX) +set(MDBX_BUILD_SOURCERY "${MDBX_SOURCERY_DIGEST}_${MDBX_SOURCERY_SUFFIX}") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/elements/config.h.in" + "${CMAKE_CURRENT_SOURCE_DIR}/elements/config.h" ESCAPE_QUOTES) +add_definitions(-DMDBX_CONFIG_H="config.h") + +add_subdirectory(tools) diff --git a/contrib/db/libmdbx/src/alloy.c b/contrib/db/libmdbx/src/alloy.c new file mode 100644 index 00000000..98f3aac3 --- /dev/null +++ b/contrib/db/libmdbx/src/alloy.c @@ -0,0 +1,26 @@ +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +/* Amalgamated build */ +#define MDBX_ALLOY 1 +#include "elements/internals.h" /* must be included fisrt */ + +#include "elements/core.c" +#include "elements/osal.c" +#include "elements/version.c" + +#if defined(_WIN32) || defined(_WIN64) +#include "elements/lck-windows.c" +#else +#include "elements/lck-posix.c" +#endif diff --git a/contrib/db/libmdbx/src/elements/config.h.in b/contrib/db/libmdbx/src/elements/config.h.in new file mode 100644 index 00000000..44ae1507 --- /dev/null +++ b/contrib/db/libmdbx/src/elements/config.h.in @@ -0,0 +1,56 @@ +/* This is CMake-template for libmdbx's config.h + ******************************************************************************/ + +/* *INDENT-OFF* */ +/* clang-format off */ + +#cmakedefine HAVE_VALGRIND_MEMCHECK_H +#cmakedefine HAS_RELAXED_CONSTEXPR + +#cmakedefine LTO_ENABLED +#cmakedefine MDBX_USE_VALGRIND +#cmakedefine ENABLE_GPROF +#cmakedefine ENABLE_GCOV +#cmakedefine ENABLE_ASAN + +/* Common */ +#cmakedefine01 MDBX_TXN_CHECKPID +#cmakedefine01 MDBX_TXN_CHECKOWNER +#cmakedefine01 MDBX_BUILD_SHARED_LIBRARY + +/* Windows */ +#cmakedefine01 MDBX_CONFIG_MANUAL_TLS_CALLBACK +#cmakedefine01 MDBX_AVOID_CRT + +/* MacOS */ +#cmakedefine01 MDBX_OSX_SPEED_INSTEADOF_DURABILITY + +/* POSIX */ +#cmakedefine01 MDBX_USE_ROBUST +#cmakedefine01 MDBX_USE_OFDLOCKS +#cmakedefine01 MDBX_DISABLE_GNU_SOURCE + +/* Simulate "AUTO" values of tristate options */ +#cmakedefine MDBX_TXN_CHECKPID_AUTO +#ifdef MDBX_TXN_CHECKPID_AUTO +#undef MDBX_TXN_CHECKPID +#endif +#cmakedefine MDBX_USE_ROBUST_AUTO +#ifdef MDBX_USE_ROBUST_AUTO +#undef MDBX_USE_ROBUST +#endif +#cmakedefine MDBX_USE_OFDLOCKS_AUTO +#ifdef MDBX_USE_OFDLOCKS_AUTO +#undef MDBX_USE_OFDLOCKS +#endif + +/* Build Info */ +#cmakedefine MDBX_BUILD_TIMESTAMP "@MDBX_BUILD_TIMESTAMP@" +#cmakedefine MDBX_BUILD_TARGET "@MDBX_BUILD_TARGET@" +#cmakedefine MDBX_BUILD_CONFIG "@MDBX_BUILD_CONFIG@" +#cmakedefine MDBX_BUILD_COMPILER "@MDBX_BUILD_COMPILER@" +#cmakedefine MDBX_BUILD_FLAGS "@MDBX_BUILD_FLAGS@" +#cmakedefine MDBX_BUILD_SOURCERY @MDBX_BUILD_SOURCERY@ + +/* *INDENT-ON* */ +/* clang-format on */ diff --git a/contrib/db/libmdbx/src/elements/core.c b/contrib/db/libmdbx/src/elements/core.c new file mode 100644 index 00000000..c64259c8 --- /dev/null +++ b/contrib/db/libmdbx/src/elements/core.c @@ -0,0 +1,16709 @@ +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * This code is derived from "LMDB engine" written by + * Howard Chu (Symas Corporation), which itself derived from btree.c + * written by Martin Hedenfalk. + * + * --- + * + * Portions Copyright 2011-2015 Howard Chu, Symas Corp. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + * + * --- + * + * Portions Copyright (c) 2009, 2010 Martin Hedenfalk + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include "internals.h" + +/*------------------------------------------------------------------------------ + * Internal inlines */ + +static __pure_function __inline bool is_powerof2(size_t x) { + return (x & (x - 1)) == 0; +} + +static __pure_function __inline size_t roundup_powerof2(size_t value, + size_t granularity) { + assert(is_powerof2(granularity)); + return (value + granularity - 1) & ~(granularity - 1); +} + +static __pure_function unsigned log2n(size_t value) { + assert(value > 0 && value < INT32_MAX && is_powerof2(value)); + assert((value & -(int32_t)value) == value); +#if __GNUC_PREREQ(4, 1) || __has_builtin(__builtin_ctzl) + return __builtin_ctzl(value); +#elif defined(_MSC_VER) + unsigned long index; + _BitScanForward(&index, (unsigned long)value); + return index; +#else + static const uint8_t debruijn_ctz32[32] = { + 0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, + 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + return debruijn_ctz32[(uint32_t)(value * 0x077CB531u) >> 27]; +#endif +} + +/*------------------------------------------------------------------------------ + * Unaligned access */ + +static __pure_function __maybe_unused __inline unsigned +field_alignment(unsigned alignment_baseline, size_t field_offset) { + unsigned merge = alignment_baseline | (unsigned)field_offset; + return merge & -(int)merge; +} + +/* read-thunk for UB-sanitizer */ +static __pure_function __inline uint8_t peek_u8(const uint8_t *ptr) { + return *ptr; +} + +/* write-thunk for UB-sanitizer */ +static __inline void poke_u8(uint8_t *ptr, const uint8_t v) { *ptr = v; } + +static __pure_function __inline uint16_t +unaligned_peek_u16(const unsigned expected_alignment, const void *ptr) { + assert((uintptr_t)ptr % expected_alignment == 0); + if (MDBX_UNALIGNED_OK || (expected_alignment % sizeof(uint16_t)) == 0) + return *(const uint16_t *)ptr; + else { + uint16_t v; + memcpy(&v, ptr, sizeof(v)); + return v; + } +} + +static __inline void unaligned_poke_u16(const unsigned expected_alignment, + void *ptr, const uint16_t v) { + assert((uintptr_t)ptr % expected_alignment == 0); + if (MDBX_UNALIGNED_OK || (expected_alignment % sizeof(v)) == 0) + *(uint16_t *)ptr = v; + else + memcpy(ptr, &v, sizeof(v)); +} + +static __pure_function __inline uint32_t +unaligned_peek_u32(const unsigned expected_alignment, const void *ptr) { + assert((uintptr_t)ptr % expected_alignment == 0); + if (MDBX_UNALIGNED_OK || (expected_alignment % sizeof(uint32_t)) == 0) + return *(const uint32_t *)ptr; + else if ((expected_alignment % sizeof(uint16_t)) == 0) { + const uint16_t lo = + ((const uint16_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__]; + const uint16_t hi = + ((const uint16_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__]; + return lo | (uint32_t)hi << 16; + } else { + uint32_t v; + memcpy(&v, ptr, sizeof(v)); + return v; + } +} + +static __inline void unaligned_poke_u32(const unsigned expected_alignment, + void *ptr, const uint32_t v) { + assert((uintptr_t)ptr % expected_alignment == 0); + if (MDBX_UNALIGNED_OK || (expected_alignment % sizeof(v)) == 0) + *(uint32_t *)ptr = v; + else if ((expected_alignment % sizeof(uint16_t)) == 0) { + ((uint16_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__] = (uint16_t)v; + ((uint16_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__] = + (uint16_t)(v >> 16); + } else + memcpy(ptr, &v, sizeof(v)); +} + +static __pure_function __inline uint64_t +unaligned_peek_u64(const unsigned expected_alignment, const void *ptr) { + assert((uintptr_t)ptr % expected_alignment == 0); + if (MDBX_UNALIGNED_OK || (expected_alignment % sizeof(uint64_t)) == 0) + return *(const uint64_t *)ptr; + else if ((expected_alignment % sizeof(uint32_t)) == 0) { + const uint32_t lo = + ((const uint32_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__]; + const uint32_t hi = + ((const uint32_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__]; + return lo | (uint64_t)hi << 32; + } else { + uint64_t v; + memcpy(&v, ptr, sizeof(v)); + return v; + } +} + +static __inline void unaligned_poke_u64(const unsigned expected_alignment, + void *ptr, const uint64_t v) { + assert((uintptr_t)ptr % expected_alignment == 0); + if (MDBX_UNALIGNED_OK || (expected_alignment % sizeof(v)) == 0) + *(uint64_t *)ptr = v; + else if ((expected_alignment % sizeof(uint32_t)) == 0) { + ((uint32_t *)ptr)[__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__] = (uint32_t)v; + ((uint32_t *)ptr)[__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__] = + (uint32_t)(v >> 32); + } else + memcpy(ptr, &v, sizeof(v)); +} + +#define UNALIGNED_PEEK_8(ptr, struct, field) \ + peek_u8((const uint8_t *)(ptr) + offsetof(struct, field)) +#define UNALIGNED_POKE_8(ptr, struct, field, value) \ + poke_u8((uint8_t *)(ptr) + offsetof(struct, field), value) + +#define UNALIGNED_PEEK_16(ptr, struct, field) \ + unaligned_peek_u16(1, (const char *)(ptr) + offsetof(struct, field)) +#define UNALIGNED_POKE_16(ptr, struct, field, value) \ + unaligned_poke_u16(1, (char *)(ptr) + offsetof(struct, field), value) + +#define UNALIGNED_PEEK_32(ptr, struct, field) \ + unaligned_peek_u32(1, (const char *)(ptr) + offsetof(struct, field)) +#define UNALIGNED_POKE_32(ptr, struct, field, value) \ + unaligned_poke_u32(1, (char *)(ptr) + offsetof(struct, field), value) + +#define UNALIGNED_PEEK_64(ptr, struct, field) \ + unaligned_peek_u64(1, (const char *)(ptr) + offsetof(struct, field)) +#define UNALIGNED_POKE_64(ptr, struct, field, value) \ + unaligned_poke_u64(1, (char *)(ptr) + offsetof(struct, field), value) + +/* Get the page number pointed to by a branch node */ +static __pure_function __inline pgno_t node_pgno(const MDBX_node *node) { + pgno_t pgno = UNALIGNED_PEEK_32(node, MDBX_node, mn_pgno32); + if (sizeof(pgno) > 4) + pgno |= ((uint64_t)UNALIGNED_PEEK_8(node, MDBX_node, mn_extra)) << 32; + return pgno; +} + +/* Set the page number in a branch node */ +static __inline void node_set_pgno(MDBX_node *node, pgno_t pgno) { + assert(pgno >= MIN_PAGENO && pgno <= MAX_PAGENO); + + UNALIGNED_POKE_32(node, MDBX_node, mn_pgno32, (uint32_t)pgno); + if (sizeof(pgno) > 4) + UNALIGNED_POKE_8(node, MDBX_node, mn_extra, + (uint8_t)((uint64_t)pgno >> 32)); +} + +/* Get the size of the data in a leaf node */ +static __pure_function __inline size_t node_ds(const MDBX_node *node) { + return UNALIGNED_PEEK_32(node, MDBX_node, mn_dsize); +} + +/* Set the size of the data for a leaf node */ +static __inline void node_set_ds(MDBX_node *node, size_t size) { + assert(size < INT_MAX); + UNALIGNED_POKE_32(node, MDBX_node, mn_dsize, (uint32_t)size); +} + +/* The size of a key in a node */ +static __pure_function __inline size_t node_ks(const MDBX_node *node) { + return UNALIGNED_PEEK_16(node, MDBX_node, mn_ksize); +} + +/* Set the size of the key for a leaf node */ +static __inline void node_set_ks(MDBX_node *node, size_t size) { + assert(size < INT16_MAX); + UNALIGNED_POKE_16(node, MDBX_node, mn_ksize, (uint16_t)size); +} + +static __pure_function __inline uint8_t node_flags(const MDBX_node *node) { + return UNALIGNED_PEEK_8(node, MDBX_node, mn_flags); +} + +static __inline void node_set_flags(MDBX_node *node, uint8_t flags) { + UNALIGNED_POKE_8(node, MDBX_node, mn_flags, flags); +} + +/* Size of the node header, excluding dynamic data at the end */ +#define NODESIZE offsetof(MDBX_node, mn_data) + +/* Address of the key for the node */ +static __pure_function __inline void *node_key(const MDBX_node *node) { + return (char *)node + NODESIZE; +} + +/* Address of the data for a node */ +static __pure_function __inline void *node_data(const MDBX_node *node) { + return (char *)node_key(node) + node_ks(node); +} + +/* Size of a node in a leaf page with a given key and data. + * This is node header plus key plus data size. */ +static __pure_function __inline size_t node_size(const MDBX_val *key, + const MDBX_val *value) { + return NODESIZE + + EVEN((key ? key->iov_len : 0) + (value ? value->iov_len : 0)); +} + +static __pure_function __inline pgno_t peek_pgno(const void *ptr) { + if (sizeof(pgno_t) == sizeof(uint32_t)) + return (pgno_t)unaligned_peek_u32(1, ptr); + else if (sizeof(pgno_t) == sizeof(uint64_t)) + return (pgno_t)unaligned_peek_u64(1, ptr); + else { + pgno_t pgno; + memcpy(&pgno, ptr, sizeof(pgno)); + return pgno; + } +} + +static __inline void poke_pgno(void *ptr, const pgno_t pgno) { + if (sizeof(pgno) == sizeof(uint32_t)) + unaligned_poke_u32(1, ptr, pgno); + else if (sizeof(pgno) == sizeof(uint64_t)) + unaligned_poke_u64(1, ptr, pgno); + else + memcpy(ptr, &pgno, sizeof(pgno)); +} + +static __pure_function __inline pgno_t +node_largedata_pgno(const MDBX_node *node) { + assert(node_flags(node) & F_BIGDATA); + return peek_pgno(node_data(node)); +} + +/* Calculate the size of a leaf node. + * + * The size depends on the environment's page size; if a data item + * is too large it will be put onto an overflow page and the node + * size will only include the key and not the data. Sizes are always + * rounded up to an even number of bytes, to guarantee 2-byte alignment + * of the MDBX_node headers. + * + * [in] env The environment handle. + * [in] key The key for the node. + * [in] data The data for the node. + * + * Returns The number of bytes needed to store the node. */ +static __pure_function __inline size_t +leaf_size(const MDBX_env *env, const MDBX_val *key, const MDBX_val *data) { + size_t node_bytes = node_size(key, data); + if (node_bytes > env->me_nodemax) { + /* put on overflow page */ + node_bytes = node_size(key, nullptr) + sizeof(pgno_t); + } + + return node_bytes + sizeof(indx_t); +} + +/* Calculate the size of a branch node. + * + * The size should depend on the environment's page size but since + * we currently don't support spilling large keys onto overflow + * pages, it's simply the size of the MDBX_node header plus the + * size of the key. Sizes are always rounded up to an even number + * of bytes, to guarantee 2-byte alignment of the MDBX_node headers. + * + * [in] env The environment handle. + * [in] key The key for the node. + * + * Returns The number of bytes needed to store the node. */ +static __pure_function __inline size_t branch_size(const MDBX_env *env, + const MDBX_val *key) { + /* Size of a node in a branch page with a given key. + * This is just the node header plus the key, there is no data. */ + size_t node_bytes = node_size(key, nullptr); + if (unlikely(node_bytes > env->me_nodemax)) { + /* put on overflow page */ + /* not implemented */ + mdbx_assert_fail(env, "INDXSIZE(key) <= env->me_nodemax", __func__, + __LINE__); + node_bytes = node_size(key, nullptr) + sizeof(pgno_t); + } + + return node_bytes + sizeof(indx_t); +} + +/*----------------------------------------------------------------------------*/ + +static __pure_function __inline size_t pgno2bytes(const MDBX_env *env, + pgno_t pgno) { + mdbx_assert(env, (1u << env->me_psize2log) == env->me_psize); + return ((size_t)pgno) << env->me_psize2log; +} + +static __pure_function __inline MDBX_page *pgno2page(const MDBX_env *env, + pgno_t pgno) { + return (MDBX_page *)(env->me_map + pgno2bytes(env, pgno)); +} + +static __pure_function __inline pgno_t bytes2pgno(const MDBX_env *env, + size_t bytes) { + mdbx_assert(env, (env->me_psize >> env->me_psize2log) == 1); + return (pgno_t)(bytes >> env->me_psize2log); +} + +static __pure_function __inline size_t pgno_align2os_bytes(const MDBX_env *env, + pgno_t pgno) { + return roundup_powerof2(pgno2bytes(env, pgno), env->me_os_psize); +} + +static __pure_function __inline pgno_t pgno_align2os_pgno(const MDBX_env *env, + pgno_t pgno) { + return bytes2pgno(env, pgno_align2os_bytes(env, pgno)); +} + +static __pure_function __inline size_t bytes_align2os_bytes(const MDBX_env *env, + size_t bytes) { + return roundup_powerof2(roundup_powerof2(bytes, env->me_psize), + env->me_os_psize); +} + +/* Address of first usable data byte in a page, after the header */ +static __pure_function __inline void *page_data(const MDBX_page *mp) { + return (char *)mp + PAGEHDRSZ; +} + +static __pure_function __inline const MDBX_page *data_page(const void *data) { + return container_of(data, MDBX_page, mp_ptrs); +} + +static __pure_function __inline MDBX_meta *page_meta(MDBX_page *mp) { + return (MDBX_meta *)page_data(mp); +} + +/* Number of nodes on a page */ +static __pure_function __inline unsigned page_numkeys(const MDBX_page *mp) { + return mp->mp_lower >> 1; +} + +/* The amount of space remaining in the page */ +static __pure_function __inline unsigned page_room(const MDBX_page *mp) { + return mp->mp_upper - mp->mp_lower; +} + +static __pure_function __inline unsigned page_space(const MDBX_env *env) { + STATIC_ASSERT(PAGEHDRSZ % 2 == 0); + return env->me_psize - PAGEHDRSZ; +} + +static __pure_function __inline unsigned page_used(const MDBX_env *env, + const MDBX_page *mp) { + return page_space(env) - page_room(mp); +} + +/* The percentage of space used in the page, in a percents. */ +static __pure_function __maybe_unused __inline double +page_fill(const MDBX_env *env, const MDBX_page *mp) { + return page_used(env, mp) * 100.0 / page_space(env); +} + +static __pure_function __inline bool +page_fill_enough(const MDBX_page *mp, unsigned spaceleft_threshold, + unsigned minkeys_threshold) { + return page_room(mp) < spaceleft_threshold && + page_numkeys(mp) >= minkeys_threshold; +} + +/* The number of overflow pages needed to store the given size. */ +static __pure_function __inline pgno_t number_of_ovpages(const MDBX_env *env, + size_t bytes) { + return bytes2pgno(env, PAGEHDRSZ - 1 + bytes) + 1; +} + +/* Address of node i in page p */ +static __pure_function __inline MDBX_node *page_node(const MDBX_page *mp, + unsigned i) { + assert((mp->mp_flags & (P_LEAF2 | P_OVERFLOW | P_META)) == 0); + assert(page_numkeys(mp) > (unsigned)(i)); + assert(mp->mp_ptrs[i] % 2 == 0); + return (MDBX_node *)((char *)mp + mp->mp_ptrs[i] + PAGEHDRSZ); +} + +/* The address of a key in a LEAF2 page. + * LEAF2 pages are used for MDBX_DUPFIXED sorted-duplicate sub-DBs. + * There are no node headers, keys are stored contiguously. */ +static __pure_function __inline void * +page_leaf2key(const MDBX_page *mp, unsigned i, size_t keysize) { + assert(mp->mp_leaf2_ksize == keysize); + (void)keysize; + return (char *)mp + PAGEHDRSZ + (i * mp->mp_leaf2_ksize); +} + +/* Set the node's key into keyptr. */ +static __inline void get_key(const MDBX_node *node, MDBX_val *keyptr) { + keyptr->iov_len = node_ks(node); + keyptr->iov_base = node_key(node); +} + +/* Set the node's key into keyptr, if requested. */ +static __inline void get_key_optional(const MDBX_node *node, + MDBX_val *keyptr /* __may_null */) { + if (keyptr) + get_key(node, keyptr); +} + +/*------------------------------------------------------------------------------ + * LY: temporary workaround for Elbrus's memcmp() bug. */ + +#if defined(__e2k__) && !__GLIBC_PREREQ(2, 24) +int __hot mdbx_e2k_memcmp_bug_workaround(const void *s1, const void *s2, + size_t n) { + if (unlikely(n > 42 + /* LY: align followed access if reasonable possible */ + && (((uintptr_t)s1) & 7) != 0 && + (((uintptr_t)s1) & 7) == (((uintptr_t)s2) & 7))) { + if (((uintptr_t)s1) & 1) { + const int diff = *(uint8_t *)s1 - *(uint8_t *)s2; + if (diff) + return diff; + s1 = (char *)s1 + 1; + s2 = (char *)s2 + 1; + n -= 1; + } + + if (((uintptr_t)s1) & 2) { + const uint16_t a = *(uint16_t *)s1; + const uint16_t b = *(uint16_t *)s2; + if (likely(a != b)) + return (__builtin_bswap16(a) > __builtin_bswap16(b)) ? 1 : -1; + s1 = (char *)s1 + 2; + s2 = (char *)s2 + 2; + n -= 2; + } + + if (((uintptr_t)s1) & 4) { + const uint32_t a = *(uint32_t *)s1; + const uint32_t b = *(uint32_t *)s2; + if (likely(a != b)) + return (__builtin_bswap32(a) > __builtin_bswap32(b)) ? 1 : -1; + s1 = (char *)s1 + 4; + s2 = (char *)s2 + 4; + n -= 4; + } + } + + while (n >= 8) { + const uint64_t a = *(uint64_t *)s1; + const uint64_t b = *(uint64_t *)s2; + if (likely(a != b)) + return (__builtin_bswap64(a) > __builtin_bswap64(b)) ? 1 : -1; + s1 = (char *)s1 + 8; + s2 = (char *)s2 + 8; + n -= 8; + } + + if (n & 4) { + const uint32_t a = *(uint32_t *)s1; + const uint32_t b = *(uint32_t *)s2; + if (likely(a != b)) + return (__builtin_bswap32(a) > __builtin_bswap32(b)) ? 1 : -1; + s1 = (char *)s1 + 4; + s2 = (char *)s2 + 4; + } + + if (n & 2) { + const uint16_t a = *(uint16_t *)s1; + const uint16_t b = *(uint16_t *)s2; + if (likely(a != b)) + return (__builtin_bswap16(a) > __builtin_bswap16(b)) ? 1 : -1; + s1 = (char *)s1 + 2; + s2 = (char *)s2 + 2; + } + + return (n & 1) ? *(uint8_t *)s1 - *(uint8_t *)s2 : 0; +} + +int __hot mdbx_e2k_strcmp_bug_workaround(const char *s1, const char *s2) { + while (true) { + int diff = *(uint8_t *)s1 - *(uint8_t *)s2; + if (likely(diff != 0) || *s1 == '\0') + return diff; + s1 += 1; + s2 += 1; + } +} + +int __hot mdbx_e2k_strncmp_bug_workaround(const char *s1, const char *s2, + size_t n) { + while (n > 0) { + int diff = *(uint8_t *)s1 - *(uint8_t *)s2; + if (likely(diff != 0) || *s1 == '\0') + return diff; + s1 += 1; + s2 += 1; + n -= 1; + } + return 0; +} + +size_t __hot mdbx_e2k_strlen_bug_workaround(const char *s) { + size_t n = 0; + while (*s) { + s += 1; + n += 1; + } + return n; +} + +size_t __hot mdbx_e2k_strnlen_bug_workaround(const char *s, size_t maxlen) { + size_t n = 0; + while (maxlen > n && *s) { + s += 1; + n += 1; + } + return n; +} +#endif /* Elbrus's memcmp() bug. */ + +/*------------------------------------------------------------------------------ + * safe read/write volatile 64-bit fields on 32-bit architectures. */ + +static __inline void atomic_yield(void) { +#if defined(_WIN32) || defined(_WIN64) + YieldProcessor(); +#elif defined(__x86_64__) || defined(__i386__) || defined(__e2k__) + __builtin_ia32_pause(); +#elif defined(__ia64__) +#if defined(__HP_cc__) || defined(__HP_aCC__) + _Asm_hint(_HINT_PAUSE); +#else + __asm__ __volatile__("hint @pause"); +#endif +#elif defined(__arm__) || defined(__aarch64__) +#ifdef __CC_ARM + __yield(); +#else + __asm__ __volatile__("yield"); +#endif +#elif (defined(__mips64) || defined(__mips64__)) && defined(__mips_isa_rev) && \ + __mips_isa_rev >= 2 + __asm__ __volatile__("pause"); +#elif defined(__mips) || defined(__mips__) || defined(__mips64) || \ + defined(__mips64__) || defined(_M_MRX000) || defined(_MIPS_) || \ + defined(__MWERKS__) || defined(__sgi) + __asm__ __volatile__(".word 0x00000140"); +#else + pthread_yield(); +#endif +} + +#if MDBX_64BIT_CAS +static __inline bool atomic_cas64(volatile uint64_t *p, uint64_t c, + uint64_t v) { +#if defined(ATOMIC_VAR_INIT) || defined(ATOMIC_LLONG_LOCK_FREE) + STATIC_ASSERT(sizeof(long long int) == 8); + STATIC_ASSERT(atomic_is_lock_free(p)); + return atomic_compare_exchange_strong((_Atomic uint64_t *)p, &c, v); +#elif defined(__GNUC__) || defined(__clang__) + return __sync_bool_compare_and_swap(p, c, v); +#elif defined(_MSC_VER) + return c == + (uint64_t)_InterlockedCompareExchange64((volatile int64_t *)p, v, c); +#elif defined(__APPLE__) + return OSAtomicCompareAndSwap64Barrier(c, v, (volatile uint64_t *)p); +#else +#error FIXME: Unsupported compiler +#endif +} +#endif /* MDBX_64BIT_CAS */ + +static __inline bool atomic_cas32(volatile uint32_t *p, uint32_t c, + uint32_t v) { +#if defined(ATOMIC_VAR_INIT) || defined(ATOMIC_LONG_LOCK_FREE) + STATIC_ASSERT(sizeof(long int) >= 4); + STATIC_ASSERT(atomic_is_lock_free(p)); + return atomic_compare_exchange_strong((_Atomic uint32_t *)p, &c, v); +#elif defined(__GNUC__) || defined(__clang__) + return __sync_bool_compare_and_swap(p, c, v); +#elif defined(_MSC_VER) + STATIC_ASSERT(sizeof(volatile long) == sizeof(volatile uint32_t)); + return c == (uint32_t)_InterlockedCompareExchange((volatile long *)p, v, c); +#elif defined(__APPLE__) + return OSAtomicCompareAndSwap32Barrier(c, v, (volatile int32_t *)p); +#else +#error FIXME: Unsupported compiler +#endif +} + +static __inline uint32_t atomic_add32(volatile uint32_t *p, uint32_t v) { +#if defined(ATOMIC_VAR_INIT) || defined(ATOMIC_LONG_LOCK_FREE) + STATIC_ASSERT(sizeof(long int) >= 4); + STATIC_ASSERT(atomic_is_lock_free(p)); + return atomic_fetch_add((_Atomic uint32_t *)p, v); +#elif defined(__GNUC__) || defined(__clang__) + return __sync_fetch_and_add(p, v); +#elif defined(_MSC_VER) + STATIC_ASSERT(sizeof(volatile long) == sizeof(volatile uint32_t)); + return _InterlockedExchangeAdd((volatile long *)p, v); +#elif defined(__APPLE__) + return OSAtomicAdd32Barrier(v, (volatile int32_t *)p); +#else +#error FIXME: Unsupported compiler +#endif +} + +#define atomic_sub32(p, v) atomic_add32(p, 0 - (v)) + +static __maybe_unused __inline bool safe64_is_valid(uint64_t v) { +#if MDBX_WORDBITS >= 64 + return v < SAFE64_INVALID_THRESHOLD; +#else + return (v >> 32) != UINT32_MAX; +#endif /* MDBX_WORDBITS */ +} + +static __maybe_unused __inline bool +safe64_is_valid_ptr(const mdbx_safe64_t *ptr) { + mdbx_compiler_barrier(); +#if MDBX_64BIT_ATOMIC + return ptr->atomic < SAFE64_INVALID_THRESHOLD; +#else + return ptr->high != UINT32_MAX; +#endif /* MDBX_64BIT_ATOMIC */ +} + +static __inline uint64_t safe64_txnid_next(uint64_t txnid) { + txnid += MDBX_TXNID_STEP; +#if !MDBX_64BIT_CAS + /* avoid overflow of low-part in safe64_reset() */ + txnid += (UINT32_MAX == (uint32_t)txnid); +#endif + return txnid; +} + +static __inline void safe64_reset(mdbx_safe64_t *ptr, bool single_writer) { + mdbx_compiler_barrier(); +#if !MDBX_64BIT_CAS + if (!single_writer) { + STATIC_ASSERT(MDBX_TXNID_STEP > 1); + /* it is safe to increment low-part to avoid ABA, since MDBX_TXNID_STEP > 1 + * and overflow was preserved in safe64_txnid_next() */ + atomic_add32(&ptr->low, 1) /* avoid ABA in safe64_reset_compare() */; + ptr->high = UINT32_MAX /* atomically make >= SAFE64_INVALID_THRESHOLD */; + atomic_add32(&ptr->low, 1) /* avoid ABA in safe64_reset_compare() */; + } else +#else + (void)single_writer; +#endif /* !MDBX_64BIT_CAS */ +#if MDBX_64BIT_ATOMIC + ptr->atomic = UINT64_MAX; +#else + /* atomically make value >= SAFE64_INVALID_THRESHOLD */ + ptr->high = UINT32_MAX; +#endif /* MDBX_64BIT_ATOMIC */ + assert(ptr->inconsistent >= SAFE64_INVALID_THRESHOLD); + mdbx_flush_noncoherent_cpu_writeback(); + mdbx_jitter4testing(true); +} + +static __inline bool safe64_reset_compare(mdbx_safe64_t *ptr, txnid_t compare) { + mdbx_compiler_barrier(); + /* LY: This function is used to reset `mr_txnid` from OOM-kick in case + * the asynchronously cancellation of read transaction. Therefore, + * there may be a collision between the cleanup performed here and + * asynchronous termination and restarting of the read transaction + * in another proces/thread. In general we MUST NOT reset the `mr_txnid` + * if a new transaction was started (i.e. if `mr_txnid` was changed). */ +#if MDBX_64BIT_CAS + bool rc = atomic_cas64(&ptr->inconsistent, compare, UINT64_MAX); + mdbx_flush_noncoherent_cpu_writeback(); +#else + /* LY: There is no gold ratio here since shared mutex is too costly, + * in such way we must acquire/release it for every update of mr_txnid, + * i.e. twice for each read transaction). */ + bool rc = false; + if (likely(ptr->low == (uint32_t)compare && + atomic_cas32(&ptr->high, (uint32_t)(compare >> 32), UINT32_MAX))) { + if (unlikely(ptr->low != (uint32_t)compare)) + atomic_cas32(&ptr->high, UINT32_MAX, (uint32_t)(compare >> 32)); + else + rc = true; + } +#endif /* MDBX_64BIT_CAS */ + mdbx_jitter4testing(true); + return rc; +} + +static __inline void safe64_write(mdbx_safe64_t *ptr, const uint64_t v) { + mdbx_compiler_barrier(); + assert(ptr->inconsistent >= SAFE64_INVALID_THRESHOLD); +#if MDBX_64BIT_ATOMIC + ptr->atomic = v; +#else /* MDBX_64BIT_ATOMIC */ + /* update low-part but still value >= SAFE64_INVALID_THRESHOLD */ + ptr->low = (uint32_t)v; + assert(ptr->inconsistent >= SAFE64_INVALID_THRESHOLD); + mdbx_flush_noncoherent_cpu_writeback(); + mdbx_jitter4testing(true); + /* update high-part from SAFE64_INVALID_THRESHOLD to actual value */ + ptr->high = (uint32_t)(v >> 32); +#endif /* MDBX_64BIT_ATOMIC */ + assert(ptr->inconsistent == v); + mdbx_flush_noncoherent_cpu_writeback(); + mdbx_jitter4testing(true); +} + +static __always_inline uint64_t safe64_read(const mdbx_safe64_t *ptr) { + mdbx_compiler_barrier(); + mdbx_jitter4testing(true); + uint64_t v; +#if MDBX_64BIT_ATOMIC + v = ptr->atomic; +#else /* MDBX_64BIT_ATOMIC */ + uint32_t hi, lo; + do { + hi = ptr->high; + mdbx_compiler_barrier(); + mdbx_jitter4testing(true); + lo = ptr->low; + mdbx_compiler_barrier(); + mdbx_jitter4testing(true); + } while (unlikely(hi != ptr->high)); + v = lo | (uint64_t)hi << 32; +#endif /* MDBX_64BIT_ATOMIC */ + mdbx_jitter4testing(true); + return v; +} + +static __inline void safe64_update(mdbx_safe64_t *ptr, const uint64_t v) { + safe64_reset(ptr, true); + safe64_write(ptr, v); +} + +/*----------------------------------------------------------------------------*/ +/* rthc (tls keys and destructors) */ + +typedef struct rthc_entry_t { + MDBX_reader *begin; + MDBX_reader *end; + mdbx_thread_key_t thr_tls_key; + bool key_valid; +} rthc_entry_t; + +#if MDBX_DEBUG +#define RTHC_INITIAL_LIMIT 1 +#else +#define RTHC_INITIAL_LIMIT 16 +#endif + +static bin128_t bootid; +#if defined(_WIN32) || defined(_WIN64) +static CRITICAL_SECTION rthc_critical_section; +static CRITICAL_SECTION lcklist_critical_section; +#else +int __cxa_thread_atexit_impl(void (*dtor)(void *), void *obj, void *dso_symbol) + __attribute__((__weak__)); +#ifdef __APPLE__ /* FIXME: Thread-Local Storage destructors & DSO-unloading */ +int __cxa_thread_atexit_impl(void (*dtor)(void *), void *obj, + void *dso_symbol) { + (void)dtor; + (void)obj; + (void)dso_symbol; + return -1; +} +#endif /* __APPLE__ */ + +static pthread_mutex_t lcklist_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t rthc_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t rthc_cond = PTHREAD_COND_INITIALIZER; +static mdbx_thread_key_t rthc_key; +static volatile uint32_t rthc_pending; + +static void __cold workaround_glibc_bug21031(void) { + /* Workaround for https://sourceware.org/bugzilla/show_bug.cgi?id=21031 + * + * Due race between pthread_key_delete() and __nptl_deallocate_tsd() + * The destructor(s) of thread-local-storate object(s) may be running + * in another thread(s) and be blocked or not finished yet. + * In such case we get a SEGFAULT after unload this library DSO. + * + * So just by yielding a few timeslices we give a chance + * to such destructor(s) for completion and avoids segfault. */ + sched_yield(); + sched_yield(); + sched_yield(); +} +#endif + +static unsigned rthc_count, rthc_limit; +static rthc_entry_t *rthc_table; +static rthc_entry_t rthc_table_static[RTHC_INITIAL_LIMIT]; + +static __inline void rthc_lock(void) { +#if defined(_WIN32) || defined(_WIN64) + EnterCriticalSection(&rthc_critical_section); +#else + mdbx_ensure(nullptr, pthread_mutex_lock(&rthc_mutex) == 0); +#endif +} + +static __inline void rthc_unlock(void) { +#if defined(_WIN32) || defined(_WIN64) + LeaveCriticalSection(&rthc_critical_section); +#else + mdbx_ensure(nullptr, pthread_mutex_unlock(&rthc_mutex) == 0); +#endif +} + +static __inline int thread_key_create(mdbx_thread_key_t *key) { + int rc; +#if defined(_WIN32) || defined(_WIN64) + *key = TlsAlloc(); + rc = (*key != TLS_OUT_OF_INDEXES) ? MDBX_SUCCESS : GetLastError(); +#else + rc = pthread_key_create(key, nullptr); +#endif + mdbx_trace("&key = %p, value 0x%x, rc %d", __Wpedantic_format_voidptr(key), + (unsigned)*key, rc); + return rc; +} + +static __inline void thread_key_delete(mdbx_thread_key_t key) { + mdbx_trace("key = 0x%x", (unsigned)key); +#if defined(_WIN32) || defined(_WIN64) + mdbx_ensure(nullptr, TlsFree(key)); +#else + mdbx_ensure(nullptr, pthread_key_delete(key) == 0); + workaround_glibc_bug21031(); +#endif +} + +static __inline void *thread_rthc_get(mdbx_thread_key_t key) { +#if defined(_WIN32) || defined(_WIN64) + return TlsGetValue(key); +#else + return pthread_getspecific(key); +#endif +} + +static void thread_rthc_set(mdbx_thread_key_t key, const void *value) { +#if defined(_WIN32) || defined(_WIN64) + mdbx_ensure(nullptr, TlsSetValue(key, (void *)value)); +#else +#define MDBX_THREAD_RTHC_ZERO 0 +#define MDBX_THREAD_RTHC_REGISTERD 1 +#define MDBX_THREAD_RTHC_COUNTED 2 + static __thread uint32_t thread_registration_state; + if (value && unlikely(thread_registration_state == MDBX_THREAD_RTHC_ZERO)) { + thread_registration_state = MDBX_THREAD_RTHC_REGISTERD; + mdbx_trace("thread registered 0x%" PRIxPTR, (uintptr_t)mdbx_thread_self()); + if (&__cxa_thread_atexit_impl == nullptr || + __cxa_thread_atexit_impl(mdbx_rthc_thread_dtor, + &thread_registration_state, + (void *)&mdbx_version /* dso_anchor */)) { + mdbx_ensure(nullptr, pthread_setspecific( + rthc_key, &thread_registration_state) == 0); + thread_registration_state = MDBX_THREAD_RTHC_COUNTED; + const unsigned count_before = atomic_add32(&rthc_pending, 1); + mdbx_ensure(nullptr, count_before < INT_MAX); + mdbx_trace("fallback to pthreads' tsd, key 0x%x, count %u", + (unsigned)rthc_key, count_before); + (void)count_before; + } + } + mdbx_ensure(nullptr, pthread_setspecific(key, value) == 0); +#endif +} + +__cold void mdbx_rthc_global_init(void) { + rthc_limit = RTHC_INITIAL_LIMIT; + rthc_table = rthc_table_static; +#if defined(_WIN32) || defined(_WIN64) + InitializeCriticalSection(&rthc_critical_section); + InitializeCriticalSection(&lcklist_critical_section); +#else + mdbx_ensure(nullptr, + pthread_key_create(&rthc_key, mdbx_rthc_thread_dtor) == 0); + mdbx_trace("pid %d, &mdbx_rthc_key = %p, value 0x%x", mdbx_getpid(), + __Wpedantic_format_voidptr(&rthc_key), (unsigned)rthc_key); +#endif + /* checking time conversion, this also avoids racing on 32-bit architectures + * during writing calculated 64-bit ratio(s) into memory. */ + uint32_t proba = UINT32_MAX; + while (true) { + unsigned time_conversion_checkup = + mdbx_osal_monotime_to_16dot16(mdbx_osal_16dot16_to_monotime(proba)); + unsigned one_more = (proba < UINT32_MAX) ? proba + 1 : proba; + unsigned one_less = (proba > 0) ? proba - 1 : proba; + mdbx_ensure(nullptr, time_conversion_checkup >= one_less && + time_conversion_checkup <= one_more); + if (proba == 0) + break; + proba >>= 1; + } + + bootid = mdbx_osal_bootid(); +} + +/* dtor called for thread, i.e. for all mdbx's environment objects */ +__cold void mdbx_rthc_thread_dtor(void *ptr) { + rthc_lock(); + mdbx_trace(">> pid %d, thread 0x%" PRIxPTR ", rthc %p", mdbx_getpid(), + (uintptr_t)mdbx_thread_self(), ptr); + + const uint32_t self_pid = mdbx_getpid(); + for (unsigned i = 0; i < rthc_count; ++i) { + if (!rthc_table[i].key_valid) + continue; + const mdbx_thread_key_t key = rthc_table[i].thr_tls_key; + MDBX_reader *const rthc = thread_rthc_get(key); + if (rthc < rthc_table[i].begin || rthc >= rthc_table[i].end) + continue; +#if !defined(_WIN32) && !defined(_WIN64) + if (pthread_setspecific(key, nullptr) != 0) { + mdbx_trace("== thread 0x%" PRIxPTR + ", rthc %p: ignore race with tsd-key deletion", + (uintptr_t)mdbx_thread_self(), ptr); + continue /* ignore race with tsd-key deletion by mdbx_env_close() */; + } +#endif + + mdbx_trace("== thread 0x%" PRIxPTR + ", rthc %p, [%i], %p ... %p (%+i), rtch-pid %i, " + "current-pid %i", + (uintptr_t)mdbx_thread_self(), __Wpedantic_format_voidptr(rthc), + i, __Wpedantic_format_voidptr(rthc_table[i].begin), + __Wpedantic_format_voidptr(rthc_table[i].end), + (int)(rthc - rthc_table[i].begin), rthc->mr_pid, self_pid); + if (rthc->mr_pid == self_pid) { + mdbx_trace("==== thread 0x%" PRIxPTR ", rthc %p, cleanup", + (uintptr_t)mdbx_thread_self(), + __Wpedantic_format_voidptr(rthc)); + rthc->mr_pid = 0; + } + } + +#if defined(_WIN32) || defined(_WIN64) + mdbx_trace("<< thread 0x%" PRIxPTR ", rthc %p", (uintptr_t)mdbx_thread_self(), + ptr); + rthc_unlock(); +#else + const char self_registration = *(char *)ptr; + *(char *)ptr = MDBX_THREAD_RTHC_ZERO; + mdbx_trace("== thread 0x%" PRIxPTR ", rthc %p, pid %d, self-status %d", + (uintptr_t)mdbx_thread_self(), ptr, mdbx_getpid(), + self_registration); + if (self_registration == MDBX_THREAD_RTHC_COUNTED) + mdbx_ensure(nullptr, atomic_sub32(&rthc_pending, 1) > 0); + + if (rthc_pending == 0) { + mdbx_trace("== thread 0x%" PRIxPTR ", rthc %p, pid %d, wake", + (uintptr_t)mdbx_thread_self(), ptr, mdbx_getpid()); + mdbx_ensure(nullptr, pthread_cond_broadcast(&rthc_cond) == 0); + } + + mdbx_trace("<< thread 0x%" PRIxPTR ", rthc %p", (uintptr_t)mdbx_thread_self(), + ptr); + /* Allow tail call optimization, i.e. gcc should generate the jmp instruction + * instead of a call for pthread_mutex_unlock() and therefore CPU could not + * return to current DSO's code section, which may be unloaded immediately + * after the mutex got released. */ + pthread_mutex_unlock(&rthc_mutex); +#endif +} + +__cold void mdbx_rthc_global_dtor(void) { + mdbx_trace(">> pid %d", mdbx_getpid()); + + rthc_lock(); +#if !defined(_WIN32) && !defined(_WIN64) + char *rthc = (char *)pthread_getspecific(rthc_key); + mdbx_trace("== thread 0x%" PRIxPTR ", rthc %p, pid %d, self-status %d", + (uintptr_t)mdbx_thread_self(), __Wpedantic_format_voidptr(rthc), + mdbx_getpid(), rthc ? *rthc : -1); + if (rthc) { + const char self_registration = *(char *)rthc; + *rthc = MDBX_THREAD_RTHC_ZERO; + if (self_registration == MDBX_THREAD_RTHC_COUNTED) + mdbx_ensure(nullptr, atomic_sub32(&rthc_pending, 1) > 0); + } + + struct timespec abstime; + mdbx_ensure(nullptr, clock_gettime(CLOCK_REALTIME, &abstime) == 0); + abstime.tv_nsec += 1000000000l / 10; + if (abstime.tv_nsec >= 1000000000l) { + abstime.tv_nsec -= 1000000000l; + abstime.tv_sec += 1; + } +#if MDBX_DEBUG > 0 + abstime.tv_sec += 600; +#endif + + for (unsigned left; (left = rthc_pending) > 0;) { + mdbx_trace("pid %d, pending %u, wait for...", mdbx_getpid(), left); + const int rc = pthread_cond_timedwait(&rthc_cond, &rthc_mutex, &abstime); + if (rc && rc != EINTR) + break; + } + thread_key_delete(rthc_key); +#endif + + const uint32_t self_pid = mdbx_getpid(); + for (unsigned i = 0; i < rthc_count; ++i) { + if (!rthc_table[i].key_valid) + continue; + const mdbx_thread_key_t key = rthc_table[i].thr_tls_key; + thread_key_delete(key); + for (MDBX_reader *rthc = rthc_table[i].begin; rthc < rthc_table[i].end; + ++rthc) { + mdbx_trace("== [%i] = key %zu, %p ... %p, rthc %p (%+i), " + "rthc-pid %i, current-pid %i", + i, (size_t)key, + __Wpedantic_format_voidptr(rthc_table[i].begin), + __Wpedantic_format_voidptr(rthc_table[i].end), + __Wpedantic_format_voidptr(rthc), + (int)(rthc - rthc_table[i].begin), rthc->mr_pid, self_pid); + if (rthc->mr_pid == self_pid) { + rthc->mr_pid = 0; + mdbx_trace("== cleanup %p", __Wpedantic_format_voidptr(rthc)); + } + } + } + + rthc_limit = rthc_count = 0; + if (rthc_table != rthc_table_static) + mdbx_free(rthc_table); + rthc_table = nullptr; + rthc_unlock(); + +#if defined(_WIN32) || defined(_WIN64) + DeleteCriticalSection(&lcklist_critical_section); + DeleteCriticalSection(&rthc_critical_section); +#else + /* LY: yielding a few timeslices to give a more chance + * to racing destructor(s) for completion. */ + workaround_glibc_bug21031(); +#endif + + mdbx_trace("<< pid %d\n", mdbx_getpid()); +} + +__cold int mdbx_rthc_alloc(mdbx_thread_key_t *key, MDBX_reader *begin, + MDBX_reader *end) { + int rc; + if (key) { +#ifndef NDEBUG + *key = (mdbx_thread_key_t)0xBADBADBAD; +#endif /* NDEBUG */ + rc = thread_key_create(key); + if (rc != MDBX_SUCCESS) + return rc; + } + + rthc_lock(); + const mdbx_thread_key_t new_key = key ? *key : 0; + mdbx_trace(">> key %zu, rthc_count %u, rthc_limit %u", (size_t)new_key, + rthc_count, rthc_limit); + if (rthc_count == rthc_limit) { + rthc_entry_t *new_table = + mdbx_realloc((rthc_table == rthc_table_static) ? nullptr : rthc_table, + sizeof(rthc_entry_t) * rthc_limit * 2); + if (new_table == nullptr) { + rc = MDBX_ENOMEM; + goto bailout; + } + if (rthc_table == rthc_table_static) + memcpy(new_table, rthc_table_static, sizeof(rthc_table_static)); + rthc_table = new_table; + rthc_limit *= 2; + } + mdbx_trace("== [%i] = key %zu, %p ... %p", rthc_count, (size_t)new_key, + __Wpedantic_format_voidptr(begin), + __Wpedantic_format_voidptr(end)); + rthc_table[rthc_count].key_valid = key ? true : false; + rthc_table[rthc_count].thr_tls_key = key ? new_key : 0; + rthc_table[rthc_count].begin = begin; + rthc_table[rthc_count].end = end; + ++rthc_count; + mdbx_trace("<< key %zu, rthc_count %u, rthc_limit %u", (size_t)new_key, + rthc_count, rthc_limit); + rthc_unlock(); + return MDBX_SUCCESS; + +bailout: + if (key) + thread_key_delete(*key); + rthc_unlock(); + return rc; +} + +__cold void mdbx_rthc_remove(const mdbx_thread_key_t key) { + thread_key_delete(key); + rthc_lock(); + mdbx_trace(">> key %zu, rthc_count %u, rthc_limit %u", (size_t)key, + rthc_count, rthc_limit); + + for (unsigned i = 0; i < rthc_count; ++i) { + if (rthc_table[i].key_valid && key == rthc_table[i].thr_tls_key) { + const uint32_t self_pid = mdbx_getpid(); + mdbx_trace("== [%i], %p ...%p, current-pid %d", i, + __Wpedantic_format_voidptr(rthc_table[i].begin), + __Wpedantic_format_voidptr(rthc_table[i].end), self_pid); + + for (MDBX_reader *rthc = rthc_table[i].begin; rthc < rthc_table[i].end; + ++rthc) { + if (rthc->mr_pid == self_pid) { + rthc->mr_pid = 0; + mdbx_trace("== cleanup %p", __Wpedantic_format_voidptr(rthc)); + } + } + if (--rthc_count > 0) + rthc_table[i] = rthc_table[rthc_count]; + else if (rthc_table != rthc_table_static) { + mdbx_free(rthc_table); + rthc_table = rthc_table_static; + rthc_limit = RTHC_INITIAL_LIMIT; + } + break; + } + } + + mdbx_trace("<< key %zu, rthc_count %u, rthc_limit %u", (size_t)key, + rthc_count, rthc_limit); + rthc_unlock(); +} + +//------------------------------------------------------------------------------ + +#define RTHC_ENVLIST_END ((MDBX_env *)((size_t)50459)) +static MDBX_env *inprocess_lcklist_head = RTHC_ENVLIST_END; + +static __inline void lcklist_lock(void) { +#if defined(_WIN32) || defined(_WIN64) + EnterCriticalSection(&lcklist_critical_section); +#else + mdbx_ensure(nullptr, pthread_mutex_lock(&lcklist_mutex) == 0); +#endif +} + +static __inline void lcklist_unlock(void) { +#if defined(_WIN32) || defined(_WIN64) + LeaveCriticalSection(&lcklist_critical_section); +#else + mdbx_ensure(nullptr, pthread_mutex_unlock(&lcklist_mutex) == 0); +#endif +} + +static uint64_t rrxmrrxmsx_0(uint64_t v) { + /* Pelle Evensen's mixer, https://bit.ly/2HOfynt */ + v ^= (v << 39 | v >> 25) ^ (v << 14 | v >> 50); + v *= UINT64_C(0xA24BAED4963EE407); + v ^= (v << 40 | v >> 24) ^ (v << 15 | v >> 49); + v *= UINT64_C(0x9FB21C651E98DF25); + return v ^ v >> 28; +} + +static int uniq_peek(const mdbx_mmap_t *pending, mdbx_mmap_t *scan) { + int rc; + uint64_t bait; + if (pending->address) { + bait = pending->lck->mti_bait_uniqueness; + rc = MDBX_SUCCESS; + } else { + bait = 0 /* hush MSVC warning */; + rc = mdbx_msync(scan, 0, sizeof(MDBX_lockinfo), true); + if (rc == MDBX_SUCCESS) + rc = + mdbx_pread(pending->fd, &bait, sizeof(scan->lck->mti_bait_uniqueness), + offsetof(MDBX_lockinfo, mti_bait_uniqueness)); + } + if (likely(rc == MDBX_SUCCESS) && bait == scan->lck->mti_bait_uniqueness) + rc = MDBX_RESULT_TRUE; + + mdbx_trace("uniq-peek: %s, bait 0x%016" PRIx64 ",%s rc %d", + pending->lck ? "mem" : "file", bait, + (rc == MDBX_RESULT_TRUE) ? " found," : (rc ? " FAILED," : ""), rc); + return rc; +} + +static int uniq_poke(const mdbx_mmap_t *pending, mdbx_mmap_t *scan, + uint64_t *abra) { + if (*abra == 0) { + const size_t tid = mdbx_thread_self(); + size_t uit = 0; + memcpy(&uit, &tid, (sizeof(tid) < sizeof(uit)) ? sizeof(tid) : sizeof(uit)); + *abra = + rrxmrrxmsx_0(mdbx_osal_monotime() + UINT64_C(5873865991930747) * uit); + } + const uint64_t cadabra = + rrxmrrxmsx_0(*abra + UINT64_C(7680760450171793) * (unsigned)mdbx_getpid()) + << 24 | + *abra >> 40; + scan->lck->mti_bait_uniqueness = cadabra; + mdbx_flush_noncoherent_cpu_writeback(); + *abra = *abra * UINT64_C(6364136223846793005) + 1; + return uniq_peek(pending, scan); +} + +__cold static int uniq_check(const mdbx_mmap_t *pending, MDBX_env **found) { + *found = nullptr; + uint64_t salt = 0; + for (MDBX_env *scan = inprocess_lcklist_head; scan != RTHC_ENVLIST_END; + scan = scan->me_lcklist_next) { + int err = scan->me_lck_mmap.lck->mti_bait_uniqueness + ? uniq_peek(pending, &scan->me_lck_mmap) + : uniq_poke(pending, &scan->me_lck_mmap, &salt); + if (err == MDBX_ENODATA) { + uint64_t length; + if (likely(mdbx_filesize(pending->fd, &length) == MDBX_SUCCESS && + length == 0)) { + /* LY: skip checking since LCK-file is empty, i.e. just created. */ + mdbx_debug("uniq-probe: %s", "unique (new/empty lck)"); + return MDBX_RESULT_TRUE; + } + } + if (err == MDBX_RESULT_TRUE) + err = uniq_poke(pending, &scan->me_lck_mmap, &salt); + if (err == MDBX_RESULT_TRUE) { + (void)mdbx_msync(&scan->me_lck_mmap, 0, sizeof(MDBX_lockinfo), false); + err = uniq_poke(pending, &scan->me_lck_mmap, &salt); + } + if (err == MDBX_RESULT_TRUE) { + err = uniq_poke(pending, &scan->me_lck_mmap, &salt); + *found = scan; + mdbx_debug("uniq-probe: found %p", __Wpedantic_format_voidptr(*found)); + return MDBX_RESULT_FALSE; + } + if (unlikely(err != MDBX_SUCCESS)) { + mdbx_debug("uniq-probe: failed rc %d", err); + return err; + } + } + + mdbx_debug("uniq-probe: %s", "unique"); + return MDBX_RESULT_TRUE; +} + +static int lcklist_detach_locked(MDBX_env *env) { + MDBX_env *inprocess_neighbor = nullptr; + int rc = MDBX_SUCCESS; + if (env->me_lcklist_next != nullptr) { + mdbx_ensure(env, env->me_lcklist_next != nullptr); + mdbx_ensure(env, inprocess_lcklist_head != RTHC_ENVLIST_END); + for (MDBX_env **ptr = &inprocess_lcklist_head; *ptr != RTHC_ENVLIST_END; + ptr = &(*ptr)->me_lcklist_next) { + if (*ptr == env) { + *ptr = env->me_lcklist_next; + env->me_lcklist_next = nullptr; + break; + } + } + mdbx_ensure(env, env->me_lcklist_next == nullptr); + } + + rc = likely(mdbx_getpid() == env->me_pid) + ? uniq_check(&env->me_lck_mmap, &inprocess_neighbor) + : MDBX_PANIC; + if (!inprocess_neighbor && env->me_live_reader) + (void)mdbx_rpid_clear(env); + if (!MDBX_IS_ERROR(rc)) + rc = mdbx_lck_destroy(env, inprocess_neighbor); + return rc; +} + +/*------------------------------------------------------------------------------ + * LY: State of the art quicksort-based sorting, with internal stack and + * shell-insertion-sort for small chunks (less than half of SORT_THRESHOLD). + */ + +/* LY: Large threshold give some boost due less overhead in the inner qsort + * loops, but also a penalty in cases reverse-sorted data. + * So, 42 is magically but reasonable: + * - 0-3% faster than std::sort (from GNU C++ STL 2018) in most cases. + * - slower by a few ticks in a few cases for sequences shorter than 21. */ +#define SORT_THRESHOLD 42 + +#define SORT_SWAP(TYPE, a, b) \ + do { \ + const TYPE swap_tmp = (a); \ + (a) = (b); \ + (b) = swap_tmp; \ + } while (0) + +#define SORT_SHELLPASS(TYPE, CMP, begin, end, gap) \ + for (TYPE *i = begin + gap; i < end; ++i) { \ + for (TYPE *j = i - (gap); j >= begin && CMP(*i, *j); j -= gap) { \ + const TYPE tmp = *i; \ + do { \ + j[gap] = *j; \ + j -= gap; \ + } while (j >= begin && CMP(tmp, *j)); \ + j[gap] = tmp; \ + break; \ + } \ + } + +#define SORT_PUSH(low, high) \ + do { \ + top->lo = (low); \ + top->hi = (high); \ + ++top; \ + } while (0) + +#define SORT_POP(low, high) \ + do { \ + --top; \ + low = top->lo; \ + high = top->hi; \ + } while (0) + +#define SORT_IMPL(NAME, TYPE, CMP) \ + \ + typedef struct { \ + TYPE *lo, *hi; \ + } NAME##_stack; \ + \ + static __hot void NAME(TYPE *const begin, TYPE *const end) { \ + const ptrdiff_t length = end - begin; \ + if (length < 2) \ + return; \ + \ + if (length > SORT_THRESHOLD / 2) { \ + NAME##_stack stack[sizeof(unsigned) * CHAR_BIT], *top = stack; \ + \ + TYPE *hi = end - 1; \ + TYPE *lo = begin; \ + while (true) { \ + TYPE *mid = lo + ((hi - lo) >> 1); \ + if (CMP(*mid, *lo)) \ + SORT_SWAP(TYPE, *mid, *lo); \ + if (CMP(*hi, *mid)) { \ + SORT_SWAP(TYPE, *hi, *mid); \ + if (CMP(*mid, *lo)) \ + SORT_SWAP(TYPE, *mid, *lo); \ + } \ + \ + TYPE *right = hi - 1; \ + TYPE *left = lo + 1; \ + do { \ + while (CMP(*mid, *right)) \ + --right; \ + while (CMP(*left, *mid)) \ + ++left; \ + if (left < right) { \ + SORT_SWAP(TYPE, *left, *right); \ + if (mid == left) \ + mid = right; \ + else if (mid == right) \ + mid = left; \ + ++left; \ + --right; \ + } else if (left == right) { \ + ++left; \ + --right; \ + break; \ + } \ + } while (left <= right); \ + \ + if (lo + SORT_THRESHOLD > right) { \ + if (left + SORT_THRESHOLD > hi) { \ + if (top == stack) \ + break; \ + else \ + SORT_POP(lo, hi); \ + } else \ + lo = left; \ + } else if (left + SORT_THRESHOLD > hi) \ + hi = right; \ + else if (right - lo > hi - left) { \ + SORT_PUSH(lo, right); \ + lo = left; \ + } else { \ + SORT_PUSH(left, hi); \ + hi = right; \ + } \ + } \ + } \ + \ + SORT_SHELLPASS(TYPE, CMP, begin, end, 8); \ + SORT_SHELLPASS(TYPE, CMP, begin, end, 1); \ + for (TYPE *scan = begin + 1; scan < end; ++scan) \ + assert(CMP(scan[-1], scan[0])); \ + } + +/*------------------------------------------------------------------------------ + * LY: Binary search */ + +#define SEARCH_IMPL(NAME, TYPE_LIST, TYPE_ARG, CMP) \ + static __always_inline TYPE_LIST *NAME(TYPE_LIST *first, unsigned length, \ + const TYPE_ARG item) { \ + TYPE_LIST *const begin = first, *const end = begin + length; \ + \ + while (length > 3) { \ + const unsigned half = length >> 1; \ + TYPE_LIST *const middle = first + half; \ + if (CMP(*middle, item)) { \ + first = middle + 1; \ + length -= half + 1; \ + } else \ + length = half; \ + } \ + \ + switch (length) { \ + case 3: \ + if (!CMP(*first, item)) \ + break; \ + ++first; \ + /* fall through */ \ + __fallthrough; \ + case 2: \ + if (!CMP(*first, item)) \ + break; \ + ++first; \ + /* fall through */ \ + __fallthrough; \ + case 1: \ + if (CMP(*first, item)) \ + ++first; \ + } \ + \ + for (TYPE_LIST *scan = begin; scan < first; ++scan) \ + assert(CMP(*scan, item)); \ + for (TYPE_LIST *scan = first; scan < end; ++scan) \ + assert(!CMP(*scan, item)); \ + (void)begin, (void)end; \ + \ + return first; \ + } + +/*----------------------------------------------------------------------------*/ + +static __inline size_t pnl2bytes(const size_t size) { + assert(size > 0 && size <= MDBX_PNL_MAX * 2); + size_t bytes = roundup_powerof2(MDBX_ASSUME_MALLOC_OVERHEAD + + sizeof(pgno_t) * (size + 2), + MDBX_PNL_GRANULATE * sizeof(pgno_t)) - + MDBX_ASSUME_MALLOC_OVERHEAD; + return bytes; +} + +static __inline pgno_t bytes2pnl(const size_t bytes) { + size_t size = bytes / sizeof(pgno_t); + assert(size > 2 && size <= MDBX_PNL_MAX * 2); + return (pgno_t)size - 2; +} + +static MDBX_PNL mdbx_pnl_alloc(size_t size) { + size_t bytes = pnl2bytes(size); + MDBX_PNL pl = mdbx_malloc(bytes); + if (likely(pl)) { +#if __GLIBC_PREREQ(2, 12) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(malloc_usable_size) + bytes = malloc_usable_size(pl); +#endif /* malloc_usable_size */ + pl[0] = bytes2pnl(bytes); + assert(pl[0] >= size); + pl[1] = 0; + pl += 1; + } + return pl; +} + +static void mdbx_pnl_free(MDBX_PNL pl) { + if (likely(pl)) + mdbx_free(pl - 1); +} + +/* Shrink the PNL to the default size if it has grown larger */ +static void mdbx_pnl_shrink(MDBX_PNL *ppl) { + assert(bytes2pnl(pnl2bytes(MDBX_PNL_INITIAL)) == MDBX_PNL_INITIAL); + assert(MDBX_PNL_SIZE(*ppl) <= MDBX_PNL_MAX && + MDBX_PNL_ALLOCLEN(*ppl) >= MDBX_PNL_SIZE(*ppl)); + MDBX_PNL_SIZE(*ppl) = 0; + if (unlikely(MDBX_PNL_ALLOCLEN(*ppl) > + MDBX_PNL_INITIAL * 2 - MDBX_CACHELINE_SIZE / sizeof(pgno_t))) { + size_t bytes = pnl2bytes(MDBX_PNL_INITIAL); + MDBX_PNL pl = mdbx_realloc(*ppl - 1, bytes); + if (likely(pl)) { +#if __GLIBC_PREREQ(2, 12) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(malloc_usable_size) + bytes = malloc_usable_size(pl); +#endif /* malloc_usable_size */ + *pl = bytes2pnl(bytes); + *ppl = pl + 1; + } + } +} + +/* Grow the PNL to the size growed to at least given size */ +static int mdbx_pnl_reserve(MDBX_PNL *ppl, const size_t wanna) { + const size_t allocated = MDBX_PNL_ALLOCLEN(*ppl); + assert(MDBX_PNL_SIZE(*ppl) <= MDBX_PNL_MAX && + MDBX_PNL_ALLOCLEN(*ppl) >= MDBX_PNL_SIZE(*ppl)); + if (likely(allocated >= wanna)) + return MDBX_SUCCESS; + + if (unlikely(wanna > /* paranoia */ MDBX_PNL_MAX)) + return MDBX_TXN_FULL; + + const size_t size = (wanna + wanna - allocated < MDBX_PNL_MAX) + ? wanna + wanna - allocated + : MDBX_PNL_MAX; + size_t bytes = pnl2bytes(size); + MDBX_PNL pl = mdbx_realloc(*ppl - 1, bytes); + if (likely(pl)) { +#if __GLIBC_PREREQ(2, 12) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(malloc_usable_size) + bytes = malloc_usable_size(pl); +#endif /* malloc_usable_size */ + *pl = bytes2pnl(bytes); + assert(*pl >= wanna); + *ppl = pl + 1; + return MDBX_SUCCESS; + } + return MDBX_ENOMEM; +} + +/* Make room for num additional elements in an PNL */ +static __inline int __must_check_result mdbx_pnl_need(MDBX_PNL *ppl, + size_t num) { + assert(MDBX_PNL_SIZE(*ppl) <= MDBX_PNL_MAX && + MDBX_PNL_ALLOCLEN(*ppl) >= MDBX_PNL_SIZE(*ppl)); + assert(num <= MDBX_PNL_MAX); + const size_t wanna = MDBX_PNL_SIZE(*ppl) + num; + return likely(MDBX_PNL_ALLOCLEN(*ppl) >= wanna) + ? MDBX_SUCCESS + : mdbx_pnl_reserve(ppl, wanna); +} + +static __inline void mdbx_pnl_xappend(MDBX_PNL pl, pgno_t pgno) { + assert(MDBX_PNL_SIZE(pl) < MDBX_PNL_ALLOCLEN(pl)); + if (mdbx_assert_enabled()) { + for (unsigned i = MDBX_PNL_SIZE(pl); i > 0; --i) + assert(pgno != pl[i]); + } + MDBX_PNL_SIZE(pl) += 1; + MDBX_PNL_LAST(pl) = pgno; +} + +/* Append an pgno onto an unsorted PNL */ +static __hot int __must_check_result mdbx_pnl_append(MDBX_PNL *ppl, + pgno_t pgno) { + /* Too big? */ + if (unlikely(MDBX_PNL_SIZE(*ppl) == MDBX_PNL_ALLOCLEN(*ppl))) { + int rc = mdbx_pnl_need(ppl, MDBX_PNL_GRANULATE); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + mdbx_pnl_xappend(*ppl, pgno); + return MDBX_SUCCESS; +} + +/* Append an PNL onto an unsorted PNL */ +static int __must_check_result mdbx_pnl_append_list(MDBX_PNL *ppl, + MDBX_PNL append) { + const unsigned len = MDBX_PNL_SIZE(append); + if (likely(len)) { + int rc = mdbx_pnl_need(ppl, MDBX_PNL_SIZE(append)); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + const MDBX_PNL pnl = *ppl; + unsigned w = MDBX_PNL_SIZE(pnl), r = 1; + do + pnl[++w] = append[r]; + while (++r <= len); + MDBX_PNL_SIZE(pnl) = w; + } + return MDBX_SUCCESS; +} + +/* Append an pgno range onto an unsorted PNL */ +static __hot int __must_check_result mdbx_pnl_append_range(MDBX_PNL *ppl, + pgno_t pgno, + unsigned n) { + assert(n > 0); + int rc = mdbx_pnl_need(ppl, n); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + const MDBX_PNL pnl = *ppl; +#if MDBX_PNL_ASCENDING + unsigned w = MDBX_PNL_SIZE(pnl); + do + pnl[++w] = pgno++; + while (--n); + MDBX_PNL_SIZE(pnl) = w; +#else + unsigned w = MDBX_PNL_SIZE(pnl) + n; + MDBX_PNL_SIZE(pnl) = w; + do + pnl[w--] = --n + pgno; + while (n); +#endif + + return MDBX_SUCCESS; +} + +/* Append an pgno range into the sorted PNL */ +static __hot int __must_check_result mdbx_pnl_insert_range(MDBX_PNL *ppl, + pgno_t pgno, + unsigned n) { + assert(n > 0); + int rc = mdbx_pnl_need(ppl, n); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + const MDBX_PNL pnl = *ppl; + unsigned r = MDBX_PNL_SIZE(pnl), w = r + n; + MDBX_PNL_SIZE(pnl) = w; + while (r && MDBX_PNL_DISORDERED(pnl[r], pgno)) + pnl[w--] = pnl[r--]; + + for (pgno_t fill = MDBX_PNL_ASCENDING ? pgno + n : pgno; w > r; --w) + pnl[w] = MDBX_PNL_ASCENDING ? --fill : fill++; + + return MDBX_SUCCESS; +} + +static bool __hot mdbx_pnl_check(const MDBX_PNL pl, const pgno_t limit) { + assert(limit >= MIN_PAGENO && limit <= MAX_PAGENO + 1); + if (likely(MDBX_PNL_SIZE(pl))) { + assert(MDBX_PNL_LEAST(pl) >= MIN_PAGENO); + assert(MDBX_PNL_MOST(pl) < limit); + assert(MDBX_PNL_SIZE(pl) <= MDBX_PNL_MAX); + if (unlikely(MDBX_PNL_SIZE(pl) > MDBX_PNL_MAX * 3 / 2)) + return false; + if (unlikely(MDBX_PNL_LEAST(pl) < MIN_PAGENO)) + return false; + if (unlikely(MDBX_PNL_MOST(pl) >= limit)) + return false; + for (const pgno_t *scan = &MDBX_PNL_LAST(pl); --scan > pl;) { + assert(MDBX_PNL_ORDERED(scan[0], scan[1])); + if (unlikely(!MDBX_PNL_ORDERED(scan[0], scan[1]))) + return false; + } + } + return true; +} + +static __inline bool mdbx_pnl_check4assert(const MDBX_PNL pl, + const pgno_t limit) { + if (unlikely(pl == nullptr)) + return true; + assert(MDBX_PNL_ALLOCLEN(pl) >= MDBX_PNL_SIZE(pl)); + if (unlikely(MDBX_PNL_ALLOCLEN(pl) < MDBX_PNL_SIZE(pl))) + return false; + return mdbx_pnl_check(pl, limit); +} + +/* Merge an PNL onto an PNL. The destination PNL must be big enough */ +static void __hot mdbx_pnl_xmerge(MDBX_PNL dst, const MDBX_PNL src) { + assert(mdbx_pnl_check4assert(dst, MAX_PAGENO + 1)); + assert(mdbx_pnl_check(src, MAX_PAGENO + 1)); + const size_t total = MDBX_PNL_SIZE(dst) + MDBX_PNL_SIZE(src); + assert(MDBX_PNL_ALLOCLEN(dst) >= total); + pgno_t *w = dst + total; + pgno_t *d = dst + MDBX_PNL_SIZE(dst); + const pgno_t *s = src + MDBX_PNL_SIZE(src); + dst[0] = /* detent for scan below */ (MDBX_PNL_ASCENDING ? 0 : ~(pgno_t)0); + while (s > src) { + while (MDBX_PNL_ORDERED(*s, *d)) + *w-- = *d--; + *w-- = *s--; + } + MDBX_PNL_SIZE(dst) = (pgno_t)total; + assert(mdbx_pnl_check4assert(dst, MAX_PAGENO + 1)); +} + +SORT_IMPL(pgno_sort, pgno_t, MDBX_PNL_ORDERED) +static __hot void mdbx_pnl_sort(MDBX_PNL pnl) { + pgno_sort(MDBX_PNL_BEGIN(pnl), MDBX_PNL_END(pnl)); + assert(mdbx_pnl_check(pnl, MAX_PAGENO + 1)); +} + +/* Search for an pgno in an PNL. + * Returns The index of the first item greater than or equal to pgno. */ +SEARCH_IMPL(pgno_bsearch, pgno_t, pgno_t, MDBX_PNL_ORDERED) + +static __hot unsigned mdbx_pnl_search(MDBX_PNL pnl, pgno_t id) { + assert(mdbx_pnl_check4assert(pnl, MAX_PAGENO + 1)); + pgno_t *begin = MDBX_PNL_BEGIN(pnl); + pgno_t *it = pgno_bsearch(begin, MDBX_PNL_SIZE(pnl), id); + pgno_t *end = begin + MDBX_PNL_SIZE(pnl); + assert(it >= begin && it <= end); + if (it != begin) + assert(MDBX_PNL_ORDERED(it[-1], id)); + if (it != end) + assert(!MDBX_PNL_ORDERED(it[0], id)); + return (unsigned)(it - begin + 1); +} + +static __hot unsigned mdbx_pnl_exist(MDBX_PNL pnl, pgno_t id) { + unsigned n = mdbx_pnl_search(pnl, id); + return (n <= MDBX_PNL_SIZE(pnl) && pnl[n] == id) ? n : 0; +} + +/*----------------------------------------------------------------------------*/ + +static __inline size_t txl2bytes(const size_t size) { + assert(size > 0 && size <= MDBX_TXL_MAX * 2); + size_t bytes = roundup_powerof2(MDBX_ASSUME_MALLOC_OVERHEAD + + sizeof(txnid_t) * (size + 2), + MDBX_TXL_GRANULATE * sizeof(txnid_t)) - + MDBX_ASSUME_MALLOC_OVERHEAD; + return bytes; +} + +static __inline size_t bytes2txl(const size_t bytes) { + size_t size = bytes / sizeof(txnid_t); + assert(size > 2 && size <= MDBX_TXL_MAX * 2); + return size - 2; +} + +static MDBX_TXL mdbx_txl_alloc(void) { + size_t bytes = txl2bytes(MDBX_TXL_INITIAL); + MDBX_TXL tl = mdbx_malloc(bytes); + if (likely(tl)) { +#if __GLIBC_PREREQ(2, 12) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(malloc_usable_size) + bytes = malloc_usable_size(tl); +#endif /* malloc_usable_size */ + tl[0] = bytes2txl(bytes); + assert(tl[0] >= MDBX_TXL_INITIAL); + tl[1] = 0; + tl += 1; + } + return tl; +} + +static void mdbx_txl_free(MDBX_TXL tl) { + if (likely(tl)) + mdbx_free(tl - 1); +} + +static int mdbx_txl_reserve(MDBX_TXL *ptl, const size_t wanna) { + const size_t allocated = (size_t)MDBX_PNL_ALLOCLEN(*ptl); + assert(MDBX_PNL_SIZE(*ptl) <= MDBX_TXL_MAX && + MDBX_PNL_ALLOCLEN(*ptl) >= MDBX_PNL_SIZE(*ptl)); + if (likely(allocated >= wanna)) + return MDBX_SUCCESS; + + if (unlikely(wanna > /* paranoia */ MDBX_TXL_MAX)) + return MDBX_TXN_FULL; + + const size_t size = (wanna + wanna - allocated < MDBX_TXL_MAX) + ? wanna + wanna - allocated + : MDBX_TXL_MAX; + size_t bytes = txl2bytes(size); + MDBX_TXL tl = mdbx_realloc(*ptl - 1, bytes); + if (likely(tl)) { +#if __GLIBC_PREREQ(2, 12) || defined(__FreeBSD__) || defined(__OpenBSD__) || \ + defined(malloc_usable_size) + bytes = malloc_usable_size(tl); +#endif /* malloc_usable_size */ + *tl = bytes2txl(bytes); + assert(*tl >= wanna); + *ptl = tl + 1; + return MDBX_SUCCESS; + } + return MDBX_ENOMEM; +} + +static __inline int __must_check_result mdbx_txl_need(MDBX_TXL *ptl, + size_t num) { + assert(MDBX_PNL_SIZE(*ptl) <= MDBX_TXL_MAX && + MDBX_PNL_ALLOCLEN(*ptl) >= MDBX_PNL_SIZE(*ptl)); + assert(num <= MDBX_PNL_MAX); + const size_t wanna = (size_t)MDBX_PNL_SIZE(*ptl) + num; + return likely(MDBX_PNL_ALLOCLEN(*ptl) >= wanna) + ? MDBX_SUCCESS + : mdbx_txl_reserve(ptl, wanna); +} + +static __inline void mdbx_txl_xappend(MDBX_TXL tl, txnid_t id) { + assert(MDBX_PNL_SIZE(tl) < MDBX_PNL_ALLOCLEN(tl)); + MDBX_PNL_SIZE(tl) += 1; + MDBX_PNL_LAST(tl) = id; +} + +#define TXNID_SORT_CMP(first, last) ((first) > (last)) +SORT_IMPL(txnid_sort, txnid_t, TXNID_SORT_CMP) +static void mdbx_txl_sort(MDBX_TXL tl) { + txnid_sort(MDBX_PNL_BEGIN(tl), MDBX_PNL_END(tl)); +} + +static int __must_check_result mdbx_txl_append(MDBX_TXL *ptl, txnid_t id) { + if (unlikely(MDBX_PNL_SIZE(*ptl) == MDBX_PNL_ALLOCLEN(*ptl))) { + int rc = mdbx_txl_need(ptl, MDBX_TXL_GRANULATE); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + mdbx_txl_xappend(*ptl, id); + return MDBX_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ + +#define DP_SORT_CMP(first, last) ((first).pgno < (last).pgno) +SORT_IMPL(dp_sort, MDBX_DP, DP_SORT_CMP) +static __inline MDBX_DPL mdbx_dpl_sort(MDBX_DPL dl) { + assert(dl->length <= MDBX_DPL_TXNFULL); + assert(dl->sorted <= dl->length); + if (dl->sorted != dl->length) { + dl->sorted = dl->length; + dp_sort(dl + 1, dl + dl->length + 1); + } + return dl; +} + +/* Returns the index of the first dirty-page whose pgno + * member is greater than or equal to id. */ +#define DP_SEARCH_CMP(dp, id) ((dp).pgno < (id)) +SEARCH_IMPL(dp_bsearch, MDBX_DP, pgno_t, DP_SEARCH_CMP) + +static unsigned __hot mdbx_dpl_search(MDBX_DPL dl, pgno_t pgno) { + if (dl->sorted < dl->length) { + /* unsorted tail case */ +#if MDBX_DEBUG + for (const MDBX_DP *ptr = dl + dl->sorted; --ptr > dl;) { + assert(ptr[0].pgno < ptr[1].pgno); + assert(ptr[0].pgno >= NUM_METAS); + } +#endif + + /* try linear search until the threshold */ + if (dl->length - dl->sorted < SORT_THRESHOLD / 2) { + unsigned i = dl->length; + while (i - dl->sorted > 7) { + if (dl[i].pgno == pgno) + return i; + if (dl[i - 1].pgno == pgno) + return i - 1; + if (dl[i - 2].pgno == pgno) + return i - 2; + if (dl[i - 3].pgno == pgno) + return i - 3; + if (dl[i - 4].pgno == pgno) + return i - 4; + if (dl[i - 5].pgno == pgno) + return i - 5; + if (dl[i - 6].pgno == pgno) + return i - 6; + if (dl[i - 7].pgno == pgno) + return i - 7; + i -= 8; + } + while (i > dl->sorted) { + if (dl[i].pgno == pgno) + return i; + --i; + } + + MDBX_DPL it = dp_bsearch(dl + 1, i, pgno); + return (unsigned)(it - dl); + } + + /* sort a whole */ + dl->sorted = dl->length; + dp_sort(dl + 1, dl + dl->length + 1); + } + +#if MDBX_DEBUG + for (const MDBX_DP *ptr = dl + dl->length; --ptr > dl;) { + assert(ptr[0].pgno < ptr[1].pgno); + assert(ptr[0].pgno >= NUM_METAS); + } +#endif + + MDBX_DPL it = dp_bsearch(dl + 1, dl->length, pgno); + return (unsigned)(it - dl); +} + +static __inline MDBX_page *mdbx_dpl_find(MDBX_DPL dl, pgno_t pgno) { + const unsigned i = mdbx_dpl_search(dl, pgno); + assert((int)i > 0); + return (i <= dl->length && dl[i].pgno == pgno) ? dl[i].ptr : nullptr; +} + +static __hot MDBX_page *mdbx_dpl_remove(MDBX_DPL dl, pgno_t prno) { + unsigned i = mdbx_dpl_search(dl, prno); + assert((int)i > 0); + MDBX_page *mp = nullptr; + if (i <= dl->length && dl[i].pgno == prno) { + dl->sorted -= dl->sorted >= i; + mp = dl[i].ptr; + while (i < dl->length) { + dl[i] = dl[i + 1]; + ++i; + } + dl->length -= 1; + } + return mp; +} + +static __inline int __must_check_result mdbx_dpl_append(MDBX_DPL dl, + pgno_t pgno, + MDBX_page *page) { + assert(dl->length <= MDBX_DPL_TXNFULL); +#if MDBX_DEBUG + for (unsigned i = dl->length; i > 0; --i) { + assert(dl[i].pgno != pgno); + if (unlikely(dl[i].pgno == pgno)) + return MDBX_PROBLEM; + } +#endif + + if (unlikely(dl->length == MDBX_DPL_TXNFULL)) + return MDBX_TXN_FULL; + + /* append page */ + const unsigned n = dl->length + 1; + if (n == 1 || (dl->sorted >= dl->length && dl[n - 1].pgno < pgno)) + dl->sorted = n; + dl->length = n; + dl[n].pgno = pgno; + dl[n].ptr = page; + return MDBX_SUCCESS; +} + +static __inline void mdbx_dpl_clear(MDBX_DPL dl) { + dl->sorted = dl->length = 0; +} + +/*----------------------------------------------------------------------------*/ + +#ifndef MDBX_ALLOY +uint8_t mdbx_runtime_flags = MDBX_RUNTIME_FLAGS_INIT; +uint8_t mdbx_loglevel = MDBX_DEBUG; +MDBX_debug_func *mdbx_debug_logger; +#endif /* MDBX_ALLOY */ + +static bool mdbx_refund(MDBX_txn *txn); +static __must_check_result int mdbx_page_retire(MDBX_cursor *mc, MDBX_page *mp); +static __must_check_result int mdbx_page_loose(MDBX_txn *txn, MDBX_page *mp); +static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, + int flags); +static int mdbx_page_new(MDBX_cursor *mc, uint32_t flags, unsigned num, + MDBX_page **mp); +static int mdbx_page_touch(MDBX_cursor *mc); +static int mdbx_cursor_touch(MDBX_cursor *mc); + +#define MDBX_END_NAMES \ + { \ + "committed", "empty-commit", "abort", "reset", "reset-tmp", "fail-begin", \ + "fail-beginchild" \ + } +enum { + /* mdbx_txn_end operation number, for logging */ + MDBX_END_COMMITTED, + MDBX_END_EMPTY_COMMIT, + MDBX_END_ABORT, + MDBX_END_RESET, + MDBX_END_RESET_TMP, + MDBX_END_FAIL_BEGIN, + MDBX_END_FAIL_BEGINCHILD +}; +#define MDBX_END_OPMASK 0x0F /* mask for mdbx_txn_end() operation number */ +#define MDBX_END_UPDATE 0x10 /* update env state (DBIs) */ +#define MDBX_END_FREE 0x20 /* free txn unless it is MDBX_env.me_txn0 */ +#define MDBX_END_EOTDONE 0x40 /* txn's cursors already closed */ +#define MDBX_END_SLOT 0x80 /* release any reader slot if MDBX_NOTLS */ +static int mdbx_txn_end(MDBX_txn *txn, unsigned mode); + +static int __must_check_result mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, + MDBX_page **mp, int *lvl); +static int __must_check_result mdbx_page_search_root(MDBX_cursor *mc, + MDBX_val *key, int modify); + +#define MDBX_PS_MODIFY 1 +#define MDBX_PS_ROOTONLY 2 +#define MDBX_PS_FIRST 4 +#define MDBX_PS_LAST 8 +static int __must_check_result mdbx_page_search(MDBX_cursor *mc, MDBX_val *key, + int flags); +static int __must_check_result mdbx_page_merge(MDBX_cursor *csrc, + MDBX_cursor *cdst); +static int __must_check_result mdbx_page_flush(MDBX_txn *txn, + const unsigned keep); + +#define MDBX_SPLIT_REPLACE MDBX_APPENDDUP /* newkey is not new */ +static int __must_check_result mdbx_page_split(MDBX_cursor *mc, + const MDBX_val *newkey, + MDBX_val *newdata, + pgno_t newpgno, unsigned nflags); + +static int __must_check_result mdbx_read_header(MDBX_env *env, MDBX_meta *meta, + uint64_t *filesize); +static int __must_check_result mdbx_sync_locked(MDBX_env *env, unsigned flags, + MDBX_meta *const pending); +static int mdbx_env_close0(MDBX_env *env); + +static MDBX_node *mdbx_node_search(MDBX_cursor *mc, MDBX_val *key, int *exactp); + +static int __must_check_result mdbx_node_add_branch(MDBX_cursor *mc, + unsigned indx, + const MDBX_val *key, + pgno_t pgno); +static int __must_check_result mdbx_node_add_leaf(MDBX_cursor *mc, + unsigned indx, + const MDBX_val *key, + MDBX_val *data, + unsigned flags); +static int __must_check_result mdbx_node_add_leaf2(MDBX_cursor *mc, + unsigned indx, + const MDBX_val *key); + +static void mdbx_node_del(MDBX_cursor *mc, size_t ksize); +static void mdbx_node_shrink(MDBX_page *mp, unsigned indx); +static int __must_check_result mdbx_node_move(MDBX_cursor *csrc, + MDBX_cursor *cdst, int fromleft); +static int __must_check_result mdbx_node_read(MDBX_cursor *mc, MDBX_node *leaf, + MDBX_val *data); +static int __must_check_result mdbx_rebalance(MDBX_cursor *mc); +static int __must_check_result mdbx_update_key(MDBX_cursor *mc, + const MDBX_val *key); + +static void mdbx_cursor_pop(MDBX_cursor *mc); +static int __must_check_result mdbx_cursor_push(MDBX_cursor *mc, MDBX_page *mp); + +static int __must_check_result mdbx_audit_ex(MDBX_txn *txn, + unsigned retired_stored, + bool dont_filter_gc); +static __maybe_unused __inline int __must_check_result +mdbx_audit(MDBX_txn *txn) { + return mdbx_audit_ex(txn, 0, (txn->mt_flags & MDBX_RDONLY) != 0); +} + +static int __must_check_result mdbx_page_check(MDBX_env *env, + const MDBX_page *const mp, + bool maybe_unfinished); +static int __must_check_result mdbx_cursor_check(MDBX_cursor *mc, bool pending); +static int __must_check_result mdbx_cursor_del0(MDBX_cursor *mc); +static int __must_check_result mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi, + MDBX_val *key, MDBX_val *data, + unsigned flags); +static int __must_check_result mdbx_cursor_sibling(MDBX_cursor *mc, + int move_right); +static int __must_check_result mdbx_cursor_next(MDBX_cursor *mc, MDBX_val *key, + MDBX_val *data, + MDBX_cursor_op op); +static int __must_check_result mdbx_cursor_prev(MDBX_cursor *mc, MDBX_val *key, + MDBX_val *data, + MDBX_cursor_op op); +static int __must_check_result mdbx_cursor_set(MDBX_cursor *mc, MDBX_val *key, + MDBX_val *data, + MDBX_cursor_op op, int *exactp); +static int __must_check_result mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key, + MDBX_val *data); +static int __must_check_result mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key, + MDBX_val *data); + +static int __must_check_result mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, + MDBX_dbi dbi); +static int __must_check_result mdbx_xcursor_init0(MDBX_cursor *mc); +static int __must_check_result mdbx_xcursor_init1(MDBX_cursor *mc, + MDBX_node *node); +static int __must_check_result mdbx_xcursor_init2(MDBX_cursor *mc, + MDBX_xcursor *src_mx, + int force); +static void mdbx_cursor_copy(const MDBX_cursor *csrc, MDBX_cursor *cdst); + +static int __must_check_result mdbx_drop0(MDBX_cursor *mc, int subs); +static int __must_check_result mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi); + +static MDBX_cmp_func mdbx_cmp_memn, mdbx_cmp_memnr, mdbx_cmp_int_align4, + mdbx_cmp_int_align2, mdbx_cmp_int_unaligned; + +static const char *__mdbx_strerr(int errnum) { + /* Table of descriptions for MDBX errors */ + static const char *const tbl[] = { + "MDBX_KEYEXIST: Key/data pair already exists", + "MDBX_NOTFOUND: No matching key/data pair found", + "MDBX_PAGE_NOTFOUND: Requested page not found", + "MDBX_CORRUPTED: Database is corrupted", + "MDBX_PANIC: Environment had fatal error", + "MDBX_VERSION_MISMATCH: DB version mismatch libmdbx", + "MDBX_INVALID: File is not an MDBX file", + "MDBX_MAP_FULL: Environment mapsize limit reached", + "MDBX_DBS_FULL: Too may DBI (maxdbs reached)", + "MDBX_READERS_FULL: Too many readers (maxreaders reached)", + NULL /* MDBX_TLS_FULL (-30789): unused in MDBX */, + "MDBX_TXN_FULL: Transaction has too many dirty pages, " + "i.e transaction too big", + "MDBX_CURSOR_FULL: Internal error - cursor stack limit reached", + "MDBX_PAGE_FULL: Internal error - page has no more space", + "MDBX_MAP_RESIZED: Database contents grew beyond environment mapsize", + "MDBX_INCOMPATIBLE: Operation and DB incompatible, or DB flags changed", + "MDBX_BAD_RSLOT: Invalid reuse of reader locktable slot", + "MDBX_BAD_TXN: Transaction must abort, has a child, or is invalid", + "MDBX_BAD_VALSIZE: Unsupported size of key/DB name/data, or wrong " + "DUPFIXED size", + "MDBX_BAD_DBI: The specified DBI handle was closed/changed unexpectedly", + "MDBX_PROBLEM: Unexpected problem - txn should abort", + "MDBX_BUSY: Another write transaction is running or " + "environment is already used while opening with MDBX_EXCLUSIVE flag", + }; + + if (errnum >= MDBX_KEYEXIST && errnum <= MDBX_LAST_ERRCODE) { + int i = errnum - MDBX_KEYEXIST; + return tbl[i]; + } + + switch (errnum) { + case MDBX_SUCCESS: + return "MDBX_SUCCESS: Successful"; + case MDBX_EMULTIVAL: + return "MDBX_EMULTIVAL: Unable to update multi-value for the given key"; + case MDBX_EBADSIGN: + return "MDBX_EBADSIGN: Wrong signature of a runtime object(s)"; + case MDBX_WANNA_RECOVERY: + return "MDBX_WANNA_RECOVERY: Database should be recovered, but this could " + "NOT be done in a read-only mode"; + case MDBX_EKEYMISMATCH: + return "MDBX_EKEYMISMATCH: The given key value is mismatched to the " + "current cursor position"; + case MDBX_TOO_LARGE: + return "MDBX_TOO_LARGE: Database is too large for current system, " + "e.g. could NOT be mapped into RAM"; + case MDBX_THREAD_MISMATCH: + return "MDBX_THREAD_MISMATCH: A thread has attempted to use a not " + "owned object, e.g. a transaction that started by another thread"; + default: + return NULL; + } +} + +const char *__cold mdbx_strerror_r(int errnum, char *buf, size_t buflen) { + const char *msg = __mdbx_strerr(errnum); + if (!msg && buflen > 0 && buflen < INT_MAX) { +#if defined(_WIN32) || defined(_WIN64) + const DWORD size = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + errnum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (DWORD)buflen, + NULL); + return size ? buf : NULL; +#elif defined(_GNU_SOURCE) && defined(__GLIBC__) + /* GNU-specific */ + if (errnum > 0) + msg = strerror_r(errnum, buf, buflen); +#elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) + /* XSI-compliant */ + if (errnum > 0 && strerror_r(errnum, buf, buflen) == 0) + msg = buf; +#else + if (errnum > 0) { + msg = strerror(errnum); + if (msg) { + strncpy(buf, msg, buflen); + msg = buf; + } + } +#endif + if (!msg) { + (void)snprintf(buf, buflen, "error %d", errnum); + msg = buf; + } + buf[buflen - 1] = '\0'; + } + return msg; +} + +const char *__cold mdbx_strerror(int errnum) { +#if defined(_WIN32) || defined(_WIN64) + static char buf[1024]; + return mdbx_strerror_r(errnum, buf, sizeof(buf)); +#else + const char *msg = __mdbx_strerr(errnum); + if (!msg) { + if (errnum > 0) + msg = strerror(errnum); + if (!msg) { + static char buf[32]; + (void)snprintf(buf, sizeof(buf) - 1, "error %d", errnum); + msg = buf; + } + } + return msg; +#endif +} + +#if defined(_WIN32) || defined(_WIN64) /* Bit of madness for Windows */ +const char *mdbx_strerror_r_ANSI2OEM(int errnum, char *buf, size_t buflen) { + const char *msg = __mdbx_strerr(errnum); + if (!msg && buflen > 0 && buflen < INT_MAX) { + const DWORD size = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, + errnum, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, (DWORD)buflen, + NULL); + if (size && CharToOemBuffA(buf, buf, size)) + msg = buf; + } + return msg; +} + +const char *mdbx_strerror_ANSI2OEM(int errnum) { + static char buf[1024]; + return mdbx_strerror_r_ANSI2OEM(errnum, buf, sizeof(buf)); +} +#endif /* Bit of madness for Windows */ + +static txnid_t mdbx_oomkick(MDBX_env *env, const txnid_t laggard); + +void __cold mdbx_debug_log(int level, const char *function, int line, + const char *fmt, ...) { + va_list args; + + va_start(args, fmt); + if (mdbx_debug_logger) + mdbx_debug_logger(level, function, line, fmt, args); + else { +#if defined(_WIN32) || defined(_WIN64) + if (IsDebuggerPresent()) { + int prefix_len = 0; + char *prefix = nullptr; + if (function && line > 0) + prefix_len = mdbx_asprintf(&prefix, "%s:%d ", function, line); + else if (function) + prefix_len = mdbx_asprintf(&prefix, "%s: ", function); + else if (line > 0) + prefix_len = mdbx_asprintf(&prefix, "%d: ", line); + if (prefix_len > 0 && prefix) { + OutputDebugStringA(prefix); + mdbx_free(prefix); + } + char *msg = nullptr; + int msg_len = mdbx_vasprintf(&msg, fmt, args); + if (msg_len > 0 && msg) { + OutputDebugStringA(msg); + mdbx_free(msg); + } + } +#else + if (function && line > 0) + fprintf(stderr, "%s:%d ", function, line); + else if (function) + fprintf(stderr, "%s: ", function); + else if (line > 0) + fprintf(stderr, "%d: ", line); + vfprintf(stderr, fmt, args); + fflush(stderr); +#endif + } + va_end(args); +} + +/* Dump a key in ascii or hexadecimal. */ +const char *mdbx_dump_val(const MDBX_val *key, char *const buf, + const size_t bufsize) { + if (!key) + return ""; + if (!buf || bufsize < 4) + return nullptr; + if (!key->iov_len) + return ""; + + const uint8_t *const data = key->iov_base; + bool is_ascii = true; + unsigned i; + for (i = 0; is_ascii && i < key->iov_len; i++) + if (data[i] < ' ' || data[i] > 127) + is_ascii = false; + + if (is_ascii) { + int len = + snprintf(buf, bufsize, "%.*s", + (key->iov_len > INT_MAX) ? INT_MAX : (int)key->iov_len, data); + assert(len > 0 && (unsigned)len < bufsize); + (void)len; + } else { + char *const detent = buf + bufsize - 2; + char *ptr = buf; + *ptr++ = '<'; + for (i = 0; i < key->iov_len; i++) { + const ptrdiff_t left = detent - ptr; + assert(left > 0); + int len = snprintf(ptr, left, "%02x", data[i]); + if (len < 0 || len >= left) + break; + ptr += len; + } + if (ptr < detent) { + ptr[0] = '>'; + ptr[1] = '\0'; + } + } + return buf; +} + +/*------------------------------------------------------------------------------ + LY: debug stuff */ + +static const char *mdbx_leafnode_type(MDBX_node *n) { + static const char *const tp[2][2] = {{"", ": DB"}, + {": sub-page", ": sub-DB"}}; + return F_ISSET(node_flags(n), F_BIGDATA) + ? ": overflow page" + : tp[F_ISSET(node_flags(n), F_DUPDATA)] + [F_ISSET(node_flags(n), F_SUBDATA)]; +} + +/* Display all the keys in the page. */ +static __maybe_unused void mdbx_page_list(MDBX_page *mp) { + pgno_t pgno = mp->mp_pgno; + const char *type, *state = IS_DIRTY(mp) ? ", dirty" : ""; + MDBX_node *node; + unsigned i, nkeys, nsize, total = 0; + MDBX_val key; + DKBUF; + + switch (mp->mp_flags & + (P_BRANCH | P_LEAF | P_LEAF2 | P_META | P_OVERFLOW | P_SUBP)) { + case P_BRANCH: + type = "Branch page"; + break; + case P_LEAF: + type = "Leaf page"; + break; + case P_LEAF | P_SUBP: + type = "Leaf sub-page"; + break; + case P_LEAF | P_LEAF2: + type = "Leaf2 page"; + break; + case P_LEAF | P_LEAF2 | P_SUBP: + type = "Leaf2 sub-page"; + break; + case P_OVERFLOW: + mdbx_verbose("Overflow page %" PRIaPGNO " pages %u%s\n", pgno, mp->mp_pages, + state); + return; + case P_META: + mdbx_verbose("Meta-page %" PRIaPGNO " txnid %" PRIu64 "\n", pgno, + page_meta(mp)->mm_txnid_a.inconsistent); + return; + default: + mdbx_verbose("Bad page %" PRIaPGNO " flags 0x%X\n", pgno, mp->mp_flags); + return; + } + + nkeys = page_numkeys(mp); + mdbx_verbose("%s %" PRIaPGNO " numkeys %u%s\n", type, pgno, nkeys, state); + + for (i = 0; i < nkeys; i++) { + if (IS_LEAF2(mp)) { /* LEAF2 pages have no mp_ptrs[] or node headers */ + key.iov_len = nsize = mp->mp_leaf2_ksize; + key.iov_base = page_leaf2key(mp, i, nsize); + total += nsize; + mdbx_verbose("key %u: nsize %u, %s\n", i, nsize, DKEY(&key)); + continue; + } + node = page_node(mp, i); + key.iov_len = node_ks(node); + key.iov_base = node->mn_data; + nsize = (unsigned)(NODESIZE + key.iov_len); + if (IS_BRANCH(mp)) { + mdbx_verbose("key %u: page %" PRIaPGNO ", %s\n", i, node_pgno(node), + DKEY(&key)); + total += nsize; + } else { + if (F_ISSET(node_flags(node), F_BIGDATA)) + nsize += sizeof(pgno_t); + else + nsize += (unsigned)node_ds(node); + total += nsize; + nsize += sizeof(indx_t); + mdbx_verbose("key %u: nsize %u, %s%s\n", i, nsize, DKEY(&key), + mdbx_leafnode_type(node)); + } + total = EVEN(total); + } + mdbx_verbose("Total: header %u + contents %u + unused %u\n", + IS_LEAF2(mp) ? PAGEHDRSZ : PAGEHDRSZ + mp->mp_lower, total, + page_room(mp)); +} + +/*----------------------------------------------------------------------------*/ + +/* Check if there is an inited xcursor, so XCURSOR_REFRESH() is proper */ +#define XCURSOR_INITED(mc) \ + ((mc)->mc_xcursor && ((mc)->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)) + +/* Update sub-page pointer, if any, in mc->mc_xcursor. + * Needed when the node which contains the sub-page may have moved. + * Called with mp = mc->mc_pg[mc->mc_top], ki = mc->mc_ki[mc->mc_top]. */ +#define XCURSOR_REFRESH(mc, mp, ki) \ + do { \ + MDBX_page *xr_pg = (mp); \ + MDBX_node *xr_node = page_node(xr_pg, ki); \ + if ((node_flags(xr_node) & (F_DUPDATA | F_SUBDATA)) == F_DUPDATA) \ + (mc)->mc_xcursor->mx_cursor.mc_pg[0] = node_data(xr_node); \ + } while (0) + +/* Perform act while tracking temporary cursor mn */ +#define WITH_CURSOR_TRACKING(mn, act) \ + do { \ + mdbx_cassert(&(mn), \ + mn.mc_txn->mt_cursors != NULL /* must be not rdonly txt */); \ + MDBX_cursor mc_dummy, **tp = &(mn).mc_txn->mt_cursors[mn.mc_dbi]; \ + MDBX_cursor *tracked = &(mn); \ + if ((mn).mc_flags & C_SUB) { \ + mc_dummy.mc_flags = C_INITIALIZED; \ + mc_dummy.mc_xcursor = (MDBX_xcursor *)&(mn); \ + tracked = &mc_dummy; \ + } \ + tracked->mc_next = *tp; \ + *tp = tracked; \ + { act; } \ + *tp = tracked->mc_next; \ + } while (0) + +int mdbx_cmp(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, + const MDBX_val *b) { + mdbx_assert(NULL, txn->mt_signature == MDBX_MT_SIGNATURE); + return txn->mt_dbxs[dbi].md_cmp(a, b); +} + +int mdbx_dcmp(MDBX_txn *txn, MDBX_dbi dbi, const MDBX_val *a, + const MDBX_val *b) { + mdbx_assert(NULL, txn->mt_signature == MDBX_MT_SIGNATURE); + return txn->mt_dbxs[dbi].md_dcmp(a, b); +} + +/* Allocate memory for a page. + * Re-use old malloc'd pages first for singletons, otherwise just malloc. + * Set MDBX_TXN_ERROR on failure. */ +static MDBX_page *mdbx_page_malloc(MDBX_txn *txn, unsigned num) { + MDBX_env *env = txn->mt_env; + MDBX_page *np = env->me_dpages; + size_t size = env->me_psize; + if (likely(num == 1 && np)) { + ASAN_UNPOISON_MEMORY_REGION(np, size); + VALGRIND_MEMPOOL_ALLOC(env, np, size); + VALGRIND_MAKE_MEM_DEFINED(&np->mp_next, sizeof(np->mp_next)); + env->me_dpages = np->mp_next; + } else { + size = pgno2bytes(env, num); + np = mdbx_malloc(size); + if (unlikely(!np)) { + txn->mt_flags |= MDBX_TXN_ERROR; + return np; + } + VALGRIND_MEMPOOL_ALLOC(env, np, size); + } + + if ((env->me_flags & MDBX_NOMEMINIT) == 0) { + /* For a single page alloc, we init everything after the page header. + * For multi-page, we init the final page; if the caller needed that + * many pages they will be filling in at least up to the last page. */ + size_t skip = PAGEHDRSZ; + if (num > 1) + skip += pgno2bytes(env, num - 1); + memset((char *)np + skip, 0, size - skip); + } +#if MDBX_DEBUG + np->mp_pgno = 0; +#endif + VALGRIND_MAKE_MEM_UNDEFINED(np, size); + np->mp_flags = 0; + np->mp_pages = num; + return np; +} + +/* Free a dirty page */ +static void mdbx_dpage_free(MDBX_env *env, MDBX_page *dp, unsigned pages) { +#if MDBX_DEBUG + dp->mp_pgno = MAX_PAGENO + 1; +#endif + if (pages == 1) { + dp->mp_next = env->me_dpages; + VALGRIND_MEMPOOL_FREE(env, dp); + env->me_dpages = dp; + } else { + /* large pages just get freed directly */ + VALGRIND_MEMPOOL_FREE(env, dp); + mdbx_free(dp); + } +} + +/* Return all dirty pages to dpage list */ +static void mdbx_dlist_free(MDBX_txn *txn) { + MDBX_env *env = txn->mt_env; + const MDBX_DPL dl = txn->tw.dirtylist; + const size_t n = dl->length; + + for (size_t i = 1; i <= n; i++) { + MDBX_page *dp = dl[i].ptr; + mdbx_dpage_free(env, dp, IS_OVERFLOW(dp) ? dp->mp_pages : 1); + } + + mdbx_dpl_clear(dl); +} + +static __inline MDBX_db *mdbx_outer_db(MDBX_cursor *mc) { + mdbx_cassert(mc, (mc->mc_flags & C_SUB) != 0); + MDBX_xcursor *mx = container_of(mc->mc_db, MDBX_xcursor, mx_db); + MDBX_cursor_couple *couple = container_of(mx, MDBX_cursor_couple, inner); + mdbx_cassert(mc, mc->mc_db == &couple->outer.mc_xcursor->mx_db); + mdbx_cassert(mc, mc->mc_dbx == &couple->outer.mc_xcursor->mx_dbx); + return couple->outer.mc_db; +} + +static __cold __maybe_unused bool mdbx_dirtylist_check(MDBX_txn *txn) { + unsigned loose = 0; + for (unsigned i = txn->tw.dirtylist->length; i > 0; --i) { + const MDBX_page *const dp = txn->tw.dirtylist[i].ptr; + if (!dp) + continue; + mdbx_tassert(txn, dp->mp_pgno == txn->tw.dirtylist[i].pgno); + if (unlikely(dp->mp_pgno != txn->tw.dirtylist[i].pgno)) + return false; + + mdbx_tassert(txn, dp->mp_flags & P_DIRTY); + if (unlikely((dp->mp_flags & P_DIRTY) == 0)) + return false; + if (dp->mp_flags & P_LOOSE) { + mdbx_tassert(txn, dp->mp_flags == (P_LOOSE | P_DIRTY)); + if (unlikely(dp->mp_flags != (P_LOOSE | P_DIRTY))) + return false; + loose += 1; + } + + const unsigned num = IS_OVERFLOW(dp) ? dp->mp_pages : 1; + mdbx_tassert(txn, txn->mt_next_pgno >= dp->mp_pgno + num); + if (unlikely(txn->mt_next_pgno < dp->mp_pgno + num)) + return false; + + if (i < txn->tw.dirtylist->sorted) { + mdbx_tassert(txn, txn->tw.dirtylist[i + 1].pgno >= dp->mp_pgno + num); + if (unlikely(txn->tw.dirtylist[i + 1].pgno < dp->mp_pgno + num)) + return false; + } + + const unsigned rpa = mdbx_pnl_search(txn->tw.reclaimed_pglist, dp->mp_pgno); + mdbx_tassert(txn, rpa > MDBX_PNL_SIZE(txn->tw.reclaimed_pglist) || + txn->tw.reclaimed_pglist[rpa] != dp->mp_pgno); + if (rpa <= MDBX_PNL_SIZE(txn->tw.reclaimed_pglist) && + unlikely(txn->tw.reclaimed_pglist[rpa] == dp->mp_pgno)) + return false; + if (num > 1) { + const unsigned rpb = + mdbx_pnl_search(txn->tw.reclaimed_pglist, dp->mp_pgno + num - 1); + mdbx_tassert(txn, rpa == rpb); + if (unlikely(rpa != rpb)) + return false; + } + } + + mdbx_tassert(txn, loose == txn->tw.loose_count); + if (unlikely(loose != txn->tw.loose_count)) + return false; + + if (txn->tw.dirtylist->length - txn->tw.dirtylist->sorted < + SORT_THRESHOLD / 2) { + for (unsigned i = 1; i <= MDBX_PNL_SIZE(txn->tw.retired_pages); ++i) { + const MDBX_page *const dp = + mdbx_dpl_find(txn->tw.dirtylist, txn->tw.retired_pages[i]); + mdbx_tassert(txn, !dp); + if (unlikely(dp)) + return false; + } + } + + return true; +} + +static void mdbx_refund_reclaimed(MDBX_txn *txn) { + /* Scanning in descend order */ + pgno_t next_pgno = txn->mt_next_pgno; + const MDBX_PNL pnl = txn->tw.reclaimed_pglist; + mdbx_tassert(txn, MDBX_PNL_SIZE(pnl) && MDBX_PNL_MOST(pnl) == next_pgno - 1); +#if MDBX_PNL_ASCENDING + unsigned i = MDBX_PNL_SIZE(pnl); + mdbx_tassert(txn, pnl[i] == next_pgno - 1); + while (--next_pgno, --i > 0 && pnl[i] == next_pgno - 1) + ; + MDBX_PNL_SIZE(pnl) = i; +#else + unsigned i = 1; + mdbx_tassert(txn, pnl[i] == next_pgno - 1); + unsigned len = MDBX_PNL_SIZE(pnl); + while (--next_pgno, ++i <= len && pnl[i] == next_pgno - 1) + ; + MDBX_PNL_SIZE(pnl) = len -= i - 1; + for (unsigned move = 0; move < len; ++move) + pnl[1 + move] = pnl[i + move]; +#endif + mdbx_verbose("refunded %" PRIaPGNO " pages: %" PRIaPGNO " -> %" PRIaPGNO, + txn->mt_next_pgno - next_pgno, txn->mt_next_pgno, next_pgno); + txn->mt_next_pgno = next_pgno; + mdbx_tassert( + txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, txn->mt_next_pgno)); +} + +static void mdbx_refund_loose(MDBX_txn *txn) { + mdbx_tassert(txn, mdbx_dirtylist_check(txn)); + mdbx_tassert(txn, txn->tw.loose_pages != nullptr); + mdbx_tassert(txn, txn->tw.loose_count > 0); + + const MDBX_DPL dl = txn->tw.dirtylist; + mdbx_tassert(txn, dl->length >= txn->tw.loose_count); + mdbx_tassert(txn, txn->tw.spill_pages == nullptr || + dl->length >= MDBX_PNL_SIZE(txn->tw.spill_pages)); + + pgno_t onstack[MDBX_CACHELINE_SIZE * 8 / sizeof(pgno_t)]; + MDBX_PNL suitable = onstack; + + if (dl->length - dl->sorted > txn->tw.loose_count) { + /* Dirty list is useless since unsorted. */ + if (bytes2pnl(sizeof(onstack)) < txn->tw.loose_count) { + suitable = mdbx_pnl_alloc(txn->tw.loose_count); + if (unlikely(!suitable)) + return /* this is not a reason for transaction fail */; + } + + /* Collect loose-pages which may be refunded. */ + mdbx_tassert(txn, txn->mt_next_pgno >= MIN_PAGENO + txn->tw.loose_count); + pgno_t most = MIN_PAGENO; + unsigned w = 0; + for (const MDBX_page *dp = txn->tw.loose_pages; dp; dp = dp->mp_next) { + mdbx_tassert(txn, dp->mp_flags == (P_LOOSE | P_DIRTY)); + mdbx_tassert(txn, txn->mt_next_pgno > dp->mp_pgno); + if (likely(txn->mt_next_pgno - txn->tw.loose_count <= dp->mp_pgno)) { + mdbx_tassert(txn, + w < ((suitable == onstack) ? bytes2pnl(sizeof(onstack)) + : MDBX_PNL_ALLOCLEN(suitable))); + suitable[++w] = dp->mp_pgno; + most = (dp->mp_pgno > most) ? dp->mp_pgno : most; + } + } + + if (most + 1 == txn->mt_next_pgno) { + /* Sort suitable list and refund pages at the tail. */ + MDBX_PNL_SIZE(suitable) = w; + mdbx_pnl_sort(suitable); + + /* Scanning in descend order */ + const int step = MDBX_PNL_ASCENDING ? -1 : 1; + const int begin = MDBX_PNL_ASCENDING ? MDBX_PNL_SIZE(suitable) : 1; + const int end = MDBX_PNL_ASCENDING ? 0 : MDBX_PNL_SIZE(suitable) + 1; + mdbx_tassert(txn, suitable[begin] >= suitable[end - step]); + mdbx_tassert(txn, most == suitable[begin]); + + for (int i = begin + step; i != end; i += step) { + if (suitable[i] != most - 1) + break; + most -= 1; + } + const unsigned refunded = txn->mt_next_pgno - most; + mdbx_verbose("refund-sorted %u pages %" PRIaPGNO " -> %" PRIaPGNO, + refunded, most, txn->mt_next_pgno); + txn->tw.loose_count -= refunded; + txn->tw.dirtyroom += refunded; + txn->mt_next_pgno = most; + + /* Filter-out dirty list */ + unsigned r = 0; + w = 0; + if (dl->sorted) { + do { + if (dl[++r].pgno < most) { + if (++w != r) + dl[w] = dl[r]; + } + } while (r < dl->sorted); + dl->sorted = w; + } + while (r < dl->length) { + if (dl[++r].pgno < most) { + if (++w != r) + dl[w] = dl[r]; + } + } + dl->length = w; + mdbx_tassert(txn, txn->mt_parent || + txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + goto unlink_loose; + } + } else { + /* Dirtylist is mostly sorted, just refund loose pages at the end. */ + mdbx_dpl_sort(dl); + mdbx_tassert(txn, dl->length < 2 || dl[1].pgno < dl[dl->length].pgno); + mdbx_tassert(txn, dl->sorted == dl->length); + + /* Scan dirtylist tail-forward and cutoff suitable pages. */ + while (dl->length && dl[dl->length].pgno == txn->mt_next_pgno - 1 && + dl[dl->length].ptr->mp_flags == (P_LOOSE | P_DIRTY)) { + MDBX_page *dp = dl[dl->length].ptr; + mdbx_verbose("refund-unsorted page %" PRIaPGNO, dp->mp_pgno); + mdbx_tassert(txn, dp->mp_pgno == dl[dl->length].pgno); + dl->length -= 1; + } + + if (dl->sorted != dl->length) { + const unsigned refunded = dl->sorted - dl->length; + dl->sorted = dl->length; + txn->tw.loose_count -= refunded; + txn->tw.dirtyroom += refunded; + txn->mt_next_pgno -= refunded; + mdbx_tassert(txn, txn->mt_parent || + txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + + /* Filter-out loose chain & dispose refunded pages. */ + unlink_loose: + for (MDBX_page **link = &txn->tw.loose_pages; *link;) { + MDBX_page *dp = *link; + mdbx_tassert(txn, dp->mp_flags == (P_LOOSE | P_DIRTY)); + if (txn->mt_next_pgno > dp->mp_pgno) { + link = &dp->mp_next; + } else { + *link = dp->mp_next; + if ((txn->mt_flags & MDBX_WRITEMAP) == 0) + mdbx_dpage_free(txn->mt_env, dp, 1); + } + } + } + } + + mdbx_tassert(txn, mdbx_dirtylist_check(txn)); + mdbx_tassert(txn, txn->mt_parent || + txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + if (suitable != onstack) + mdbx_pnl_free(suitable); + txn->tw.loose_refund_wl = txn->mt_next_pgno; +} + +static bool mdbx_refund(MDBX_txn *txn) { + const pgno_t before = txn->mt_next_pgno; + + if (txn->tw.loose_pages && txn->tw.loose_refund_wl > txn->mt_next_pgno) + mdbx_refund_loose(txn); + + while (true) { + if (MDBX_PNL_SIZE(txn->tw.reclaimed_pglist) == 0 || + MDBX_PNL_MOST(txn->tw.reclaimed_pglist) != txn->mt_next_pgno - 1) + break; + + mdbx_refund_reclaimed(txn); + if (!txn->tw.loose_pages || txn->tw.loose_refund_wl <= txn->mt_next_pgno) + break; + + const pgno_t memo = txn->mt_next_pgno; + mdbx_refund_loose(txn); + if (memo == txn->mt_next_pgno) + break; + } + + return before != txn->mt_next_pgno; +} + +static __cold void mdbx_kill_page(MDBX_env *env, MDBX_page *mp, pgno_t pgno, + unsigned npages) { + mdbx_assert(env, pgno >= NUM_METAS && npages); + if (IS_DIRTY(mp) || (env->me_flags & MDBX_WRITEMAP)) { + const size_t bytes = pgno2bytes(env, npages); + memset(mp, 0, bytes); + mp->mp_pgno = pgno; + if ((env->me_flags & MDBX_WRITEMAP) == 0) + mdbx_pwrite(env->me_fd, mp, bytes, pgno2bytes(env, pgno)); + } else { + struct iovec iov[MDBX_COMMIT_PAGES]; + iov[0].iov_len = env->me_psize; + iov[0].iov_base = (char *)env->me_pbuf + env->me_psize; + size_t iov_off = pgno2bytes(env, pgno); + unsigned n = 1; + while (--npages) { + iov[n] = iov[0]; + if (++n == MDBX_COMMIT_PAGES) { + mdbx_pwritev(env->me_fd, iov, MDBX_COMMIT_PAGES, iov_off, + pgno2bytes(env, MDBX_COMMIT_PAGES)); + iov_off += pgno2bytes(env, MDBX_COMMIT_PAGES); + n = 0; + } + } + mdbx_pwritev(env->me_fd, iov, n, iov_off, pgno2bytes(env, n)); + } +} + +/* Retire, loosen or free a single page. + * + * Saves single pages to a list for future reuse + * in this same txn. It has been pulled from the GC + * and already resides on the dirty list, but has been + * deleted. Use these pages first before pulling again + * from the GC. + * + * If the page wasn't dirtied in this txn, just add it + * to this txn's free list. */ + +static __hot int mdbx_page_loose(MDBX_txn *txn, MDBX_page *mp) { + const unsigned npages = IS_OVERFLOW(mp) ? mp->mp_pages : 1; + const pgno_t pgno = mp->mp_pgno; + + if (txn->mt_parent) { + mdbx_tassert(txn, (txn->mt_env->me_flags & MDBX_WRITEMAP) == 0); + mdbx_tassert(txn, mp != pgno2page(txn->mt_env, pgno)); + /* If txn has a parent, make sure the page is in our dirty list. */ + MDBX_page *dp = mdbx_dpl_find(txn->tw.dirtylist, pgno); + /* TODO: use extended flag-mask to track parent's dirty-pages */ + if (dp == nullptr) { + mp->mp_next = txn->tw.retired2parent_pages; + txn->tw.retired2parent_pages = mp; + txn->tw.retired2parent_count += npages; + return MDBX_SUCCESS; + } + if (unlikely(mp != dp)) { /* bad cursor? */ + mdbx_error( + "wrong page 0x%p #%" PRIaPGNO " in the dirtylist, expecting %p", + __Wpedantic_format_voidptr(dp), pgno, __Wpedantic_format_voidptr(mp)); + txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PROBLEM; + } + /* ok, it's ours */ + } + + mdbx_debug("loosen page %" PRIaPGNO, pgno); + const bool is_dirty = IS_DIRTY(mp); + if (MDBX_DEBUG || unlikely((txn->mt_env->me_flags & MDBX_PAGEPERTURB) != 0)) { + mdbx_kill_page(txn->mt_env, mp, pgno, npages); + VALGRIND_MAKE_MEM_UNDEFINED(mp, PAGEHDRSZ); + } + VALGRIND_MAKE_MEM_NOACCESS(page_data(mp), + pgno2bytes(txn->mt_env, npages) - PAGEHDRSZ); + ASAN_POISON_MEMORY_REGION(page_data(mp), + pgno2bytes(txn->mt_env, npages) - PAGEHDRSZ); + + if (unlikely(npages > + 1 /* overflow pages doesn't comes to the loose-list */)) { + if (is_dirty) { + /* Remove from dirty list */ + MDBX_page *dp = mdbx_dpl_remove(txn->tw.dirtylist, pgno); + if (unlikely(dp != mp)) { + mdbx_error("not found page 0x%p #%" PRIaPGNO " in the dirtylist", + __Wpedantic_format_voidptr(mp), pgno); + txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PROBLEM; + } + txn->tw.dirtyroom++; + mdbx_tassert(txn, txn->mt_parent || + txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + if ((txn->mt_flags & MDBX_WRITEMAP) == 0) + mdbx_dpage_free(txn->mt_env, mp, npages); + } + + if (unlikely(pgno + npages == txn->mt_next_pgno)) { + txn->mt_next_pgno = pgno; + mdbx_refund(txn); + return MDBX_SUCCESS; + } + + int rc = mdbx_pnl_insert_range(&txn->tw.reclaimed_pglist, pgno, npages); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + return MDBX_SUCCESS; + } + + mp->mp_flags = P_LOOSE | P_DIRTY; + mp->mp_next = txn->tw.loose_pages; + txn->tw.loose_pages = mp; + txn->tw.loose_count++; + if (unlikely(txn->mt_next_pgno == mp->mp_pgno + 1)) + mdbx_refund(txn); + + return MDBX_SUCCESS; +} + +static __hot int mdbx_page_retire(MDBX_cursor *mc, MDBX_page *mp) { + const unsigned npages = IS_OVERFLOW(mp) ? mp->mp_pages : 1; + const pgno_t pgno = mp->mp_pgno; + MDBX_txn *const txn = mc->mc_txn; + + if (unlikely(mc->mc_flags & C_SUB)) { + MDBX_db *outer = mdbx_outer_db(mc); + mdbx_cassert(mc, !IS_BRANCH(mp) || outer->md_branch_pages > 0); + outer->md_branch_pages -= IS_BRANCH(mp); + mdbx_cassert(mc, !IS_LEAF(mp) || outer->md_leaf_pages > 0); + outer->md_leaf_pages -= IS_LEAF(mp); + mdbx_cassert(mc, !IS_OVERFLOW(mp)); + } + mdbx_cassert(mc, !IS_BRANCH(mp) || mc->mc_db->md_branch_pages > 0); + mc->mc_db->md_branch_pages -= IS_BRANCH(mp); + mdbx_cassert(mc, !IS_LEAF(mp) || mc->mc_db->md_leaf_pages > 0); + mc->mc_db->md_leaf_pages -= IS_LEAF(mp); + mdbx_cassert(mc, !IS_OVERFLOW(mp) || mc->mc_db->md_overflow_pages >= npages); + mc->mc_db->md_overflow_pages -= IS_OVERFLOW(mp) ? npages : 0; + + if (IS_DIRTY(mp)) { + int rc = mdbx_page_loose(txn, mp); + if (unlikely(rc != MDBX_SUCCESS)) + mc->mc_flags &= ~(C_INITIALIZED | C_EOF); + return rc; + } + + if (txn->tw.spill_pages) { + const unsigned i = mdbx_pnl_exist(txn->tw.spill_pages, pgno << 1); + if (i) { + /* This page is no longer spilled */ + mdbx_tassert(txn, i == MDBX_PNL_SIZE(txn->tw.spill_pages) || + txn->tw.spill_pages[i + 1] >= (pgno + npages) << 1); + txn->tw.spill_pages[i] |= 1; + if (i == MDBX_PNL_SIZE(txn->tw.spill_pages)) + MDBX_PNL_SIZE(txn->tw.spill_pages) -= 1; + int rc = mdbx_page_loose(txn, mp); + if (unlikely(rc != MDBX_SUCCESS)) + mc->mc_flags &= ~(C_INITIALIZED | C_EOF); + return rc; + } + } + + mdbx_tassert(txn, mp == pgno2page(txn->mt_env, pgno)); + int rc = mdbx_pnl_append_range(&txn->tw.retired_pages, pgno, npages); + mdbx_tassert(txn, mdbx_dpl_find(txn->tw.dirtylist, pgno) == nullptr); + return rc; +} + +static __must_check_result __inline int mdbx_retire_pgno(MDBX_cursor *mc, + const pgno_t pgno) { + MDBX_page *mp; + int rc = mdbx_page_get(mc, pgno, &mp, NULL); + if (likely(rc == MDBX_SUCCESS)) + rc = mdbx_page_retire(mc, mp); + return rc; +} + +/* Set or clear P_KEEP in dirty, non-overflow, non-sub pages watched by txn. + * + * [in] mc A cursor handle for the current operation. + * [in] pflags Flags of the pages to update: + * - P_DIRTY to set P_KEEP, + * - P_DIRTY|P_KEEP to clear it. + * [in] all No shortcuts. Needed except after a full mdbx_page_flush(). + * + * Returns 0 on success, non-zero on failure. */ +static int mdbx_pages_xkeep(MDBX_cursor *mc, unsigned pflags, bool all) { + const unsigned Mask = P_SUBP | P_DIRTY | P_LOOSE | P_KEEP; + MDBX_txn *txn = mc->mc_txn; + MDBX_cursor *m3, *m0 = mc; + MDBX_xcursor *mx; + MDBX_page *dp, *mp; + unsigned i, j; + int rc = MDBX_SUCCESS; + + /* Mark pages seen by cursors: First m0, then tracked cursors */ + for (i = txn->mt_numdbs;;) { + if (mc->mc_flags & C_INITIALIZED) { + for (m3 = mc;; m3 = &mx->mx_cursor) { + mp = NULL; + for (j = 0; j < m3->mc_snum; j++) { + mp = m3->mc_pg[j]; + if ((mp->mp_flags & Mask) == pflags) + mp->mp_flags ^= P_KEEP; + } + mx = m3->mc_xcursor; + /* Proceed to mx if it is at a sub-database */ + if (!(mx && (mx->mx_cursor.mc_flags & C_INITIALIZED))) + break; + if (!(mp && IS_LEAF(mp))) + break; + if (!(node_flags(page_node(mp, m3->mc_ki[j - 1])) & F_SUBDATA)) + break; + } + } + mc = mc->mc_next; + for (; !mc || mc == m0; mc = txn->mt_cursors[--i]) + if (i == 0) + goto mark_done; + } + +mark_done: + if (all) { + /* Mark dirty root pages */ + for (i = 0; i < txn->mt_numdbs; i++) { + if (txn->mt_dbflags[i] & DB_DIRTY) { + pgno_t pgno = txn->mt_dbs[i].md_root; + if (pgno == P_INVALID) + continue; + int level; + if (unlikely((rc = mdbx_page_get(m0, pgno, &dp, &level)) != + MDBX_SUCCESS)) + break; + if ((dp->mp_flags & Mask) == pflags && level <= 1) + dp->mp_flags ^= P_KEEP; + } + } + } + + return rc; +} + +/* Spill pages from the dirty list back to disk. + * This is intended to prevent running into MDBX_TXN_FULL situations, + * but note that they may still occur in a few cases: + * + * 1) our estimate of the txn size could be too small. Currently this + * seems unlikely, except with a large number of MDBX_MULTIPLE items. + * + * 2) child txns may run out of space if their parents dirtied a + * lot of pages and never spilled them. TODO: we probably should do + * a preemptive spill during mdbx_txn_begin() of a child txn, if + * the parent's dirtyroom is below a given threshold. + * + * Otherwise, if not using nested txns, it is expected that apps will + * not run into MDBX_TXN_FULL any more. The pages are flushed to disk + * the same way as for a txn commit, e.g. their P_DIRTY flag is cleared. + * If the txn never references them again, they can be left alone. + * If the txn only reads them, they can be used without any fuss. + * If the txn writes them again, they can be dirtied immediately without + * going thru all of the work of mdbx_page_touch(). Such references are + * handled by mdbx_page_unspill(). + * + * Also note, we never spill DB root pages, nor pages of active cursors, + * because we'll need these back again soon anyway. And in nested txns, + * we can't spill a page in a child txn if it was already spilled in a + * parent txn. That would alter the parent txns' data even though + * the child hasn't committed yet, and we'd have no way to undo it if + * the child aborted. + * + * [in] mc cursor A cursor handle identifying the transaction and + * database for which we are checking space. + * [in] key For a put operation, the key being stored. + * [in] data For a put operation, the data being stored. + * + * Returns 0 on success, non-zero on failure. */ +static int mdbx_page_spill(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { + if (mc->mc_flags & C_SUB) + return MDBX_SUCCESS; + + MDBX_txn *txn = mc->mc_txn; + MDBX_DPL dl = txn->tw.dirtylist; + + /* Estimate how much space this op will take */ + pgno_t i = mc->mc_db->md_depth; + /* Named DBs also dirty the main DB */ + if (mc->mc_dbi >= CORE_DBS) + i += txn->mt_dbs[MAIN_DBI].md_depth; + /* For puts, roughly factor in the key+data size */ + if (key) + i += bytes2pgno(txn->mt_env, node_size(key, data) + txn->mt_env->me_psize); + i += i; /* double it for good measure */ + pgno_t need = i; + + if (txn->tw.dirtyroom > i) + return MDBX_SUCCESS; + + if (!txn->tw.spill_pages) { + txn->tw.spill_pages = mdbx_pnl_alloc(MDBX_DPL_TXNFULL / 8); + if (unlikely(!txn->tw.spill_pages)) + return MDBX_ENOMEM; + } else { + /* purge deleted slots */ + MDBX_PNL sl = txn->tw.spill_pages; + pgno_t num = MDBX_PNL_SIZE(sl), j = 0; + for (i = 1; i <= num; i++) { + if ((sl[i] & 1) == 0) + sl[++j] = sl[i]; + } + MDBX_PNL_SIZE(sl) = j; + } + + /* Preserve pages which may soon be dirtied again */ + int rc = mdbx_pages_xkeep(mc, P_DIRTY, true); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + + /* Less aggressive spill - we originally spilled the entire dirty list, + * with a few exceptions for cursor pages and DB root pages. But this + * turns out to be a lot of wasted effort because in a large txn many + * of those pages will need to be used again. So now we spill only 1/8th + * of the dirty pages. Testing revealed this to be a good tradeoff, + * better than 1/2, 1/4, or 1/10. */ + if (need < MDBX_DPL_TXNFULL / 8) + need = MDBX_DPL_TXNFULL / 8; + + /* Save the page IDs of all the pages we're flushing */ + /* flush from the tail forward, this saves a lot of shifting later on. */ + for (i = dl->length; i && need; i--) { + pgno_t pn = dl[i].pgno << 1; + MDBX_page *dp = dl[i].ptr; + if (dp->mp_flags & (P_LOOSE | P_KEEP)) + continue; + /* Can't spill twice, + * make sure it's not already in a parent's spill list. */ + if (txn->mt_parent) { + MDBX_txn *parent; + for (parent = txn->mt_parent; parent; parent = parent->mt_parent) { + if (parent->tw.spill_pages && + mdbx_pnl_exist(parent->tw.spill_pages, pn)) { + dp->mp_flags |= P_KEEP; + break; + } + } + if (parent) + continue; + } + rc = mdbx_pnl_append(&txn->tw.spill_pages, pn); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + need--; + } + mdbx_pnl_sort(txn->tw.spill_pages); + + /* Flush the spilled part of dirty list */ + rc = mdbx_page_flush(txn, i); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + + /* Reset any dirty pages we kept that page_flush didn't see */ + rc = mdbx_pages_xkeep(mc, P_DIRTY | P_KEEP, i != 0); + +bailout: + txn->mt_flags |= rc ? MDBX_TXN_ERROR : MDBX_TXN_SPILLS; + return rc; +} + +/*----------------------------------------------------------------------------*/ + +#define METAPAGE(env, n) page_meta(pgno2page(env, n)) +#define METAPAGE_END(env) METAPAGE(env, NUM_METAS) + +static __inline txnid_t meta_txnid(const MDBX_env *env, const MDBX_meta *meta, + bool allow_volatile) { + mdbx_assert(env, meta >= METAPAGE(env, 0) || meta < METAPAGE_END(env)); + txnid_t a = safe64_read(&meta->mm_txnid_a); + txnid_t b = safe64_read(&meta->mm_txnid_b); + if (allow_volatile) + return (a == b) ? a : 0; + mdbx_assert(env, a == b); + return a; +} + +static __inline txnid_t mdbx_meta_txnid_stable(const MDBX_env *env, + const MDBX_meta *meta) { + return meta_txnid(env, meta, false); +} + +static __inline txnid_t mdbx_meta_txnid_fluid(const MDBX_env *env, + const MDBX_meta *meta) { + return meta_txnid(env, meta, true); +} + +static __inline void mdbx_meta_update_begin(const MDBX_env *env, + MDBX_meta *meta, txnid_t txnid) { + mdbx_assert(env, meta >= METAPAGE(env, 0) || meta < METAPAGE_END(env)); + mdbx_assert(env, meta->mm_txnid_a.inconsistent < txnid && + meta->mm_txnid_b.inconsistent < txnid); + (void)env; + safe64_update(&meta->mm_txnid_a, txnid); +} + +static __inline void mdbx_meta_update_end(const MDBX_env *env, MDBX_meta *meta, + txnid_t txnid) { + mdbx_assert(env, meta >= METAPAGE(env, 0) || meta < METAPAGE_END(env)); + mdbx_assert(env, meta->mm_txnid_a.inconsistent == txnid); + mdbx_assert(env, meta->mm_txnid_b.inconsistent < txnid); + (void)env; + mdbx_jitter4testing(true); + safe64_update(&meta->mm_txnid_b, txnid); +} + +static __inline void mdbx_meta_set_txnid(const MDBX_env *env, MDBX_meta *meta, + txnid_t txnid) { + mdbx_assert(env, meta < METAPAGE(env, 0) || meta > METAPAGE_END(env)); + (void)env; + /* update inconsistent since this function used ONLY for filling meta-image + * for writing, but not the actual meta-page */ + meta->mm_txnid_a.inconsistent = txnid; + meta->mm_txnid_b.inconsistent = txnid; +} + +static __inline uint64_t mdbx_meta_sign(const MDBX_meta *meta) { + uint64_t sign = MDBX_DATASIGN_NONE; +#if 0 /* TODO */ + sign = hippeus_hash64(...); +#else + (void)meta; +#endif + /* LY: newer returns MDBX_DATASIGN_NONE or MDBX_DATASIGN_WEAK */ + return (sign > MDBX_DATASIGN_WEAK) ? sign : ~sign; +} + +enum meta_choise_mode { prefer_last, prefer_noweak, prefer_steady }; + +static __inline bool mdbx_meta_ot(const enum meta_choise_mode mode, + const MDBX_env *env, const MDBX_meta *a, + const MDBX_meta *b) { + mdbx_jitter4testing(true); + txnid_t txnid_a = mdbx_meta_txnid_fluid(env, a); + txnid_t txnid_b = mdbx_meta_txnid_fluid(env, b); + + mdbx_jitter4testing(true); + switch (mode) { + default: + assert(false); + __unreachable(); + /* fall through */ + __fallthrough; + case prefer_steady: + if (META_IS_STEADY(a) != META_IS_STEADY(b)) + return META_IS_STEADY(b); + /* fall through */ + __fallthrough; + case prefer_noweak: + if (META_IS_WEAK(a) != META_IS_WEAK(b)) + return !META_IS_WEAK(b); + /* fall through */ + __fallthrough; + case prefer_last: + mdbx_jitter4testing(true); + if (txnid_a == txnid_b) + return META_IS_STEADY(b) || (META_IS_WEAK(a) && !META_IS_WEAK(b)); + return txnid_a < txnid_b; + } +} + +static __inline bool mdbx_meta_eq(const MDBX_env *env, const MDBX_meta *a, + const MDBX_meta *b) { + mdbx_jitter4testing(true); + const txnid_t txnid = mdbx_meta_txnid_fluid(env, a); + if (!txnid || txnid != mdbx_meta_txnid_fluid(env, b)) + return false; + + mdbx_jitter4testing(true); + if (META_IS_STEADY(a) != META_IS_STEADY(b)) + return false; + + mdbx_jitter4testing(true); + return true; +} + +static int mdbx_meta_eq_mask(const MDBX_env *env) { + MDBX_meta *m0 = METAPAGE(env, 0); + MDBX_meta *m1 = METAPAGE(env, 1); + MDBX_meta *m2 = METAPAGE(env, 2); + + int rc = mdbx_meta_eq(env, m0, m1) ? 1 : 0; + if (mdbx_meta_eq(env, m1, m2)) + rc += 2; + if (mdbx_meta_eq(env, m2, m0)) + rc += 4; + return rc; +} + +static __inline MDBX_meta *mdbx_meta_recent(const enum meta_choise_mode mode, + const MDBX_env *env, MDBX_meta *a, + MDBX_meta *b) { + const bool a_older_that_b = mdbx_meta_ot(mode, env, a, b); + mdbx_assert(env, !mdbx_meta_eq(env, a, b)); + return a_older_that_b ? b : a; +} + +static __inline MDBX_meta *mdbx_meta_ancient(const enum meta_choise_mode mode, + const MDBX_env *env, MDBX_meta *a, + MDBX_meta *b) { + const bool a_older_that_b = mdbx_meta_ot(mode, env, a, b); + mdbx_assert(env, !mdbx_meta_eq(env, a, b)); + return a_older_that_b ? a : b; +} + +static __inline MDBX_meta * +mdbx_meta_mostrecent(const enum meta_choise_mode mode, const MDBX_env *env) { + MDBX_meta *m0 = METAPAGE(env, 0); + MDBX_meta *m1 = METAPAGE(env, 1); + MDBX_meta *m2 = METAPAGE(env, 2); + + MDBX_meta *head = mdbx_meta_recent(mode, env, m0, m1); + head = mdbx_meta_recent(mode, env, head, m2); + return head; +} + +static __hot MDBX_meta *mdbx_meta_steady(const MDBX_env *env) { + return mdbx_meta_mostrecent(prefer_steady, env); +} + +static __hot MDBX_meta *mdbx_meta_head(const MDBX_env *env) { + return mdbx_meta_mostrecent(prefer_last, env); +} + +static __hot txnid_t mdbx_recent_committed_txnid(const MDBX_env *env) { + while (true) { + const MDBX_meta *head = mdbx_meta_head(env); + const txnid_t recent = mdbx_meta_txnid_fluid(env, head); + mdbx_compiler_barrier(); + if (likely(head == mdbx_meta_head(env) && + recent == mdbx_meta_txnid_fluid(env, head))) + return recent; + } +} + +static __hot txnid_t mdbx_recent_steady_txnid(const MDBX_env *env) { + while (true) { + const MDBX_meta *head = mdbx_meta_steady(env); + const txnid_t recent = mdbx_meta_txnid_fluid(env, head); + mdbx_compiler_barrier(); + if (likely(head == mdbx_meta_steady(env) && + recent == mdbx_meta_txnid_fluid(env, head))) + return recent; + } +} + +static __hot txnid_t mdbx_reclaiming_detent(const MDBX_env *env) { + if (F_ISSET(env->me_flags, MDBX_UTTERLY_NOSYNC)) + return likely(env->me_txn0->mt_owner == mdbx_thread_self()) + ? env->me_txn0->mt_txnid - MDBX_TXNID_STEP + : mdbx_recent_committed_txnid(env); + + return mdbx_recent_steady_txnid(env); +} + +static const char *mdbx_durable_str(const MDBX_meta *const meta) { + if (META_IS_WEAK(meta)) + return "Weak"; + if (META_IS_STEADY(meta)) + return (meta->mm_datasync_sign == mdbx_meta_sign(meta)) ? "Steady" + : "Tainted"; + return "Legacy"; +} + +/*----------------------------------------------------------------------------*/ + +/* Find oldest txnid still referenced. */ +static txnid_t mdbx_find_oldest(MDBX_txn *txn) { + mdbx_tassert(txn, (txn->mt_flags & MDBX_RDONLY) == 0); + MDBX_env *env = txn->mt_env; + const txnid_t edge = mdbx_reclaiming_detent(env); + mdbx_tassert(txn, edge <= txn->mt_txnid); + + MDBX_lockinfo *const lck = env->me_lck; + if (unlikely(lck == NULL /* exclusive mode */)) + return env->me_lckless_stub.oldest = edge; + + const txnid_t last_oldest = lck->mti_oldest_reader; + mdbx_tassert(txn, edge >= last_oldest); + if (likely(last_oldest == edge)) + return edge; + + const uint32_t nothing_changed = MDBX_STRING_TETRAD("None"); + const uint32_t snap_readers_refresh_flag = lck->mti_readers_refresh_flag; + mdbx_jitter4testing(false); + if (snap_readers_refresh_flag == nothing_changed) + return last_oldest; + + txnid_t oldest = edge; + lck->mti_readers_refresh_flag = nothing_changed; + mdbx_flush_noncoherent_cpu_writeback(); + const unsigned snap_nreaders = lck->mti_numreaders; + for (unsigned i = 0; i < snap_nreaders; ++i) { + if (lck->mti_readers[i].mr_pid) { + /* mdbx_jitter4testing(true); */ + const txnid_t snap = safe64_read(&lck->mti_readers[i].mr_txnid); + if (oldest > snap && last_oldest <= /* ignore pending updates */ snap) { + oldest = snap; + if (oldest == last_oldest) + return oldest; + } + } + } + + if (oldest != last_oldest) { + mdbx_notice("update oldest %" PRIaTXN " -> %" PRIaTXN, last_oldest, oldest); + mdbx_tassert(txn, oldest >= lck->mti_oldest_reader); + lck->mti_oldest_reader = oldest; + } + return oldest; +} + +/* Find largest mvcc-snapshot still referenced. */ +static __cold pgno_t mdbx_find_largest(MDBX_env *env, pgno_t largest) { + MDBX_lockinfo *const lck = env->me_lck; + if (likely(lck != NULL /* exclusive mode */)) { + const unsigned snap_nreaders = lck->mti_numreaders; + for (unsigned i = 0; i < snap_nreaders; ++i) { + retry: + if (lck->mti_readers[i].mr_pid) { + /* mdbx_jitter4testing(true); */ + const pgno_t snap_pages = lck->mti_readers[i].mr_snapshot_pages_used; + const txnid_t snap_txnid = safe64_read(&lck->mti_readers[i].mr_txnid); + mdbx_memory_barrier(); + if (unlikely(snap_pages != lck->mti_readers[i].mr_snapshot_pages_used || + snap_txnid != safe64_read(&lck->mti_readers[i].mr_txnid))) + goto retry; + if (largest < snap_pages && + lck->mti_oldest_reader <= /* ignore pending updates */ snap_txnid && + snap_txnid <= env->me_txn0->mt_txnid) + largest = snap_pages; + } + } + } + + return largest; +} + +/* Add a page to the txn's dirty list */ +static int __must_check_result mdbx_page_dirty(MDBX_txn *txn, MDBX_page *mp) { + const int rc = mdbx_dpl_append(txn->tw.dirtylist, mp->mp_pgno, mp); + if (unlikely(rc != MDBX_SUCCESS)) { + txn->mt_flags |= MDBX_TXN_ERROR; + return rc; + } + txn->tw.dirtyroom--; + mdbx_tassert(txn, txn->mt_parent || + txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + return MDBX_SUCCESS; +} + +/* Turn on/off readahead. It's harmful when the DB is larger than RAM. */ +static int __cold mdbx_set_readahead(MDBX_env *env, const size_t offset, + const size_t length, const bool enable) { + assert(length > 0); + mdbx_notice("readahead %s %u..%u", enable ? "ON" : "OFF", + bytes2pgno(env, offset), bytes2pgno(env, offset + length)); + +#if defined(F_RDAHEAD) + if (unlikely(fcntl(env->me_fd, F_RDAHEAD, enable) == -1)) + return errno; +#endif /* F_RDAHEAD */ + + if (enable) { +#if defined(F_RDADVISE) + struct radvisory hint; + hint.ra_offset = offset; + hint.ra_count = length; + (void)/* Ignore ENOTTY for DB on the ram-disk and so on */ fcntl( + env->me_fd, F_RDADVISE, &hint); +#endif /* F_RDADVISE */ +#if defined(MADV_WILLNEED) + if (unlikely(madvise(env->me_map + offset, length, MADV_WILLNEED) != 0)) + return errno; +#elif defined(POSIX_MADV_WILLNEED) + rc = posix_madvise(env->me_map + offset, length, POSIX_MADV_WILLNEED); + if (unlikely(rc != 0)) + return errno; +#elif defined(_WIN32) || defined(_WIN64) + if (mdbx_PrefetchVirtualMemory) { + WIN32_MEMORY_RANGE_ENTRY hint; + hint.VirtualAddress = env->me_map + offset; + hint.NumberOfBytes = length; + (void)mdbx_PrefetchVirtualMemory(GetCurrentProcess(), 1, &hint, 0); + } +#elif defined(POSIX_FADV_WILLNEED) + int err = posix_fadvise(env->me_fd, offset, length, POSIX_FADV_WILLNEED); + if (unlikely(err != 0)) + return err; +#endif /* MADV_WILLNEED */ + } else { +#if defined(MADV_RANDOM) + if (unlikely(madvise(env->me_map + offset, length, MADV_RANDOM) != 0)) + return errno; +#elif defined(POSIX_MADV_RANDOM) + int err = posix_madvise(env->me_map + offset, length, POSIX_MADV_RANDOM); + if (unlikely(err != 0)) + return err; +#elif defined(POSIX_FADV_RANDOM) + int err = posix_fadvise(env->me_fd, offset, length, POSIX_FADV_RANDOM); + if (unlikely(err != 0)) + return err; +#endif /* MADV_RANDOM */ + } + return MDBX_SUCCESS; +} + +static __cold int mdbx_mapresize(MDBX_env *env, const pgno_t size_pgno, + const pgno_t limit_pgno) { + const size_t limit_bytes = pgno_align2os_bytes(env, limit_pgno); + const size_t size_bytes = pgno_align2os_bytes(env, size_pgno); + + mdbx_verbose("resize datafile/mapping: " + "present %" PRIuPTR " -> %" PRIuPTR ", " + "limit %" PRIuPTR " -> %" PRIuPTR, + env->me_dxb_mmap.current, size_bytes, env->me_dxb_mmap.limit, + limit_bytes); + + mdbx_assert(env, limit_bytes >= size_bytes); + mdbx_assert(env, bytes2pgno(env, size_bytes) >= size_pgno); + mdbx_assert(env, bytes2pgno(env, limit_bytes) >= limit_pgno); + +#if defined(_WIN32) || defined(_WIN64) + /* Acquire guard in exclusive mode for: + * - to avoid collision between read and write txns around env->me_dbgeo; + * - to avoid attachment of new reading threads (see mdbx_rdt_lock); */ + mdbx_srwlock_AcquireExclusive(&env->me_remap_guard); + mdbx_handle_array_t *suspended = NULL; + mdbx_handle_array_t array_onstack; + int rc = MDBX_SUCCESS; + if (limit_bytes == env->me_dxb_mmap.limit && + size_bytes == env->me_dxb_mmap.current && + size_bytes == env->me_dxb_mmap.filesize) + goto bailout; + + /* 1) Windows allows only extending a read-write section, but not a + * corresponing mapped view. Therefore in other cases we must suspend + * the local threads for safe remap. + * 2) At least on Windows 10 1803 the entire mapped section is unavailable + * for short time during NtExtendSection() or VirtualAlloc() execution. + * + * THEREFORE LOCAL THREADS SUSPENDING IS ALWAYS REQUIRED! */ + array_onstack.limit = ARRAY_LENGTH(array_onstack.handles); + array_onstack.count = 0; + suspended = &array_onstack; + rc = mdbx_suspend_threads_before_remap(env, &suspended); + if (rc != MDBX_SUCCESS) { + mdbx_error("failed suspend-for-remap: errcode %d", rc); + goto bailout; + } +#else + /* Acquire guard to avoid collision between read and write txns + * around env->me_dbgeo */ + int rc = mdbx_fastmutex_acquire(&env->me_remap_guard); + if (rc != MDBX_SUCCESS) + return rc; + if (limit_bytes == env->me_dxb_mmap.limit && + size_bytes == env->me_dxb_mmap.current) + goto bailout; +#endif /* Windows */ + + const size_t prev_limit = env->me_dxb_mmap.limit; + const void *const prev_addr = env->me_map; + const size_t prev_size = env->me_dxb_mmap.current; + if (size_bytes < prev_size) { + mdbx_notice("resize-MADV_%s %u..%u", + (env->me_flags & MDBX_WRITEMAP) ? "REMOVE" : "DONTNEED", + size_pgno, bytes2pgno(env, prev_size)); +#if defined(MADV_REMOVE) + if ((env->me_flags & MDBX_WRITEMAP) == 0 || + madvise(env->me_map + size_bytes, prev_size - size_bytes, + MADV_REMOVE) != 0) +#endif +#if defined(MADV_DONTNEED) + (void)madvise(env->me_map + size_bytes, prev_size - size_bytes, + MADV_DONTNEED); +#elif defined(POSIX_MADV_DONTNEED) + (void)posix_madvise(env->me_map + size_bytes, prev_size - size_bytes, + POSIX_MADV_DONTNEED); +#elif defined(POSIX_FADV_DONTNEED) + (void)posix_fadvise(env->me_fd, size_bytes, prev_size - size_bytes, + POSIX_FADV_DONTNEED); +#else + __noop(); +#endif /* MADV_DONTNEED */ + if (*env->me_discarded_tail > size_pgno) + *env->me_discarded_tail = size_pgno; + } + + rc = mdbx_mresize(env->me_flags, &env->me_dxb_mmap, size_bytes, limit_bytes); + if (rc == MDBX_SUCCESS && (env->me_flags & MDBX_NORDAHEAD) == 0) { + rc = mdbx_is_readahead_reasonable(size_bytes, 0); + if (rc == MDBX_RESULT_FALSE) + rc = mdbx_set_readahead( + env, 0, (size_bytes > prev_size) ? size_bytes : prev_size, false); + else if (rc == MDBX_RESULT_TRUE) { + rc = MDBX_SUCCESS; + const size_t readahead_pivot = + (limit_bytes != prev_limit || env->me_dxb_mmap.address != prev_addr +#if defined(_WIN32) || defined(_WIN64) + || prev_size > size_bytes +#endif /* Windows */ + ) + ? 0 /* reassign readahead to the entire map + because it was remapped */ + : prev_size; + if (size_bytes > readahead_pivot) { + *env->me_discarded_tail = size_pgno; + rc = mdbx_set_readahead(env, readahead_pivot, + size_bytes - readahead_pivot, true); + } + } + } + +bailout: + if (rc == MDBX_SUCCESS) { +#if defined(_WIN32) || defined(_WIN64) + mdbx_assert(env, size_bytes == env->me_dxb_mmap.current); + mdbx_assert(env, size_bytes <= env->me_dxb_mmap.filesize); + mdbx_assert(env, limit_bytes == env->me_dxb_mmap.limit); +#endif /* Windows */ +#ifdef MDBX_USE_VALGRIND + if (prev_limit != env->me_dxb_mmap.limit || prev_addr != env->me_map) { + VALGRIND_DISCARD(env->me_valgrind_handle); + env->me_valgrind_handle = 0; + if (env->me_dxb_mmap.limit) + env->me_valgrind_handle = + VALGRIND_CREATE_BLOCK(env->me_map, env->me_dxb_mmap.limit, "mdbx"); + } +#endif /* MDBX_USE_VALGRIND */ + } else { + if (rc != MDBX_RESULT_TRUE) { + mdbx_error("failed resize datafile/mapping: " + "present %" PRIuPTR " -> %" PRIuPTR ", " + "limit %" PRIuPTR " -> %" PRIuPTR ", errcode %d", + env->me_dxb_mmap.current, size_bytes, env->me_dxb_mmap.limit, + limit_bytes, rc); + } else { + mdbx_notice("unable resize datafile/mapping: " + "present %" PRIuPTR " -> %" PRIuPTR ", " + "limit %" PRIuPTR " -> %" PRIuPTR ", errcode %d", + env->me_dxb_mmap.current, size_bytes, env->me_dxb_mmap.limit, + limit_bytes, rc); + } + if (!env->me_dxb_mmap.address) { + env->me_flags |= MDBX_FATAL_ERROR; + if (env->me_txn) + env->me_txn->mt_flags |= MDBX_TXN_ERROR; + rc = MDBX_PANIC; + } + } + +#if defined(_WIN32) || defined(_WIN64) + int err = MDBX_SUCCESS; + mdbx_srwlock_ReleaseExclusive(&env->me_remap_guard); + if (suspended) { + err = mdbx_resume_threads_after_remap(suspended); + if (suspended != &array_onstack) + mdbx_free(suspended); + } +#else + int err = mdbx_fastmutex_release(&env->me_remap_guard); +#endif /* Windows */ + if (err != MDBX_SUCCESS) { + mdbx_fatal("failed resume-after-remap: errcode %d", err); + return MDBX_PANIC; + } + return rc; +} + +/* Allocate page numbers and memory for writing. Maintain mt_last_reclaimed, + * mt_reclaimed_pglist and mt_next_pgno. Set MDBX_TXN_ERROR on failure. + * + * If there are free pages available from older transactions, they + * are re-used first. Otherwise allocate a new page at mt_next_pgno. + * Do not modify the GC, just merge GC records into mt_reclaimed_pglist + * and move mt_last_reclaimed to say which records were consumed. Only this + * function can create mt_reclaimed_pglist and move + * mt_last_reclaimed/mt_next_pgno. + * + * [in] mc cursor A cursor handle identifying the transaction and + * database for which we are allocating. + * [in] num the number of pages to allocate. + * [out] mp Address of the allocated page(s). Requests for multiple pages + * will always be satisfied by a single contiguous chunk of memory. + * + * Returns 0 on success, non-zero on failure.*/ + +#define MDBX_ALLOC_CACHE 1 +#define MDBX_ALLOC_GC 2 +#define MDBX_ALLOC_NEW 4 +#define MDBX_ALLOC_KICK 8 +#define MDBX_ALLOC_ALL \ + (MDBX_ALLOC_CACHE | MDBX_ALLOC_GC | MDBX_ALLOC_NEW | MDBX_ALLOC_KICK) + +static int mdbx_page_alloc(MDBX_cursor *mc, unsigned num, MDBX_page **mp, + int flags) { + int rc; + MDBX_txn *txn = mc->mc_txn; + MDBX_env *env = txn->mt_env; + MDBX_page *np; + + if (likely(flags & MDBX_ALLOC_GC)) { + flags |= env->me_flags & (MDBX_COALESCE | MDBX_LIFORECLAIM); + if (unlikely(mc->mc_flags & C_RECLAIMING)) { + /* If mc is updating the GC, then the retired-list cannot play + * catch-up with itself by growing while trying to save it. */ + flags &= + ~(MDBX_ALLOC_GC | MDBX_ALLOC_KICK | MDBX_COALESCE | MDBX_LIFORECLAIM); + } else if (unlikely(txn->mt_dbs[FREE_DBI].md_entries == 0)) { + /* avoid (recursive) search inside empty tree and while tree is updating, + * https://github.com/leo-yuriev/libmdbx/issues/31 */ + flags &= ~MDBX_ALLOC_GC; + } + } + + if (likely(flags & MDBX_ALLOC_CACHE)) { + /* If there are any loose pages, just use them */ + mdbx_assert(env, mp && num); + if (likely(num == 1 && txn->tw.loose_pages)) { + if (txn->tw.loose_refund_wl > txn->mt_next_pgno) { + mdbx_refund(txn); + if (unlikely(!txn->tw.loose_pages)) + goto skip_cache; + } + + np = txn->tw.loose_pages; + txn->tw.loose_pages = np->mp_next; + txn->tw.loose_count--; + mdbx_debug("db %d use loose page %" PRIaPGNO, DDBI(mc), np->mp_pgno); + mdbx_tassert(txn, np->mp_pgno < txn->mt_next_pgno); + mdbx_ensure(env, np->mp_pgno >= NUM_METAS); + VALGRIND_MAKE_MEM_UNDEFINED(page_data(np), page_space(txn->mt_env)); + ASAN_UNPOISON_MEMORY_REGION(page_data(np), page_space(txn->mt_env)); + *mp = np; + return MDBX_SUCCESS; + } + } +skip_cache: + + mdbx_tassert( + txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, txn->mt_next_pgno)); + pgno_t pgno, *re_list = txn->tw.reclaimed_pglist; + unsigned range_begin = 0, re_len = MDBX_PNL_SIZE(re_list); + txnid_t oldest = 0, last = 0; + const unsigned wanna_range = num - 1; + + while (1) { /* oom-kick retry loop */ + /* If our dirty list is already full, we can't do anything */ + if (unlikely(txn->tw.dirtyroom == 0)) { + rc = MDBX_TXN_FULL; + goto fail; + } + + MDBX_cursor recur; + for (MDBX_cursor_op op = MDBX_FIRST;; + op = (flags & MDBX_LIFORECLAIM) ? MDBX_PREV : MDBX_NEXT) { + MDBX_val key, data; + + /* Seek a big enough contiguous page range. + * Prefer pages with lower pgno. */ + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + if (likely(flags & MDBX_ALLOC_CACHE) && re_len > wanna_range && + (!(flags & MDBX_COALESCE) || op == MDBX_FIRST)) { + mdbx_tassert(txn, MDBX_PNL_LAST(re_list) < txn->mt_next_pgno && + MDBX_PNL_FIRST(re_list) < txn->mt_next_pgno); + range_begin = MDBX_PNL_ASCENDING ? 1 : re_len; + pgno = MDBX_PNL_LEAST(re_list); + if (likely(wanna_range == 0)) + goto done; +#if MDBX_PNL_ASCENDING + mdbx_tassert(txn, pgno == re_list[1] && range_begin == 1); + while (true) { + unsigned range_end = range_begin + wanna_range; + if (re_list[range_end] - pgno == wanna_range) + goto done; + if (range_end == re_len) + break; + pgno = re_list[++range_begin]; + } +#else + mdbx_tassert(txn, pgno == re_list[re_len] && range_begin == re_len); + while (true) { + if (re_list[range_begin - wanna_range] - pgno == wanna_range) + goto done; + if (range_begin == wanna_range) + break; + pgno = re_list[--range_begin]; + } +#endif /* MDBX_PNL sort-order */ + } + + if (op == MDBX_FIRST) { /* 1st iteration, setup cursor, etc */ + if (unlikely(!(flags & MDBX_ALLOC_GC))) + break /* reclaiming is prohibited for now */; + + /* Prepare to fetch more and coalesce */ + oldest = (flags & MDBX_LIFORECLAIM) ? mdbx_find_oldest(txn) + : *env->me_oldest; + rc = mdbx_cursor_init(&recur, txn, FREE_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + if (flags & MDBX_LIFORECLAIM) { + /* Begin from oldest reader if any */ + if (oldest > 2) { + last = oldest - 1; + op = MDBX_SET_RANGE; + } + } else if (txn->tw.last_reclaimed) { + /* Continue lookup from txn->tw.last_reclaimed to oldest reader */ + last = txn->tw.last_reclaimed; + op = MDBX_SET_RANGE; + } + + key.iov_base = &last; + key.iov_len = sizeof(last); + } + + if (!(flags & MDBX_LIFORECLAIM)) { + /* Do not try fetch more if the record will be too recent */ + if (op != MDBX_FIRST && ++last >= oldest) { + oldest = mdbx_find_oldest(txn); + if (oldest <= last) + break; + } + } + + rc = mdbx_cursor_get(&recur, &key, NULL, op); + if (rc == MDBX_NOTFOUND && (flags & MDBX_LIFORECLAIM)) { + if (op == MDBX_SET_RANGE) + continue; + txnid_t snap = mdbx_find_oldest(txn); + if (oldest < snap) { + oldest = snap; + last = oldest - 1; + key.iov_base = &last; + key.iov_len = sizeof(last); + op = MDBX_SET_RANGE; + rc = mdbx_cursor_get(&recur, &key, NULL, op); + } + } + if (unlikely(rc)) { + if (rc == MDBX_NOTFOUND) + break; + goto fail; + } + + if (unlikely(key.iov_len != sizeof(txnid_t))) { + rc = MDBX_CORRUPTED; + goto fail; + } + last = unaligned_peek_u64(4, key.iov_base); + if (unlikely(last < 1 || last >= SAFE64_INVALID_THRESHOLD)) { + rc = MDBX_CORRUPTED; + goto fail; + } + if (oldest <= last) { + oldest = mdbx_find_oldest(txn); + if (oldest <= last) { + if (flags & MDBX_LIFORECLAIM) + continue; + break; + } + } + + if (flags & MDBX_LIFORECLAIM) { + /* skip IDs of records that already reclaimed */ + if (txn->tw.lifo_reclaimed) { + unsigned i; + for (i = (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed); i > 0; --i) + if (txn->tw.lifo_reclaimed[i] == last) + break; + if (i) + continue; + } + } + + /* Reading next GC record */ + np = recur.mc_pg[recur.mc_top]; + if (unlikely((rc = mdbx_node_read( + &recur, page_node(np, recur.mc_ki[recur.mc_top]), + &data)) != MDBX_SUCCESS)) + goto fail; + + if ((flags & MDBX_LIFORECLAIM) && !txn->tw.lifo_reclaimed) { + txn->tw.lifo_reclaimed = mdbx_txl_alloc(); + if (unlikely(!txn->tw.lifo_reclaimed)) { + rc = MDBX_ENOMEM; + goto fail; + } + } + + /* Append PNL from GC record to me_reclaimed_pglist */ + mdbx_cassert(mc, (mc->mc_flags & C_GCFREEZE) == 0); + pgno_t *gc_pnl = (pgno_t *)data.iov_base; + mdbx_tassert(txn, data.iov_len >= MDBX_PNL_SIZEOF(gc_pnl)); + if (unlikely(data.iov_len < MDBX_PNL_SIZEOF(gc_pnl) || + !mdbx_pnl_check(gc_pnl, txn->mt_next_pgno))) { + rc = MDBX_CORRUPTED; + goto fail; + } + const unsigned gc_len = MDBX_PNL_SIZE(gc_pnl); + rc = mdbx_pnl_need(&txn->tw.reclaimed_pglist, gc_len); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + re_list = txn->tw.reclaimed_pglist; + + /* Remember ID of GC record */ + if (flags & MDBX_LIFORECLAIM) { + if ((rc = mdbx_txl_append(&txn->tw.lifo_reclaimed, last)) != 0) + goto fail; + } + txn->tw.last_reclaimed = last; + + if (mdbx_log_enabled(MDBX_LOG_EXTRA)) { + mdbx_debug_extra("PNL read txn %" PRIaTXN " root %" PRIaPGNO + " num %u, PNL", + last, txn->mt_dbs[FREE_DBI].md_root, gc_len); + unsigned i; + for (i = gc_len; i; i--) + mdbx_debug_extra_print(" %" PRIaPGNO, gc_pnl[i]); + mdbx_debug_extra_print("%s", "\n"); + } + + /* Merge in descending sorted order */ + const unsigned prev_re_len = MDBX_PNL_SIZE(re_list); + mdbx_pnl_xmerge(re_list, gc_pnl); + /* re-check to avoid duplicates */ + if (unlikely(!mdbx_pnl_check(re_list, txn->mt_next_pgno))) { + rc = MDBX_CORRUPTED; + goto fail; + } + + re_len = MDBX_PNL_SIZE(re_list); + mdbx_tassert(txn, re_len == 0 || re_list[re_len] < txn->mt_next_pgno); + if (re_len && unlikely(MDBX_PNL_MOST(re_list) == txn->mt_next_pgno - 1)) { + /* Refund suitable pages into "unallocated" space */ + mdbx_refund(txn); + re_list = txn->tw.reclaimed_pglist; + re_len = MDBX_PNL_SIZE(re_list); + } + + if (unlikely((flags & MDBX_ALLOC_CACHE) == 0)) { + /* Done for a kick-reclaim mode, actually no page needed */ + return MDBX_SUCCESS; + } + + /* Don't try to coalesce too much. */ + if (unlikely(re_len > MDBX_DPL_TXNFULL / 4)) + break; + if (re_len /* current size */ >= env->me_maxgc_ov1page || + (re_len > prev_re_len && re_len - prev_re_len /* delta from prev */ >= + env->me_maxgc_ov1page / 2)) + flags &= ~MDBX_COALESCE; + } + + if ((flags & (MDBX_COALESCE | MDBX_ALLOC_CACHE)) == + (MDBX_COALESCE | MDBX_ALLOC_CACHE) && + re_len > wanna_range) { + range_begin = MDBX_PNL_ASCENDING ? 1 : re_len; + pgno = MDBX_PNL_LEAST(re_list); + if (likely(wanna_range == 0)) + goto done; +#if MDBX_PNL_ASCENDING + mdbx_tassert(txn, pgno == re_list[1] && range_begin == 1); + while (true) { + unsigned range_end = range_begin + wanna_range; + if (re_list[range_end] - pgno == wanna_range) + goto done; + if (range_end == re_len) + break; + pgno = re_list[++range_begin]; + } +#else + mdbx_tassert(txn, pgno == re_list[re_len] && range_begin == re_len); + while (true) { + if (re_list[range_begin - wanna_range] - pgno == wanna_range) + goto done; + if (range_begin == wanna_range) + break; + pgno = re_list[--range_begin]; + } +#endif /* MDBX_PNL sort-order */ + } + + /* Use new pages from the map when nothing suitable in the GC */ + range_begin = 0; + pgno = txn->mt_next_pgno; + rc = MDBX_MAP_FULL; + const pgno_t next = pgno_add(pgno, num); + if (likely(next <= txn->mt_end_pgno)) { + rc = MDBX_NOTFOUND; + if (likely(flags & MDBX_ALLOC_NEW)) + goto done; + } + + const MDBX_meta *head = mdbx_meta_head(env); + if ((flags & MDBX_ALLOC_GC) && + ((flags & MDBX_ALLOC_KICK) || rc == MDBX_MAP_FULL)) { + MDBX_meta *steady = mdbx_meta_steady(env); + + if (oldest == mdbx_meta_txnid_stable(env, steady) && + !META_IS_STEADY(head) && META_IS_STEADY(steady)) { + /* LY: Here an oom was happened: + * - all pages had allocated; + * - reclaiming was stopped at the last steady-sync; + * - the head-sync is weak. + * Now we need make a sync to resume reclaiming. If both + * MDBX_NOSYNC and MDBX_MAPASYNC flags are set, then assume that + * utterly no-sync write mode was requested. In such case + * don't make a steady-sync, but only a legacy-mode checkpoint, + * just for resume reclaiming only, not for data consistency. */ + + mdbx_debug("kick-gc: head %" PRIaTXN "-%s, tail %" PRIaTXN + "-%s, oldest %" PRIaTXN, + mdbx_meta_txnid_stable(env, head), mdbx_durable_str(head), + mdbx_meta_txnid_stable(env, steady), + mdbx_durable_str(steady), oldest); + + const unsigned syncflags = F_ISSET(env->me_flags, MDBX_UTTERLY_NOSYNC) + ? env->me_flags + : env->me_flags & MDBX_WRITEMAP; + MDBX_meta meta = *head; + if (mdbx_sync_locked(env, syncflags, &meta) == MDBX_SUCCESS) { + txnid_t snap = mdbx_find_oldest(txn); + if (snap > oldest) + continue; + } + } + + if (rc == MDBX_MAP_FULL && oldest < txn->mt_txnid - MDBX_TXNID_STEP) { + if (mdbx_oomkick(env, oldest) > oldest) + continue; + } + } + + if (rc == MDBX_MAP_FULL && next < head->mm_geo.upper) { + mdbx_assert(env, next > txn->mt_end_pgno); + pgno_t aligned = pgno_align2os_pgno( + env, pgno_add(next, head->mm_geo.grow - next % head->mm_geo.grow)); + + if (aligned > head->mm_geo.upper) + aligned = head->mm_geo.upper; + mdbx_assert(env, aligned > txn->mt_end_pgno); + + mdbx_verbose("try growth datafile to %" PRIaPGNO " pages (+%" PRIaPGNO + ")", + aligned, aligned - txn->mt_end_pgno); + rc = mdbx_mapresize(env, aligned, head->mm_geo.upper); + if (rc == MDBX_SUCCESS) { + env->me_txn->mt_end_pgno = aligned; + if (!mp) + return rc; + goto done; + } + + mdbx_warning("unable growth datafile to %" PRIaPGNO "pages (+%" PRIaPGNO + "), errcode %d", + aligned, aligned - txn->mt_end_pgno, rc); + } + + fail: + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + if (mp) { + *mp = NULL; + txn->mt_flags |= MDBX_TXN_ERROR; + } + mdbx_assert(env, rc != MDBX_SUCCESS); + return rc; + } + +done: + mdbx_tassert(txn, mp && num); + mdbx_ensure(env, pgno >= NUM_METAS); + if (env->me_flags & MDBX_WRITEMAP) { + np = pgno2page(env, pgno); + /* LY: reset no-access flag from mdbx_loose_page() */ + VALGRIND_MAKE_MEM_UNDEFINED(np, pgno2bytes(env, num)); + ASAN_UNPOISON_MEMORY_REGION(np, pgno2bytes(env, num)); + } else { + if (unlikely(!(np = mdbx_page_malloc(txn, num)))) { + rc = MDBX_ENOMEM; + goto fail; + } + } + + if (range_begin) { + mdbx_cassert(mc, (mc->mc_flags & C_GCFREEZE) == 0); + mdbx_tassert(txn, pgno < txn->mt_next_pgno); + mdbx_tassert(txn, pgno == re_list[range_begin]); + /* Cutoff allocated pages from me_reclaimed_pglist */ +#if MDBX_PNL_ASCENDING + for (unsigned i = range_begin + num; i <= re_len;) + re_list[range_begin++] = re_list[i++]; + MDBX_PNL_SIZE(re_list) = re_len = range_begin - 1; +#else + MDBX_PNL_SIZE(re_list) = re_len -= num; + for (unsigned i = range_begin - num; i < re_len;) + re_list[++i] = re_list[++range_begin]; +#endif + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + } else { + txn->mt_next_pgno = pgno + num; + mdbx_assert(env, txn->mt_next_pgno <= txn->mt_end_pgno); + } + + if (unlikely(env->me_flags & MDBX_PAGEPERTURB)) + memset(np, -1, pgno2bytes(env, num)); + VALGRIND_MAKE_MEM_UNDEFINED(np, pgno2bytes(env, num)); + + np->mp_pgno = pgno; + np->mp_leaf2_ksize = 0; + np->mp_flags = 0; + np->mp_pages = num; + rc = mdbx_page_dirty(txn, np); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + *mp = np; + + mdbx_tassert( + txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, txn->mt_next_pgno)); + return MDBX_SUCCESS; +} + +/* Copy the used portions of a non-overflow page. + * [in] dst page to copy into + * [in] src page to copy from + * [in] psize size of a page */ +__hot static void mdbx_page_copy(MDBX_page *dst, MDBX_page *src, + unsigned psize) { + STATIC_ASSERT(UINT16_MAX > MAX_PAGESIZE - PAGEHDRSZ); + STATIC_ASSERT(MIN_PAGESIZE > PAGEHDRSZ + NODESIZE * 4); + if (!IS_LEAF2(src)) { + size_t upper = src->mp_upper, lower = src->mp_lower, unused = upper - lower; + + /* If page isn't full, just copy the used portion. Adjust + * alignment so memcpy may copy words instead of bytes. */ + if (unused > sizeof(void *) * 42) { + lower = roundup_powerof2(lower + PAGEHDRSZ, sizeof(void *)); + upper = (upper + PAGEHDRSZ) & ~(sizeof(void *) - 1); + memcpy(dst, src, lower); + memcpy((char *)dst + upper, (char *)src + upper, psize - upper); + return; + } + } + memcpy(dst, src, psize); +} + +/* Pull a page off the txn's spill list, if present. + * + * If a page being referenced was spilled to disk in this txn, bring + * it back and make it dirty/writable again. + * + * [in] txn the transaction handle. + * [in] mp the page being referenced. It must not be dirty. + * [out] ret the writable page, if any. + * ret is unchanged if mp wasn't spilled. */ +__hot static int __must_check_result mdbx_page_unspill(MDBX_txn *txn, + MDBX_page *mp, + MDBX_page **ret) { + MDBX_env *env = txn->mt_env; + pgno_t pgno = mp->mp_pgno, pn = pgno << 1; + + for (const MDBX_txn *tx2 = txn; tx2; tx2 = tx2->mt_parent) { + if (!tx2->tw.spill_pages) + continue; + unsigned i = mdbx_pnl_exist(tx2->tw.spill_pages, pn); + if (!i) + continue; + if (txn->tw.dirtyroom == 0) + return MDBX_TXN_FULL; + unsigned num = IS_OVERFLOW(mp) ? mp->mp_pages : 1; + MDBX_page *np = mp; + if ((env->me_flags & MDBX_WRITEMAP) == 0) { + np = mdbx_page_malloc(txn, num); + if (unlikely(!np)) + return MDBX_ENOMEM; + if (unlikely(num > 1)) + memcpy(np, mp, pgno2bytes(env, num)); + else + mdbx_page_copy(np, mp, env->me_psize); + } + mdbx_debug("unspill page %" PRIaPGNO, mp->mp_pgno); + if (tx2 == txn) { + /* If in current txn, this page is no longer spilled. + * If it happens to be the last page, truncate the spill list. + * Otherwise mark it as deleted by setting the LSB. */ + txn->tw.spill_pages[i] |= 1; + if (i == MDBX_PNL_SIZE(txn->tw.spill_pages)) + MDBX_PNL_SIZE(txn->tw.spill_pages) -= 1; + } /* otherwise, if belonging to a parent txn, the + * page remains spilled until child commits */ + + int rc = mdbx_page_dirty(txn, np); + if (likely(rc == MDBX_SUCCESS)) { + np->mp_flags |= P_DIRTY; + *ret = np; + } + return rc; + } + return MDBX_SUCCESS; +} + +/* Touch a page: make it dirty and re-insert into tree with updated pgno. + * Set MDBX_TXN_ERROR on failure. + * + * [in] mc cursor pointing to the page to be touched + * + * Returns 0 on success, non-zero on failure. */ +__hot static int mdbx_page_touch(MDBX_cursor *mc) { + MDBX_page *mp = mc->mc_pg[mc->mc_top], *np; + MDBX_txn *txn = mc->mc_txn; + MDBX_cursor *m2, *m3; + pgno_t pgno; + int rc; + + mdbx_cassert(mc, !IS_OVERFLOW(mp)); + if (!F_ISSET(mp->mp_flags, P_DIRTY)) { + if (txn->mt_flags & MDBX_TXN_SPILLS) { + np = NULL; + rc = mdbx_page_unspill(txn, mp, &np); + if (unlikely(rc)) + goto fail; + if (likely(np)) + goto done; + } + + if (unlikely((rc = mdbx_pnl_need(&txn->tw.retired_pages, 1)) || + (rc = mdbx_page_alloc(mc, 1, &np, MDBX_ALLOC_ALL)))) + goto fail; + pgno = np->mp_pgno; + mdbx_debug("touched db %d page %" PRIaPGNO " -> %" PRIaPGNO, DDBI(mc), + mp->mp_pgno, pgno); + mdbx_cassert(mc, mp->mp_pgno != pgno); + mdbx_pnl_xappend(txn->tw.retired_pages, mp->mp_pgno); + mdbx_tassert(txn, mdbx_dpl_find(txn->tw.dirtylist, mp->mp_pgno) == nullptr); + /* Update the parent page, if any, to point to the new page */ + if (mc->mc_top) { + MDBX_page *parent = mc->mc_pg[mc->mc_top - 1]; + MDBX_node *node = page_node(parent, mc->mc_ki[mc->mc_top - 1]); + node_set_pgno(node, pgno); + } else { + mc->mc_db->md_root = pgno; + } + } else if (txn->mt_parent && !IS_SUBP(mp)) { + mdbx_tassert(txn, (txn->mt_env->me_flags & MDBX_WRITEMAP) == 0); + pgno = mp->mp_pgno; + /* If txn has a parent, make sure the page is in our dirty list. */ + const MDBX_page *const dp = mdbx_dpl_find(txn->tw.dirtylist, pgno); + if (dp) { + if (unlikely(mp != dp)) { /* bad cursor? */ + mdbx_error("wrong page 0x%p #%" PRIaPGNO + " in the dirtylist, expecting %p", + __Wpedantic_format_voidptr(dp), pgno, + __Wpedantic_format_voidptr(mp)); + mc->mc_flags &= ~(C_INITIALIZED | C_EOF); + rc = MDBX_PROBLEM; + goto fail; + } + return MDBX_SUCCESS; + } + + mdbx_debug("clone db %d page %" PRIaPGNO, DDBI(mc), mp->mp_pgno); + mdbx_cassert(mc, txn->tw.dirtylist->length <= MDBX_DPL_TXNFULL); + /* No - copy it */ + np = mdbx_page_malloc(txn, 1); + if (unlikely(!np)) { + rc = MDBX_ENOMEM; + goto fail; + } + rc = mdbx_dpl_append(txn->tw.dirtylist, pgno, np); + if (unlikely(rc)) { + mdbx_dpage_free(txn->mt_env, np, 1); + goto fail; + } + } else { + return MDBX_SUCCESS; + } + + mdbx_page_copy(np, mp, txn->mt_env->me_psize); + np->mp_pgno = pgno; + np->mp_flags |= P_DIRTY; + +done: + /* Adjust cursors pointing to mp */ + mc->mc_pg[mc->mc_top] = np; + m2 = txn->mt_cursors[mc->mc_dbi]; + if (mc->mc_flags & C_SUB) { + for (; m2; m2 = m2->mc_next) { + m3 = &m2->mc_xcursor->mx_cursor; + if (m3->mc_snum < mc->mc_snum) + continue; + if (m3->mc_pg[mc->mc_top] == mp) + m3->mc_pg[mc->mc_top] = np; + } + } else { + for (; m2; m2 = m2->mc_next) { + if (m2->mc_snum < mc->mc_snum) + continue; + if (m2 == mc) + continue; + if (m2->mc_pg[mc->mc_top] == mp) { + m2->mc_pg[mc->mc_top] = np; + if (XCURSOR_INITED(m2) && IS_LEAF(np)) + XCURSOR_REFRESH(m2, np, m2->mc_ki[mc->mc_top]); + } + } + } + return MDBX_SUCCESS; + +fail: + txn->mt_flags |= MDBX_TXN_ERROR; + return rc; +} + +__cold int mdbx_env_sync_ex(MDBX_env *env, int force, int nonblock) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) { + env->me_flags |= MDBX_FATAL_ERROR; + return MDBX_PANIC; + } +#endif /* MDBX_TXN_CHECKPID */ + + unsigned flags = env->me_flags & ~MDBX_NOMETASYNC; + if (unlikely(flags & (MDBX_RDONLY | MDBX_FATAL_ERROR))) + return MDBX_EACCESS; + + if (unlikely(!env->me_map)) + return MDBX_EPERM; + + int rc = MDBX_RESULT_TRUE /* means "nothing to sync" */; + bool need_unlock = false; + if (nonblock && *env->me_unsynced_pages == 0) + goto fastpath; + + const bool outside_txn = (env->me_txn0->mt_owner != mdbx_thread_self()); + if (outside_txn) { + int err = mdbx_txn_lock(env, nonblock); + if (unlikely(err != MDBX_SUCCESS)) + return err; + need_unlock = true; + } + + const MDBX_meta *head = mdbx_meta_head(env); + pgno_t unsynced_pages = *env->me_unsynced_pages; + if (!META_IS_STEADY(head) || unsynced_pages) { + const pgno_t autosync_threshold = *env->me_autosync_threshold; + const uint64_t autosync_period = *env->me_autosync_period; + if (force || (autosync_threshold && unsynced_pages >= autosync_threshold) || + (autosync_period && + mdbx_osal_monotime() - *env->me_sync_timestamp >= autosync_period)) + flags &= MDBX_WRITEMAP /* clear flags for full steady sync */; + + if (outside_txn) { + if (unsynced_pages > /* FIXME: define threshold */ 16 && + (flags & (MDBX_NOSYNC | MDBX_MAPASYNC)) == 0) { + mdbx_assert(env, ((flags ^ env->me_flags) & MDBX_WRITEMAP) == 0); + const size_t usedbytes = pgno_align2os_bytes(env, head->mm_geo.next); + + mdbx_txn_unlock(env); + + /* LY: pre-sync without holding lock to reduce latency for writer(s) */ + int err = (flags & MDBX_WRITEMAP) + ? mdbx_msync(&env->me_dxb_mmap, 0, usedbytes, false) + : mdbx_filesync(env->me_fd, MDBX_SYNC_DATA); + if (unlikely(err != MDBX_SUCCESS)) + return err; + + err = mdbx_txn_lock(env, nonblock); + if (unlikely(err != MDBX_SUCCESS)) + return err; + + /* LY: head and unsynced_pages may be changed. */ + head = mdbx_meta_head(env); + unsynced_pages = *env->me_unsynced_pages; + } + env->me_txn0->mt_txnid = meta_txnid(env, head, false); + mdbx_find_oldest(env->me_txn0); + rc = MDBX_RESULT_FALSE /* means "some data was synced" */; + } + + if (!META_IS_STEADY(head) || + ((flags & (MDBX_NOSYNC | MDBX_MAPASYNC)) == 0 && unsynced_pages)) { + mdbx_debug("meta-head %" PRIaPGNO ", %s, sync_pending %" PRIaPGNO, + data_page(head)->mp_pgno, mdbx_durable_str(head), + unsynced_pages); + MDBX_meta meta = *head; + int err = mdbx_sync_locked(env, flags | MDBX_SHRINK_ALLOWED, &meta); + if (unlikely(err != MDBX_SUCCESS)) { + if (need_unlock) + mdbx_txn_unlock(env); + return err; + } + rc = MDBX_RESULT_FALSE /* means "some data was synced" */; + } + } + +fastpath: + /* LY: sync meta-pages if MDBX_NOMETASYNC enabled + * and someone was not synced above. */ + if (rc == MDBX_RESULT_TRUE && (env->me_flags & MDBX_NOMETASYNC) != 0) { + const txnid_t head_txnid = mdbx_recent_committed_txnid(env); + if (*env->me_meta_sync_txnid != (uint32_t)head_txnid) { + rc = (flags & MDBX_WRITEMAP) + ? mdbx_msync(&env->me_dxb_mmap, 0, pgno2bytes(env, NUM_METAS), + false) + : mdbx_filesync(env->me_fd, MDBX_SYNC_DATA | MDBX_SYNC_IODQ); + if (likely(rc == MDBX_SUCCESS)) + *env->me_meta_sync_txnid = (uint32_t)head_txnid; + } + } + if (need_unlock) + mdbx_txn_unlock(env); + return rc; +} + +__cold int mdbx_env_sync(MDBX_env *env) { + return mdbx_env_sync_ex(env, true, false); +} + +__cold int mdbx_env_sync_poll(MDBX_env *env) { + return mdbx_env_sync_ex(env, false, true); +} + +/* Back up parent txn's cursors, then grab the originals for tracking */ +static int mdbx_cursor_shadow(MDBX_txn *src, MDBX_txn *dst) { + MDBX_cursor *mc, *bk; + MDBX_xcursor *mx; + + for (int i = src->mt_numdbs; --i >= 0;) { + dst->mt_cursors[i] = NULL; + if ((mc = src->mt_cursors[i]) != NULL) { + size_t size = sizeof(MDBX_cursor); + if (mc->mc_xcursor) + size += sizeof(MDBX_xcursor); + for (; mc; mc = bk->mc_next) { + bk = mdbx_malloc(size); + if (unlikely(!bk)) + return MDBX_ENOMEM; + *bk = *mc; + mc->mc_backup = bk; + mc->mc_db = &dst->mt_dbs[i]; + /* Kill pointers into src to reduce abuse: The + * user may not use mc until dst ends. But we need a valid + * txn pointer here for cursor fixups to keep working. */ + mc->mc_txn = dst; + mc->mc_dbflag = &dst->mt_dbflags[i]; + if ((mx = mc->mc_xcursor) != NULL) { + *(MDBX_xcursor *)(bk + 1) = *mx; + mx->mx_cursor.mc_txn = dst; + } + mc->mc_next = dst->mt_cursors[i]; + dst->mt_cursors[i] = mc; + } + } + } + return MDBX_SUCCESS; +} + +/* Close this write txn's cursors, give parent txn's cursors back to parent. + * + * [in] txn the transaction handle. + * [in] merge true to keep changes to parent cursors, false to revert. + * + * Returns 0 on success, non-zero on failure. */ +static void mdbx_cursors_eot(MDBX_txn *txn, unsigned merge) { + MDBX_cursor **cursors = txn->mt_cursors, *mc, *next, *bk; + MDBX_xcursor *mx; + int i; + + for (i = txn->mt_numdbs; --i >= 0;) { + for (mc = cursors[i]; mc; mc = next) { + unsigned stage = mc->mc_signature; + mdbx_ensure(txn->mt_env, + stage == MDBX_MC_SIGNATURE || stage == MDBX_MC_WAIT4EOT); + next = mc->mc_next; + mdbx_tassert(txn, !next || next->mc_signature == MDBX_MC_SIGNATURE || + next->mc_signature == MDBX_MC_WAIT4EOT); + if ((bk = mc->mc_backup) != NULL) { + if (merge) { + /* Commit changes to parent txn */ + mc->mc_next = bk->mc_next; + mc->mc_backup = bk->mc_backup; + mc->mc_txn = bk->mc_txn; + mc->mc_db = bk->mc_db; + mc->mc_dbflag = bk->mc_dbflag; + if ((mx = mc->mc_xcursor) != NULL) + mx->mx_cursor.mc_txn = bk->mc_txn; + } else { + /* Abort nested txn */ + *mc = *bk; + if ((mx = mc->mc_xcursor) != NULL) + *mx = *(MDBX_xcursor *)(bk + 1); + } + bk->mc_signature = 0; + mdbx_free(bk); + } + if (stage == MDBX_MC_WAIT4EOT) { + mc->mc_signature = 0; + mdbx_free(mc); + } else { + mc->mc_signature = MDBX_MC_READY4CLOSE; + mc->mc_flags = 0 /* reset C_UNTRACK */; + } + } + cursors[i] = NULL; + } +} + +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) +/* Find largest mvcc-snapshot still referenced by this process. */ +static pgno_t mdbx_find_largest_this(MDBX_env *env, pgno_t largest) { + MDBX_lockinfo *const lck = env->me_lck; + if (likely(lck != NULL /* exclusive mode */)) { + const unsigned snap_nreaders = lck->mti_numreaders; + for (unsigned i = 0; i < snap_nreaders; ++i) { + retry: + if (lck->mti_readers[i].mr_pid == env->me_pid) { + /* mdbx_jitter4testing(true); */ + const pgno_t snap_pages = lck->mti_readers[i].mr_snapshot_pages_used; + const txnid_t snap_txnid = safe64_read(&lck->mti_readers[i].mr_txnid); + mdbx_memory_barrier(); + if (unlikely(snap_pages != lck->mti_readers[i].mr_snapshot_pages_used || + snap_txnid != safe64_read(&lck->mti_readers[i].mr_txnid))) + goto retry; + if (largest < snap_pages && + lck->mti_oldest_reader <= /* ignore pending updates */ snap_txnid && + snap_txnid < SAFE64_INVALID_THRESHOLD) + largest = snap_pages; + } + } + } + return largest; +} + +static void mdbx_txn_valgrind(MDBX_env *env, MDBX_txn *txn) { +#if !defined(__SANITIZE_ADDRESS__) + if (!RUNNING_ON_VALGRIND) + return; +#endif + + if (txn) { /* transaction start */ + if (env->me_poison_edge < txn->mt_next_pgno) + env->me_poison_edge = txn->mt_next_pgno; + VALGRIND_MAKE_MEM_DEFINED(env->me_map, pgno2bytes(env, txn->mt_next_pgno)); + ASAN_UNPOISON_MEMORY_REGION(env->me_map, + pgno2bytes(env, txn->mt_next_pgno)); + /* don't touch more, it should be already poisoned */ + } else { /* transaction end */ + bool should_unlock = false; + pgno_t last = MAX_PAGENO; + if (env->me_txn0 && env->me_txn0->mt_owner == mdbx_thread_self()) { + /* inside write-txn */ + MDBX_meta *head = mdbx_meta_head(env); + last = head->mm_geo.next; + } else if (mdbx_txn_lock(env, true) == MDBX_SUCCESS) { + /* no write-txn */ + last = NUM_METAS; + should_unlock = true; + } else { + /* write txn is running, therefore shouldn't poison any memory range */ + return; + } + + last = mdbx_find_largest_this(env, last); + const pgno_t edge = env->me_poison_edge; + if (edge > last) { + mdbx_assert(env, last >= NUM_METAS); + env->me_poison_edge = last; + VALGRIND_MAKE_MEM_NOACCESS(env->me_map + pgno2bytes(env, last), + pgno2bytes(env, edge - last)); + ASAN_POISON_MEMORY_REGION(env->me_map + pgno2bytes(env, last), + pgno2bytes(env, edge - last)); + } + if (should_unlock) + mdbx_txn_unlock(env); + } +} +#endif /* MDBX_USE_VALGRIND */ + +/* Common code for mdbx_txn_begin() and mdbx_txn_renew(). */ +static int mdbx_txn_renew0(MDBX_txn *txn, unsigned flags) { + MDBX_env *env = txn->mt_env; + int rc; + +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) { + env->me_flags |= MDBX_FATAL_ERROR; + return MDBX_PANIC; + } +#endif /* MDBX_TXN_CHECKPID */ + + STATIC_ASSERT(sizeof(MDBX_reader) == 32); +#ifdef MDBX_OSAL_LOCK + STATIC_ASSERT(offsetof(MDBX_lockinfo, mti_wmutex) % MDBX_CACHELINE_SIZE == 0); + STATIC_ASSERT(offsetof(MDBX_lockinfo, mti_rmutex) % MDBX_CACHELINE_SIZE == 0); +#else + STATIC_ASSERT( + offsetof(MDBX_lockinfo, mti_oldest_reader) % MDBX_CACHELINE_SIZE == 0); + STATIC_ASSERT(offsetof(MDBX_lockinfo, mti_numreaders) % MDBX_CACHELINE_SIZE == + 0); +#endif + STATIC_ASSERT(offsetof(MDBX_lockinfo, mti_readers) % MDBX_CACHELINE_SIZE == + 0); + + if (flags & MDBX_RDONLY) { + txn->mt_flags = MDBX_RDONLY | (env->me_flags & MDBX_NOTLS); + MDBX_reader *r = txn->to.reader; + STATIC_ASSERT(sizeof(size_t) == sizeof(r->mr_tid)); + if (likely(env->me_flags & MDBX_ENV_TXKEY)) { + mdbx_assert(env, !(env->me_flags & MDBX_NOTLS)); + r = thread_rthc_get(env->me_txkey); + if (likely(r)) { + mdbx_assert(env, r->mr_pid == env->me_pid); + mdbx_assert(env, r->mr_tid == mdbx_thread_self()); + } + } else { + mdbx_assert(env, !env->me_lck || (env->me_flags & MDBX_NOTLS)); + } + + if (likely(r)) { + if (unlikely(r->mr_pid != env->me_pid || + r->mr_txnid.inconsistent < SAFE64_INVALID_THRESHOLD)) + return MDBX_BAD_RSLOT; + } else if (env->me_lck) { + unsigned slot, nreaders; + const size_t tid = mdbx_thread_self(); + mdbx_assert(env, env->me_lck->mti_magic_and_version == MDBX_LOCK_MAGIC); + mdbx_assert(env, env->me_lck->mti_os_and_format == MDBX_LOCK_FORMAT); + + rc = mdbx_rdt_lock(env); + if (unlikely(MDBX_IS_ERROR(rc))) + return rc; + if (unlikely(env->me_flags & MDBX_FATAL_ERROR)) { + mdbx_rdt_unlock(env); + return MDBX_PANIC; + } +#if defined(_WIN32) || defined(_WIN64) + if (unlikely(!env->me_map)) { + mdbx_rdt_unlock(env); + return MDBX_EPERM; + } +#endif /* Windows */ + rc = MDBX_SUCCESS; + + if (unlikely(env->me_live_reader != env->me_pid)) { + rc = mdbx_rpid_set(env); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_rdt_unlock(env); + return rc; + } + env->me_live_reader = env->me_pid; + } + + while (1) { + nreaders = env->me_lck->mti_numreaders; + for (slot = 0; slot < nreaders; slot++) + if (env->me_lck->mti_readers[slot].mr_pid == 0) + break; + + if (likely(slot < env->me_maxreaders)) + break; + + rc = mdbx_reader_check0(env, true, NULL); + if (rc != MDBX_RESULT_TRUE) { + mdbx_rdt_unlock(env); + return (rc == MDBX_SUCCESS) ? MDBX_READERS_FULL : rc; + } + } + + r = &env->me_lck->mti_readers[slot]; + /* Claim the reader slot, carefully since other code + * uses the reader table un-mutexed: First reset the + * slot, next publish it in lck->mti_numreaders. After + * that, it is safe for mdbx_env_close() to touch it. + * When it will be closed, we can finally claim it. */ + r->mr_pid = 0; + safe64_reset(&r->mr_txnid, true); + if (slot == nreaders) + env->me_lck->mti_numreaders = ++nreaders; + r->mr_tid = tid; + r->mr_pid = env->me_pid; + mdbx_rdt_unlock(env); + + if (likely(env->me_flags & MDBX_ENV_TXKEY)) { + mdbx_assert(env, env->me_live_reader == env->me_pid); + thread_rthc_set(env->me_txkey, r); + } + } + + while (1) { + MDBX_meta *const meta = mdbx_meta_head(env); + mdbx_jitter4testing(false); + const txnid_t snap = mdbx_meta_txnid_fluid(env, meta); + mdbx_jitter4testing(false); + if (likely(r)) { + safe64_reset(&r->mr_txnid, false); + r->mr_snapshot_pages_used = meta->mm_geo.next; + r->mr_snapshot_pages_retired = meta->mm_pages_retired; + safe64_write(&r->mr_txnid, snap); + mdbx_jitter4testing(false); + mdbx_assert(env, r->mr_pid == mdbx_getpid()); + mdbx_assert(env, r->mr_tid == mdbx_thread_self()); + mdbx_assert(env, r->mr_txnid.inconsistent == snap); + mdbx_compiler_barrier(); + env->me_lck->mti_readers_refresh_flag = true; + mdbx_flush_noncoherent_cpu_writeback(); + } + mdbx_jitter4testing(true); + + /* Snap the state from current meta-head */ + txn->mt_txnid = snap; + txn->mt_geo = meta->mm_geo; + memcpy(txn->mt_dbs, meta->mm_dbs, CORE_DBS * sizeof(MDBX_db)); + txn->mt_canary = meta->mm_canary; + + /* LY: Retry on a race, ITS#7970. */ + mdbx_compiler_barrier(); + if (likely(meta == mdbx_meta_head(env) && + snap == mdbx_meta_txnid_fluid(env, meta) && + snap >= *env->me_oldest)) { + mdbx_jitter4testing(false); + break; + } + } + + if (unlikely(txn->mt_txnid == 0 || + txn->mt_txnid >= SAFE64_INVALID_THRESHOLD)) { + mdbx_error("%s", "environment corrupted by died writer, must shutdown!"); + rc = MDBX_WANNA_RECOVERY; + goto bailout; + } + mdbx_assert(env, txn->mt_txnid >= *env->me_oldest); + txn->to.reader = r; + txn->mt_dbxs = env->me_dbxs; /* mostly static anyway */ + mdbx_ensure(env, txn->mt_txnid >= + /* paranoia is appropriate here */ *env->me_oldest); + } else { + /* Not yet touching txn == env->me_txn0, it may be active */ + mdbx_jitter4testing(false); + rc = mdbx_txn_lock(env, F_ISSET(flags, MDBX_TRYTXN)); + if (unlikely(rc)) + return rc; + if (unlikely(env->me_flags & MDBX_FATAL_ERROR)) { + mdbx_txn_unlock(env); + return MDBX_PANIC; + } +#if defined(_WIN32) || defined(_WIN64) + if (unlikely(!env->me_map)) { + mdbx_txn_unlock(env); + return MDBX_EPERM; + } +#endif /* Windows */ + + mdbx_jitter4testing(false); + MDBX_meta *meta = mdbx_meta_head(env); + mdbx_jitter4testing(false); + txn->mt_canary = meta->mm_canary; + const txnid_t snap = mdbx_meta_txnid_stable(env, meta); + txn->mt_txnid = safe64_txnid_next(snap); + if (unlikely(txn->mt_txnid >= SAFE64_INVALID_THRESHOLD)) { + mdbx_debug("%s", "txnid overflow!"); + rc = MDBX_TXN_FULL; + goto bailout; + } + + txn->mt_flags = flags; + txn->mt_child = NULL; + txn->tw.loose_pages = NULL; + txn->tw.loose_count = 0; + txn->tw.dirtyroom = MDBX_DPL_TXNFULL; + txn->tw.dirtylist = env->me_dirtylist; + mdbx_dpl_clear(txn->tw.dirtylist); + MDBX_PNL_SIZE(txn->tw.retired_pages) = 0; + txn->tw.spill_pages = NULL; + txn->tw.last_reclaimed = 0; + if (txn->tw.lifo_reclaimed) + MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) = 0; + env->me_txn = txn; + memcpy(txn->mt_dbiseqs, env->me_dbiseqs, env->me_maxdbs * sizeof(unsigned)); + /* Copy the DB info and flags */ + memcpy(txn->mt_dbs, meta->mm_dbs, CORE_DBS * sizeof(MDBX_db)); + /* Moved to here to avoid a data race in read TXNs */ + txn->mt_geo = meta->mm_geo; + txn->tw.loose_refund_wl = txn->mt_next_pgno; + } + + /* Setup db info */ + txn->mt_numdbs = env->me_numdbs; + mdbx_compiler_barrier(); + for (unsigned i = CORE_DBS; i < txn->mt_numdbs; i++) { + unsigned x = env->me_dbflags[i]; + txn->mt_dbs[i].md_flags = x & PERSISTENT_FLAGS; + txn->mt_dbflags[i] = + (x & MDBX_VALID) ? DB_VALID | DB_USRVALID | DB_STALE : 0; + } + txn->mt_dbflags[MAIN_DBI] = DB_VALID | DB_USRVALID; + txn->mt_dbflags[FREE_DBI] = DB_VALID; + + if (unlikely(env->me_flags & MDBX_FATAL_ERROR)) { + mdbx_warning("%s", "environment had fatal error, must shutdown!"); + rc = MDBX_PANIC; + } else { + const size_t size = pgno2bytes(env, txn->mt_end_pgno); + if (unlikely(size > env->me_dxb_mmap.limit)) { + if (txn->mt_geo.upper > MAX_PAGENO || + bytes2pgno(env, pgno2bytes(env, txn->mt_geo.upper)) != + txn->mt_geo.upper) { + rc = MDBX_MAP_RESIZED; + goto bailout; + } + rc = mdbx_mapresize(env, txn->mt_end_pgno, txn->mt_geo.upper); + if (rc != MDBX_SUCCESS) { + if (rc == MDBX_RESULT_TRUE) + rc = MDBX_MAP_RESIZED; + goto bailout; + } + } + if (txn->mt_flags & MDBX_RDONLY) { +#if defined(_WIN32) || defined(_WIN64) + if (size > env->me_dbgeo.lower && env->me_dbgeo.shrink) { + txn->mt_flags |= MDBX_SHRINK_ALLOWED; + mdbx_srwlock_AcquireShared(&env->me_remap_guard); + } +#endif + } else { + env->me_dxb_mmap.current = size; + } +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) + mdbx_txn_valgrind(env, txn); +#endif + txn->mt_owner = mdbx_thread_self(); + return MDBX_SUCCESS; + } +bailout: + mdbx_tassert(txn, rc != MDBX_SUCCESS); + mdbx_txn_end(txn, MDBX_END_SLOT | MDBX_END_FAIL_BEGIN); + return rc; +} + +static __always_inline int check_txn(const MDBX_txn *txn, int bad_bits) { + if (unlikely(!txn)) + return MDBX_EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(txn->mt_flags & bad_bits)) + return MDBX_BAD_TXN; + +#if MDBX_TXN_CHECKOWNER + if ((txn->mt_flags & MDBX_NOTLS) == 0 && + unlikely(txn->mt_owner != mdbx_thread_self())) + return txn->mt_owner ? MDBX_THREAD_MISMATCH : MDBX_BAD_TXN; +#endif /* MDBX_TXN_CHECKOWNER */ + + return MDBX_SUCCESS; +} + +static __always_inline int check_txn_rw(const MDBX_txn *txn, int bad_bits) { + if (unlikely(!txn)) + return MDBX_EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(txn->mt_flags & bad_bits)) + return MDBX_BAD_TXN; + + if (unlikely(F_ISSET(txn->mt_flags, MDBX_RDONLY))) + return MDBX_EACCESS; + +#if MDBX_TXN_CHECKOWNER + if (unlikely(txn->mt_owner != mdbx_thread_self())) + return txn->mt_owner ? MDBX_THREAD_MISMATCH : MDBX_BAD_TXN; +#endif /* MDBX_TXN_CHECKOWNER */ + + return MDBX_SUCCESS; +} + +int mdbx_txn_renew(MDBX_txn *txn) { + int rc; + + if (unlikely(!txn)) + return MDBX_EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely((txn->mt_flags & MDBX_RDONLY) == 0)) + return MDBX_EINVAL; + + if (unlikely(txn->mt_owner != 0)) + return MDBX_THREAD_MISMATCH; + + rc = mdbx_txn_renew0(txn, MDBX_RDONLY); + if (rc == MDBX_SUCCESS) { + txn->mt_owner = mdbx_thread_self(); + mdbx_debug("renew txn %" PRIaTXN "%c %p on env %p, root page %" PRIaPGNO + "/%" PRIaPGNO, + txn->mt_txnid, (txn->mt_flags & MDBX_RDONLY) ? 'r' : 'w', + (void *)txn, (void *)txn->mt_env, txn->mt_dbs[MAIN_DBI].md_root, + txn->mt_dbs[FREE_DBI].md_root); + } + return rc; +} + +int mdbx_txn_begin(MDBX_env *env, MDBX_txn *parent, unsigned flags, + MDBX_txn **ret) { + MDBX_txn *txn; + int rc; + unsigned size, tsize; + + if (unlikely(!ret)) + return MDBX_EINVAL; + *ret = NULL; + + if (unlikely(!env)) + return MDBX_EINVAL; + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) + env->me_flags |= MDBX_FATAL_ERROR; +#endif /* MDBX_TXN_CHECKPID */ + + if (unlikely(env->me_flags & MDBX_FATAL_ERROR)) + return MDBX_PANIC; + +#if !defined(_WIN32) && !defined(_WIN64) + /* Don't check env->me_map until lock to + * avoid race with re-mapping for shrinking */ + if (unlikely(!env->me_map)) + return MDBX_EPERM; +#endif /* Windows */ + + if (unlikely(flags & ~MDBX_TXN_BEGIN_FLAGS)) + return MDBX_EINVAL; + + if (unlikely(env->me_flags & MDBX_RDONLY & + ~flags)) /* write txn in RDONLY env */ + return MDBX_EACCESS; + + flags |= env->me_flags & MDBX_WRITEMAP; + + if (parent) { + /* Nested transactions: Max 1 child, write txns only, no writemap */ + rc = check_txn_rw(parent, MDBX_RDONLY | MDBX_WRITEMAP | MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + +#if defined(_WIN32) || defined(_WIN64) + if (unlikely(!env->me_map)) + return MDBX_EPERM; +#endif /* Windows */ + + flags |= parent->mt_flags & + (MDBX_TXN_BEGIN_FLAGS | MDBX_SHRINK_ALLOWED | MDBX_TXN_SPILLS); + /* Child txns save MDBX_pgstate and use own copy of cursors */ + size = env->me_maxdbs * (sizeof(MDBX_db) + sizeof(MDBX_cursor *) + 1); + size += tsize = sizeof(MDBX_txn); + } else if (flags & MDBX_RDONLY) { + if (env->me_txn0 && unlikely(env->me_txn0->mt_owner == mdbx_thread_self())) + return MDBX_BUSY; + size = env->me_maxdbs * (sizeof(MDBX_db) + 1); + size += tsize = sizeof(MDBX_txn); + } else { + /* Reuse preallocated write txn. However, do not touch it until + * mdbx_txn_renew0() succeeds, since it currently may be active. */ + txn = env->me_txn0; + if (unlikely(txn->mt_owner == mdbx_thread_self())) + return MDBX_BUSY; + goto renew; + } + if (unlikely((txn = mdbx_malloc(size)) == NULL)) { + mdbx_debug("calloc: %s", "failed"); + return MDBX_ENOMEM; + } + memset(txn, 0, tsize); + txn->mt_dbxs = env->me_dbxs; /* static */ + txn->mt_dbs = (MDBX_db *)((char *)txn + tsize); + txn->mt_dbflags = (uint8_t *)txn + size - env->me_maxdbs; + txn->mt_flags = flags; + txn->mt_env = env; + + if (parent) { + mdbx_tassert(txn, mdbx_dirtylist_check(parent)); + txn->mt_cursors = (MDBX_cursor **)(txn->mt_dbs + env->me_maxdbs); + txn->mt_dbiseqs = parent->mt_dbiseqs; + txn->tw.dirtylist = mdbx_malloc(sizeof(MDBX_DP) * (MDBX_DPL_TXNFULL + 1)); + txn->tw.reclaimed_pglist = + mdbx_pnl_alloc(MDBX_PNL_ALLOCLEN(parent->tw.reclaimed_pglist)); + if (!txn->tw.dirtylist || !txn->tw.reclaimed_pglist) { + mdbx_pnl_free(txn->tw.reclaimed_pglist); + mdbx_free(txn->tw.dirtylist); + mdbx_free(txn); + return MDBX_ENOMEM; + } + mdbx_dpl_clear(txn->tw.dirtylist); + memcpy(txn->tw.reclaimed_pglist, parent->tw.reclaimed_pglist, + MDBX_PNL_SIZEOF(parent->tw.reclaimed_pglist)); + mdbx_assert(env, mdbx_pnl_check4assert( + txn->tw.reclaimed_pglist, + (txn->mt_next_pgno /* LY: intentional assigment here, + only for assertion */ + = parent->mt_next_pgno))); + + txn->tw.last_reclaimed = parent->tw.last_reclaimed; + if (parent->tw.lifo_reclaimed) { + txn->tw.lifo_reclaimed = parent->tw.lifo_reclaimed; + parent->tw.lifo_reclaimed = + (void *)(intptr_t)MDBX_PNL_SIZE(parent->tw.lifo_reclaimed); + } + + txn->tw.retired_pages = parent->tw.retired_pages; + parent->tw.retired_pages = + (void *)(intptr_t)MDBX_PNL_SIZE(parent->tw.retired_pages); + + txn->mt_txnid = parent->mt_txnid; + txn->tw.dirtyroom = parent->tw.dirtyroom; + txn->mt_geo = parent->mt_geo; + txn->tw.loose_refund_wl = parent->tw.loose_refund_wl; + txn->mt_canary = parent->mt_canary; + parent->mt_flags |= MDBX_TXN_HAS_CHILD; + parent->mt_child = txn; + txn->mt_parent = parent; + txn->mt_numdbs = parent->mt_numdbs; + txn->mt_owner = parent->mt_owner; + memcpy(txn->mt_dbs, parent->mt_dbs, txn->mt_numdbs * sizeof(MDBX_db)); + /* Copy parent's mt_dbflags, but clear DB_NEW */ + for (unsigned i = 0; i < txn->mt_numdbs; i++) + txn->mt_dbflags[i] = parent->mt_dbflags[i] & ~(DB_FRESH | DB_CREAT); + mdbx_tassert(parent, + parent->mt_parent || + parent->tw.dirtyroom + parent->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + env->me_txn = txn; + rc = mdbx_cursor_shadow(parent, txn); + if (unlikely(rc != MDBX_SUCCESS)) + mdbx_txn_end(txn, MDBX_END_FAIL_BEGINCHILD); + } else { /* MDBX_RDONLY */ + txn->mt_dbiseqs = env->me_dbiseqs; + renew: + rc = mdbx_txn_renew0(txn, flags); + } + + if (unlikely(rc != MDBX_SUCCESS)) { + if (txn != env->me_txn0) + mdbx_free(txn); + } else { + mdbx_assert(env, (txn->mt_flags & + ~(MDBX_RDONLY | MDBX_WRITEMAP | MDBX_SHRINK_ALLOWED | + MDBX_NOMETASYNC | MDBX_NOSYNC | MDBX_MAPASYNC)) == 0); + txn->mt_signature = MDBX_MT_SIGNATURE; + *ret = txn; + mdbx_debug("begin txn %" PRIaTXN "%c %p on env %p, root page %" PRIaPGNO + "/%" PRIaPGNO, + txn->mt_txnid, (flags & MDBX_RDONLY) ? 'r' : 'w', (void *)txn, + (void *)env, txn->mt_dbs[MAIN_DBI].md_root, + txn->mt_dbs[FREE_DBI].md_root); + } + + return rc; +} + +int mdbx_txn_info(MDBX_txn *txn, MDBX_txn_info *info, int scan_rlt) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_HAS_CHILD); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!info)) + return MDBX_EINVAL; + + MDBX_env *const env = txn->mt_env; +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) { + env->me_flags |= MDBX_FATAL_ERROR; + return MDBX_PANIC; + } +#endif /* MDBX_TXN_CHECKPID */ + + info->txn_id = txn->mt_txnid; + info->txn_space_used = pgno2bytes(env, txn->mt_geo.next); + + if (txn->mt_flags & MDBX_RDONLY) { + const MDBX_meta *head_meta; + txnid_t head_txnid; + uint64_t head_retired; + do { + /* fetch info from volatile head */ + head_meta = mdbx_meta_head(env); + head_txnid = mdbx_meta_txnid_fluid(env, head_meta); + head_retired = head_meta->mm_pages_retired; + info->txn_space_limit_soft = pgno2bytes(env, head_meta->mm_geo.now); + info->txn_space_limit_hard = pgno2bytes(env, head_meta->mm_geo.upper); + info->txn_space_leftover = + pgno2bytes(env, head_meta->mm_geo.now - head_meta->mm_geo.next); + mdbx_compiler_barrier(); + } while (unlikely(head_meta != mdbx_meta_head(env) || + head_txnid != mdbx_meta_txnid_fluid(env, head_meta))); + + info->txn_reader_lag = head_txnid - info->txn_id; + info->txn_space_dirty = info->txn_space_retired = 0; + if (txn->to.reader && + head_retired > txn->to.reader->mr_snapshot_pages_retired) { + info->txn_space_dirty = info->txn_space_retired = + pgno2bytes(env, (pgno_t)(head_retired - + txn->to.reader->mr_snapshot_pages_retired)); + + size_t retired_next_reader = 0; + MDBX_lockinfo *const lck = env->me_lck; + if (scan_rlt && info->txn_reader_lag > 1 && lck) { + /* find next more recent reader */ + txnid_t next_reader = head_txnid; + const unsigned snap_nreaders = lck->mti_numreaders; + for (unsigned i = 0; i < snap_nreaders; ++i) { + retry: + if (lck->mti_readers[i].mr_pid) { + mdbx_jitter4testing(true); + const txnid_t snap_txnid = + safe64_read(&lck->mti_readers[i].mr_txnid); + const uint64_t snap_retired = + lck->mti_readers[i].mr_snapshot_pages_retired; + mdbx_compiler_barrier(); + if (unlikely(snap_retired != + lck->mti_readers[i].mr_snapshot_pages_retired) || + snap_txnid != safe64_read(&lck->mti_readers[i].mr_txnid)) + goto retry; + if (snap_txnid <= txn->mt_txnid) { + retired_next_reader = 0; + break; + } + if (snap_txnid < next_reader) { + next_reader = snap_txnid; + retired_next_reader = pgno2bytes( + env, (pgno_t)(snap_retired - + txn->to.reader->mr_snapshot_pages_retired)); + } + } + } + } + info->txn_space_dirty = retired_next_reader; + } + } else { + info->txn_space_limit_soft = pgno2bytes(env, txn->mt_geo.now); + info->txn_space_limit_hard = pgno2bytes(env, txn->mt_geo.upper); + info->txn_space_retired = pgno2bytes( + env, txn->mt_child ? (unsigned)(uintptr_t)txn->tw.retired_pages + : MDBX_PNL_SIZE(txn->tw.retired_pages)); + info->txn_space_leftover = pgno2bytes(env, txn->tw.dirtyroom); + info->txn_space_dirty = + pgno2bytes(env, MDBX_DPL_TXNFULL - txn->tw.dirtyroom); + info->txn_reader_lag = INT64_MAX; + MDBX_lockinfo *const lck = env->me_lck; + if (scan_rlt && lck) { + txnid_t oldest_snapshot = txn->mt_txnid; + const unsigned snap_nreaders = lck->mti_numreaders; + if (snap_nreaders) { + oldest_snapshot = mdbx_find_oldest(txn); + if (oldest_snapshot == txn->mt_txnid - 1) { + /* check if there is at least one reader */ + bool exists = false; + for (unsigned i = 0; i < snap_nreaders; ++i) { + if (lck->mti_readers[i].mr_pid && + txn->mt_txnid > safe64_read(&lck->mti_readers[i].mr_txnid)) { + exists = true; + break; + } + } + oldest_snapshot += !exists; + } + } + info->txn_reader_lag = txn->mt_txnid - oldest_snapshot; + } + } + + return MDBX_SUCCESS; +} + +MDBX_env *mdbx_txn_env(MDBX_txn *txn) { + if (unlikely(!txn || txn->mt_signature != MDBX_MT_SIGNATURE || + txn->mt_env->me_signature != MDBX_ME_SIGNATURE)) + return NULL; + return txn->mt_env; +} + +uint64_t mdbx_txn_id(MDBX_txn *txn) { + if (unlikely(!txn || txn->mt_signature != MDBX_MT_SIGNATURE)) + return 0; + return txn->mt_txnid; +} + +int mdbx_txn_flags(MDBX_txn *txn) { + if (unlikely(!txn || txn->mt_signature != MDBX_MT_SIGNATURE)) + return -1; + return txn->mt_flags; +} + +/* Export or close DBI handles opened in this txn. */ +static void mdbx_dbis_update(MDBX_txn *txn, int keep) { + MDBX_dbi n = txn->mt_numdbs; + if (n) { + bool locked = false; + MDBX_env *env = txn->mt_env; + uint8_t *tdbflags = txn->mt_dbflags; + + for (unsigned i = n; --i >= CORE_DBS;) { + if (likely((tdbflags[i] & DB_CREAT) == 0)) + continue; + if (!locked) { + mdbx_ensure(env, + mdbx_fastmutex_acquire(&env->me_dbi_lock) == MDBX_SUCCESS); + locked = true; + } + if (keep) { + env->me_dbflags[i] = txn->mt_dbs[i].md_flags | MDBX_VALID; + mdbx_compiler_barrier(); + if (env->me_numdbs <= i) + env->me_numdbs = i + 1; + } else { + char *ptr = env->me_dbxs[i].md_name.iov_base; + if (ptr) { + env->me_dbxs[i].md_name.iov_len = 0; + mdbx_compiler_barrier(); + mdbx_assert(env, env->me_dbflags[i] == 0); + env->me_dbiseqs[i]++; + env->me_dbxs[i].md_name.iov_base = NULL; + mdbx_free(ptr); + } + } + } + + if (unlikely(locked)) + mdbx_ensure(env, + mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); + } +} + +/* End a transaction, except successful commit of a nested transaction. + * May be called twice for readonly txns: First reset it, then abort. + * [in] txn the transaction handle to end + * [in] mode why and how to end the transaction */ +static int mdbx_txn_end(MDBX_txn *txn, unsigned mode) { + MDBX_env *env = txn->mt_env; + static const char *const names[] = MDBX_END_NAMES; + +#if MDBX_TXN_CHECKPID + if (unlikely(txn->mt_env->me_pid != mdbx_getpid())) { + env->me_flags |= MDBX_FATAL_ERROR; + return MDBX_PANIC; + } +#endif /* MDBX_TXN_CHECKPID */ + + mdbx_debug("%s txn %" PRIaTXN "%c %p on mdbenv %p, root page %" PRIaPGNO + "/%" PRIaPGNO, + names[mode & MDBX_END_OPMASK], txn->mt_txnid, + (txn->mt_flags & MDBX_RDONLY) ? 'r' : 'w', (void *)txn, + (void *)env, txn->mt_dbs[MAIN_DBI].md_root, + txn->mt_dbs[FREE_DBI].md_root); + + mdbx_ensure(env, txn->mt_txnid >= + /* paranoia is appropriate here */ *env->me_oldest); + + int rc = MDBX_SUCCESS; + if (F_ISSET(txn->mt_flags, MDBX_RDONLY)) { + if (txn->to.reader) { + MDBX_reader *slot = txn->to.reader; + mdbx_assert(env, slot->mr_pid == env->me_pid); + if (likely(!F_ISSET(txn->mt_flags, MDBX_TXN_FINISHED))) { + mdbx_assert(env, txn->mt_txnid == slot->mr_txnid.inconsistent && + slot->mr_txnid.inconsistent >= + env->me_lck->mti_oldest_reader); +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) + mdbx_txn_valgrind(env, nullptr); +#endif + slot->mr_snapshot_pages_used = 0; + safe64_reset(&slot->mr_txnid, false); + env->me_lck->mti_readers_refresh_flag = true; + mdbx_flush_noncoherent_cpu_writeback(); + } else { + mdbx_assert(env, slot->mr_pid == env->me_pid); + mdbx_assert(env, + slot->mr_txnid.inconsistent >= SAFE64_INVALID_THRESHOLD); + } + if (mode & MDBX_END_SLOT) { + if ((env->me_flags & MDBX_ENV_TXKEY) == 0) + slot->mr_pid = 0; + txn->to.reader = NULL; + } + } +#if defined(_WIN32) || defined(_WIN64) + if (txn->mt_flags & MDBX_SHRINK_ALLOWED) + mdbx_srwlock_ReleaseShared(&env->me_remap_guard); +#endif + txn->mt_numdbs = 0; /* prevent further DBI activity */ + txn->mt_flags = MDBX_RDONLY | MDBX_TXN_FINISHED; + txn->mt_owner = 0; + } else if (!F_ISSET(txn->mt_flags, MDBX_TXN_FINISHED)) { +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) + if (txn == env->me_txn0) + mdbx_txn_valgrind(env, nullptr); +#endif + /* Export or close DBI handles created in this txn */ + mdbx_dbis_update(txn, mode & MDBX_END_UPDATE); + if (!(mode & MDBX_END_EOTDONE)) /* !(already closed cursors) */ + mdbx_cursors_eot(txn, 0); + if (!(env->me_flags & MDBX_WRITEMAP)) + mdbx_dlist_free(txn); + + txn->mt_flags = MDBX_TXN_FINISHED; + txn->mt_owner = 0; + env->me_txn = txn->mt_parent; + if (txn == env->me_txn0) { + mdbx_assert(env, txn->mt_parent == NULL); + mdbx_pnl_shrink(&txn->tw.retired_pages); + mdbx_pnl_shrink(&txn->tw.reclaimed_pglist); + /* The writer mutex was locked in mdbx_txn_begin. */ + mdbx_txn_unlock(env); + } else { + mdbx_assert(env, txn->mt_parent != NULL); + mdbx_assert(env, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + MDBX_txn *const parent = txn->mt_parent; + env->me_txn->mt_child = NULL; + env->me_txn->mt_flags &= ~MDBX_TXN_HAS_CHILD; + mdbx_pnl_free(txn->tw.reclaimed_pglist); + mdbx_pnl_free(txn->tw.spill_pages); + + if (txn->tw.lifo_reclaimed) { + mdbx_assert(env, MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) >= + (unsigned)(uintptr_t)parent->tw.lifo_reclaimed); + MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) = + (unsigned)(uintptr_t)parent->tw.lifo_reclaimed; + parent->tw.lifo_reclaimed = txn->tw.lifo_reclaimed; + } + + if (txn->tw.retired_pages) { + mdbx_assert(env, MDBX_PNL_SIZE(txn->tw.retired_pages) >= + (unsigned)(uintptr_t)parent->tw.retired_pages); + MDBX_PNL_SIZE(txn->tw.retired_pages) = + (unsigned)(uintptr_t)parent->tw.retired_pages; + parent->tw.retired_pages = txn->tw.retired_pages; + } + + mdbx_free(txn->tw.dirtylist); + + if (parent->mt_geo.upper != txn->mt_geo.upper || + parent->mt_geo.now != txn->mt_geo.now) { + /* undo resize performed by child txn */ + rc = mdbx_mapresize(env, parent->mt_geo.now, parent->mt_geo.upper); + if (rc == MDBX_RESULT_TRUE) { + /* unable undo resize (it is regular for Windows), + * therefore promote size changes from child to the parent txn */ + mdbx_notice("unable undo resize performed by child txn, promote to " + "the parent (%u->%u, %u->%u)", + txn->mt_geo.now, parent->mt_geo.now, txn->mt_geo.upper, + parent->mt_geo.upper); + parent->mt_geo.now = txn->mt_geo.now; + parent->mt_geo.upper = txn->mt_geo.upper; + rc = MDBX_SUCCESS; + } else if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_error("error %d while undo resize performed by child txn, fail " + "the parent", + rc); + parent->mt_flags |= MDBX_TXN_ERROR; + if (!env->me_dxb_mmap.address) + env->me_flags |= MDBX_FATAL_ERROR; + } + } + } + } + + mdbx_assert(env, txn == env->me_txn0 || txn->mt_owner == 0); + if ((mode & MDBX_END_FREE) != 0 && txn != env->me_txn0) { + txn->mt_signature = 0; + mdbx_free(txn); + } + + return rc; +} + +int mdbx_txn_reset(MDBX_txn *txn) { + int rc = check_txn(txn, 0); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + /* This call is only valid for read-only txns */ + if (unlikely((txn->mt_flags & MDBX_RDONLY) == 0)) + return MDBX_EINVAL; + + /* LY: don't close DBI-handles */ + rc = mdbx_txn_end(txn, MDBX_END_RESET | MDBX_END_UPDATE); + if (rc == MDBX_SUCCESS) { + mdbx_tassert(txn, txn->mt_signature == MDBX_MT_SIGNATURE); + mdbx_tassert(txn, txn->mt_owner == 0); + } + return rc; +} + +int mdbx_txn_abort(MDBX_txn *txn) { + int rc = check_txn(txn, 0); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (F_ISSET(txn->mt_flags, MDBX_RDONLY)) + /* LY: don't close DBI-handles */ + return mdbx_txn_end(txn, MDBX_END_ABORT | MDBX_END_UPDATE | MDBX_END_SLOT | + MDBX_END_FREE); + + if (txn->mt_child) + mdbx_txn_abort(txn->mt_child); + + return mdbx_txn_end(txn, MDBX_END_ABORT | MDBX_END_SLOT | MDBX_END_FREE); +} + +static __inline int mdbx_backlog_size(MDBX_txn *txn) { + int reclaimed = MDBX_PNL_SIZE(txn->tw.reclaimed_pglist); + return reclaimed + txn->tw.loose_count; +} + +static __inline int mdbx_backlog_extragap(MDBX_env *env) { + /* LY: extra page(s) for b-tree rebalancing */ + return (env->me_flags & MDBX_LIFORECLAIM) ? 2 : 1; +} + +/* LY: Prepare a backlog of pages to modify GC itself, + * while reclaiming is prohibited. It should be enough to prevent search + * in mdbx_page_alloc() during a deleting, when GC tree is unbalanced. */ +static int mdbx_prep_backlog(MDBX_txn *txn, MDBX_cursor *mc) { + /* LY: extra page(s) for b-tree rebalancing */ + const int extra = + mdbx_backlog_extragap(txn->mt_env) + + MDBX_PNL_SIZEOF(txn->tw.retired_pages) / txn->mt_env->me_maxkey_limit; + + if (mdbx_backlog_size(txn) < mc->mc_db->md_depth + extra) { + mc->mc_flags &= ~C_RECLAIMING; + int rc = mdbx_cursor_touch(mc); + if (unlikely(rc)) + return rc; + + while (unlikely(mdbx_backlog_size(txn) < extra)) { + rc = mdbx_page_alloc(mc, 1, NULL, MDBX_ALLOC_GC); + if (unlikely(rc)) { + if (rc != MDBX_NOTFOUND) + return rc; + break; + } + } + mc->mc_flags |= C_RECLAIMING; + } + + return MDBX_SUCCESS; +} + +static void mdbx_prep_backlog_data(MDBX_txn *txn, MDBX_cursor *mc, + size_t bytes) { + const int wanna = (int)number_of_ovpages(txn->mt_env, bytes) + + mdbx_backlog_extragap(txn->mt_env); + if (unlikely(wanna > mdbx_backlog_size(txn))) { + mc->mc_flags &= ~C_RECLAIMING; + do { + if (mdbx_page_alloc(mc, 1, NULL, MDBX_ALLOC_GC) != MDBX_SUCCESS) + break; + } while (wanna > mdbx_backlog_size(txn)); + mc->mc_flags |= C_RECLAIMING; + } +} + +/* Count all the pages in each DB and in the freelist and make sure + * it matches the actual number of pages being used. + * All named DBs must be open for a correct count. */ +static __cold int mdbx_audit_ex(MDBX_txn *txn, unsigned retired_stored, + bool dont_filter_gc) { + pgno_t pending = 0; + if ((txn->mt_flags & MDBX_RDONLY) == 0) { + pending = txn->tw.loose_count + MDBX_PNL_SIZE(txn->tw.reclaimed_pglist) + + (MDBX_PNL_SIZE(txn->tw.retired_pages) - retired_stored) + + txn->tw.retired2parent_count; + for (MDBX_txn *parent = txn->mt_parent; parent; parent = parent->mt_parent) + pending += parent->tw.loose_count; + } + + MDBX_cursor_couple cx; + int rc = mdbx_cursor_init(&cx.outer, txn, FREE_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + pgno_t freecount = 0; + MDBX_val key, data; + while ((rc = mdbx_cursor_get(&cx.outer, &key, &data, MDBX_NEXT)) == 0) { + if (!dont_filter_gc) { + if (unlikely(key.iov_len != sizeof(txnid_t))) + return MDBX_CORRUPTED; + txnid_t id = unaligned_peek_u64(4, key.iov_base); + if (txn->tw.lifo_reclaimed) { + for (unsigned i = 1; i <= MDBX_PNL_SIZE(txn->tw.lifo_reclaimed); ++i) + if (id == txn->tw.lifo_reclaimed[i]) + goto skip; + } else if (id <= txn->tw.last_reclaimed) + goto skip; + } + + freecount += *(pgno_t *)data.iov_base; + skip:; + } + mdbx_tassert(txn, rc == MDBX_NOTFOUND); + + for (MDBX_dbi i = FREE_DBI; i < txn->mt_numdbs; i++) + txn->mt_dbflags[i] &= ~DB_AUDITED; + + pgno_t count = 0; + for (MDBX_dbi i = FREE_DBI; i <= MAIN_DBI; i++) { + if (!(txn->mt_dbflags[i] & DB_VALID)) + continue; + rc = mdbx_cursor_init(&cx.outer, txn, i); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + txn->mt_dbflags[i] |= DB_AUDITED; + if (txn->mt_dbs[i].md_root == P_INVALID) + continue; + count += txn->mt_dbs[i].md_branch_pages + txn->mt_dbs[i].md_leaf_pages + + txn->mt_dbs[i].md_overflow_pages; + + if (i != MAIN_DBI) + continue; + rc = mdbx_page_search(&cx.outer, NULL, MDBX_PS_FIRST); + while (rc == MDBX_SUCCESS) { + MDBX_page *mp = cx.outer.mc_pg[cx.outer.mc_top]; + for (unsigned j = 0; j < page_numkeys(mp); j++) { + MDBX_node *node = page_node(mp, j); + if (node_flags(node) == F_SUBDATA) { + if (unlikely(node_ds(node) < sizeof(MDBX_db))) + return MDBX_CORRUPTED; + MDBX_db db_copy, *db; + memcpy(db = &db_copy, node_data(node), sizeof(db_copy)); + if ((txn->mt_flags & MDBX_RDONLY) == 0) { + for (MDBX_dbi k = txn->mt_numdbs; --k > MAIN_DBI;) { + if ((txn->mt_dbflags[k] & DB_VALID) && + /* txn->mt_dbxs[k].md_name.iov_len > 0 && */ + node_ks(node) == txn->mt_dbxs[k].md_name.iov_len && + memcmp(node_key(node), txn->mt_dbxs[k].md_name.iov_base, + node_ks(node)) == 0) { + txn->mt_dbflags[k] |= DB_AUDITED; + if (txn->mt_dbflags[k] & DB_DIRTY) { + mdbx_tassert(txn, (txn->mt_dbflags[k] & DB_STALE) == 0); + db = txn->mt_dbs + k; + } + break; + } + } + } + count += + db->md_branch_pages + db->md_leaf_pages + db->md_overflow_pages; + } + } + rc = mdbx_cursor_sibling(&cx.outer, 1); + } + mdbx_tassert(txn, rc == MDBX_NOTFOUND); + } + + for (MDBX_dbi i = FREE_DBI; i < txn->mt_numdbs; i++) { + if ((txn->mt_dbflags[i] & (DB_VALID | DB_AUDITED | DB_STALE)) != DB_VALID) + continue; + if (F_ISSET(txn->mt_dbflags[i], DB_DIRTY | DB_CREAT)) { + count += txn->mt_dbs[i].md_branch_pages + txn->mt_dbs[i].md_leaf_pages + + txn->mt_dbs[i].md_overflow_pages; + } else { + mdbx_warning("audit %s@%" PRIaTXN + ": unable account dbi %d / \"%*s\", state 0x%02x", + txn->mt_parent ? "nested-" : "", txn->mt_txnid, i, + (int)txn->mt_dbxs[i].md_name.iov_len, + (const char *)txn->mt_dbxs[i].md_name.iov_base, + txn->mt_dbflags[i]); + } + } + + if (pending + freecount + count + NUM_METAS == txn->mt_next_pgno) + return MDBX_SUCCESS; + + if ((txn->mt_flags & MDBX_RDONLY) == 0) + mdbx_error("audit @%" PRIaTXN ": %u(pending) = %u(loose-count) + " + "%u(reclaimed-list) + %u(retired-pending) - %u(retired-stored) " + "+ %u(retired2parent)", + txn->mt_txnid, pending, txn->tw.loose_count, + MDBX_PNL_SIZE(txn->tw.reclaimed_pglist), + txn->tw.retired_pages ? MDBX_PNL_SIZE(txn->tw.retired_pages) : 0, + retired_stored, txn->tw.retired2parent_count); + mdbx_error("audit @%" PRIaTXN ": %" PRIaPGNO "(pending) + %" PRIaPGNO + "(free) + %" PRIaPGNO "(count) = %" PRIaPGNO + "(total) <> %" PRIaPGNO "(next-pgno)", + txn->mt_txnid, pending, freecount, count + NUM_METAS, + pending + freecount + count + NUM_METAS, txn->mt_next_pgno); + return MDBX_PROBLEM; +} + +static __inline void clean_reserved_gc_pnl(MDBX_env *env, MDBX_val pnl) { + /* PNL is initially empty, zero out at least the length */ + memset(pnl.iov_base, 0, sizeof(pgno_t)); + if ((env->me_flags & (MDBX_WRITEMAP | MDBX_NOMEMINIT)) == 0) + /* zero out to avoid leaking values from uninitialized malloc'ed memory + * to the file in non-writemap mode if length of the saving page-list + * was changed during space reservation. */ + memset(pnl.iov_base, 0, pnl.iov_len); +} + +/* Cleanup reclaimed GC records, than save the retired-list as of this + * transaction to GC (aka freeDB). This recursive changes the reclaimed-list + * loose-list and retired-list. Keep trying until it stabilizes. */ +static int mdbx_update_gc(MDBX_txn *txn) { + /* txn->tw.reclaimed_pglist[] can grow and shrink during this call. + * txn->tw.last_reclaimed and txn->tw.retired_pages[] can only grow. + * Page numbers cannot disappear from txn->tw.retired_pages[]. */ + MDBX_env *const env = txn->mt_env; + const bool lifo = (env->me_flags & MDBX_LIFORECLAIM) != 0; + const char *dbg_prefix_mode = lifo ? " lifo" : " fifo"; + (void)dbg_prefix_mode; + mdbx_trace("\n>>> @%" PRIaTXN, txn->mt_txnid); + + unsigned retired_stored = 0, loop = 0; + MDBX_cursor mc; + int rc = mdbx_cursor_init(&mc, txn, FREE_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout_notracking; + + mc.mc_flags |= C_RECLAIMING; + mc.mc_next = txn->mt_cursors[FREE_DBI]; + txn->mt_cursors[FREE_DBI] = &mc; + +retry: + mdbx_trace("%s", " >> restart"); + mdbx_tassert( + txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, txn->mt_next_pgno)); + mdbx_tassert(txn, mdbx_dirtylist_check(txn)); + mdbx_tassert(txn, txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + if (unlikely(/* paranoia */ ++loop > 42)) { + mdbx_error("too more loops %u, bailout", loop); + rc = MDBX_PROBLEM; + goto bailout; + } + + unsigned settled = 0, cleaned_gc_slot = 0, reused_gc_slot = 0, + filled_gc_slot = ~0u; + txnid_t cleaned_gc_id = 0, gc_rid = txn->tw.last_reclaimed; + while (true) { + /* Come back here after each Put() in case retired-list changed */ + MDBX_val key, data; + mdbx_trace("%s", " >> continue"); + + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + if (txn->tw.lifo_reclaimed) { + if (cleaned_gc_slot < MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)) { + settled = 0; + cleaned_gc_slot = 0; + reused_gc_slot = 0; + filled_gc_slot = ~0u; + /* LY: cleanup reclaimed records. */ + do { + cleaned_gc_id = txn->tw.lifo_reclaimed[++cleaned_gc_slot]; + mdbx_tassert(txn, + cleaned_gc_slot > 0 && cleaned_gc_id < *env->me_oldest); + key.iov_base = &cleaned_gc_id; + key.iov_len = sizeof(cleaned_gc_id); + rc = mdbx_cursor_get(&mc, &key, NULL, MDBX_SET); + if (rc == MDBX_NOTFOUND) + continue; + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + rc = mdbx_prep_backlog(txn, &mc); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + mdbx_tassert(txn, cleaned_gc_id < *env->me_oldest); + mdbx_trace("%s.cleanup-reclaimed-id [%u]%" PRIaTXN, dbg_prefix_mode, + cleaned_gc_slot, cleaned_gc_id); + rc = mdbx_cursor_del(&mc, 0); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } while (cleaned_gc_slot < MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)); + mdbx_txl_sort(txn->tw.lifo_reclaimed); + gc_rid = MDBX_PNL_LAST(txn->tw.lifo_reclaimed); + } + } else { + /* If using records from GC which we have not yet deleted, + * now delete them and any we reserved for me_reclaimed_pglist. */ + while (cleaned_gc_id <= txn->tw.last_reclaimed) { + gc_rid = cleaned_gc_id; + settled = 0; + rc = mdbx_cursor_first(&mc, &key, NULL); + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_NOTFOUND) + break; + goto bailout; + } + if (unlikely(key.iov_len != sizeof(txnid_t))) { + rc = MDBX_CORRUPTED; + goto bailout; + } + cleaned_gc_id = unaligned_peek_u64(4, key.iov_base); + if (unlikely(cleaned_gc_id < 1 || + cleaned_gc_id >= SAFE64_INVALID_THRESHOLD)) { + rc = MDBX_CORRUPTED; + goto bailout; + } + if (cleaned_gc_id > txn->tw.last_reclaimed) + break; + if (cleaned_gc_id < txn->tw.last_reclaimed) { + rc = mdbx_prep_backlog(txn, &mc); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + mdbx_tassert(txn, cleaned_gc_id <= txn->tw.last_reclaimed); + mdbx_tassert(txn, cleaned_gc_id < *env->me_oldest); + mdbx_trace("%s.cleanup-reclaimed-id %" PRIaTXN, dbg_prefix_mode, + cleaned_gc_id); + rc = mdbx_cursor_del(&mc, 0); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + } + + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + mdbx_tassert(txn, mdbx_dirtylist_check(txn)); + mdbx_tassert(txn, txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + if (mdbx_audit_enabled()) { + rc = mdbx_audit_ex(txn, retired_stored, false); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + + /* return suitable into unallocated space */ + if (mdbx_refund(txn)) { + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + if (mdbx_audit_enabled()) { + rc = mdbx_audit_ex(txn, retired_stored, false); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + } + + /* handle loose pages - put ones into the reclaimed- or retired-list */ + if (txn->tw.loose_pages) { + /* Return loose page numbers to me_reclaimed_pglist, + * though usually none are left at this point. + * The pages themselves remain in dirtylist. */ + if (unlikely(!txn->tw.lifo_reclaimed && txn->tw.last_reclaimed < 1)) { + if (txn->tw.loose_count > 0) { + /* Put loose page numbers in tw.retired_pages, + * since unable to return them to me_reclaimed_pglist. */ + if (unlikely((rc = mdbx_pnl_need(&txn->tw.retired_pages, + txn->tw.loose_count)) != 0)) + goto bailout; + for (MDBX_page *mp = txn->tw.loose_pages; mp; mp = mp->mp_next) + mdbx_pnl_xappend(txn->tw.retired_pages, mp->mp_pgno); + mdbx_trace("%s: append %u loose-pages to retired-pages", + dbg_prefix_mode, txn->tw.loose_count); + } + } else { + /* Room for loose pages + temp PNL with same */ + rc = mdbx_pnl_need(&txn->tw.reclaimed_pglist, + 2 * txn->tw.loose_count + 2); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + MDBX_PNL loose = txn->tw.reclaimed_pglist + + MDBX_PNL_ALLOCLEN(txn->tw.reclaimed_pglist) - + txn->tw.loose_count - 1; + unsigned count = 0; + for (MDBX_page *mp = txn->tw.loose_pages; mp; mp = mp->mp_next) { + mdbx_tassert(txn, mp->mp_flags == (P_LOOSE | P_DIRTY)); + loose[++count] = mp->mp_pgno; + } + mdbx_tassert(txn, count == txn->tw.loose_count); + MDBX_PNL_SIZE(loose) = count; + mdbx_pnl_sort(loose); + mdbx_pnl_xmerge(txn->tw.reclaimed_pglist, loose); + mdbx_trace("%s: append %u loose-pages to reclaimed-pages", + dbg_prefix_mode, txn->tw.loose_count); + } + + /* filter-out list of dirty-pages from loose-pages */ + const MDBX_DPL dl = txn->tw.dirtylist; + unsigned w = 0; + for (unsigned r = w; ++r <= dl->length;) { + MDBX_page *dp = dl[r].ptr; + mdbx_tassert(txn, (dp->mp_flags & P_DIRTY)); + mdbx_tassert(txn, dl[r].pgno + (IS_OVERFLOW(dp) ? dp->mp_pages : 1) <= + txn->mt_next_pgno); + if ((dp->mp_flags & P_LOOSE) == 0) { + if (++w != r) + dl[w] = dl[r]; + } else { + mdbx_tassert(txn, dp->mp_flags == (P_LOOSE | P_DIRTY)); + if ((env->me_flags & MDBX_WRITEMAP) == 0) + mdbx_dpage_free(env, dp, 1); + } + } + mdbx_trace("%s: filtered-out loose-pages from %u -> %u dirty-pages", + dbg_prefix_mode, dl->length, w); + mdbx_tassert(txn, txn->tw.loose_count == dl->length - w); + dl->length = w; + dl->sorted = 0; + txn->tw.dirtyroom += txn->tw.loose_count; + txn->tw.loose_pages = NULL; + txn->tw.loose_count = 0; + } + + /* handle retired-list - store ones into single gc-record */ + if (retired_stored < MDBX_PNL_SIZE(txn->tw.retired_pages)) { + if (unlikely(!retired_stored)) { + /* Make sure last page of GC is touched and on retired-list */ + mc.mc_flags &= ~C_RECLAIMING; + rc = mdbx_page_search(&mc, NULL, MDBX_PS_LAST | MDBX_PS_MODIFY); + mc.mc_flags |= C_RECLAIMING; + if (unlikely(rc != MDBX_SUCCESS) && rc != MDBX_NOTFOUND) + goto bailout; + } + /* Write to last page of GC */ + key.iov_len = sizeof(txn->mt_txnid); + key.iov_base = &txn->mt_txnid; + do { + data.iov_len = MDBX_PNL_SIZEOF(txn->tw.retired_pages); + mdbx_prep_backlog_data(txn, &mc, data.iov_len); + rc = mdbx_cursor_put(&mc, &key, &data, MDBX_RESERVE); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + /* Retry if tw.retired_pages[] grew during the Put() */ + } while (data.iov_len < MDBX_PNL_SIZEOF(txn->tw.retired_pages)); + + retired_stored = (unsigned)MDBX_PNL_SIZE(txn->tw.retired_pages); + mdbx_pnl_sort(txn->tw.retired_pages); + mdbx_assert(env, data.iov_len == MDBX_PNL_SIZEOF(txn->tw.retired_pages)); + memcpy(data.iov_base, txn->tw.retired_pages, data.iov_len); + + mdbx_trace("%s.put-retired #%u @ %" PRIaTXN, dbg_prefix_mode, + retired_stored, txn->mt_txnid); + + if (mdbx_log_enabled(MDBX_LOG_EXTRA)) { + unsigned i = retired_stored; + mdbx_debug_extra("PNL write txn %" PRIaTXN " root %" PRIaPGNO + " num %u, PNL", + txn->mt_txnid, txn->mt_dbs[FREE_DBI].md_root, i); + for (; i; i--) + mdbx_debug_extra_print(" %" PRIaPGNO, txn->tw.retired_pages[i]); + mdbx_debug_extra_print("%s", "\n"); + } + continue; + } + + /* handle reclaimed and loost pages - merge and store both into gc */ + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + mdbx_tassert(txn, txn->tw.loose_count == 0); + + mdbx_trace("%s", " >> reserving"); + if (mdbx_audit_enabled()) { + rc = mdbx_audit_ex(txn, retired_stored, false); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + const unsigned amount = (unsigned)MDBX_PNL_SIZE(txn->tw.reclaimed_pglist); + const unsigned left = amount - settled; + mdbx_trace("%s: amount %u, settled %d, left %d, lifo-reclaimed-slots %u, " + "reused-gc-slots %u", + dbg_prefix_mode, amount, settled, (int)left, + txn->tw.lifo_reclaimed + ? (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) + : 0, + reused_gc_slot); + if (0 >= (int)left) + break; + + const unsigned prefer_max_scatter = 257; + txnid_t reservation_gc_id; + if (lifo) { + const unsigned lifo_len = + txn->tw.lifo_reclaimed + ? (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) + : 0; + if (lifo_len < prefer_max_scatter && + left > (lifo_len - reused_gc_slot) * env->me_maxgc_ov1page) { + /* LY: need just a txn-id for save page list. */ + mc.mc_flags &= ~C_RECLAIMING; + rc = mdbx_page_alloc(&mc, 0, NULL, MDBX_ALLOC_GC | MDBX_ALLOC_KICK); + mc.mc_flags |= C_RECLAIMING; + if (likely(rc == MDBX_SUCCESS)) { + /* LY: ok, reclaimed from GC. */ + mdbx_trace("%s: took @%" PRIaTXN " from GC, continue", + dbg_prefix_mode, MDBX_PNL_LAST(txn->tw.lifo_reclaimed)); + continue; + } + if (unlikely(rc != MDBX_NOTFOUND)) + /* LY: other troubles... */ + goto bailout; + + /* LY: GC is empty, will look any free txn-id in high2low order. */ + if (unlikely(gc_rid == 0)) { + mdbx_tassert(txn, txn->tw.last_reclaimed == 0); + txn->tw.last_reclaimed = gc_rid = mdbx_find_oldest(txn) - 1; + if (txn->tw.lifo_reclaimed == nullptr) { + txn->tw.lifo_reclaimed = mdbx_txl_alloc(); + if (unlikely(!txn->tw.lifo_reclaimed)) { + rc = MDBX_ENOMEM; + goto bailout; + } + } + } + + mdbx_tassert(txn, txn->tw.lifo_reclaimed != nullptr); + rc = MDBX_RESULT_TRUE; + do { + if (unlikely(gc_rid <= 1)) { + if (unlikely(MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) <= + reused_gc_slot)) { + mdbx_notice("** restart: reserve depleted (reused_gc_slot %u >= " + "lifo_reclaimed %u" PRIaTXN, + reused_gc_slot, + (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)); + goto retry; + } + break; + } + + mdbx_tassert(txn, gc_rid > 1 && gc_rid < SAFE64_INVALID_THRESHOLD); + rc = mdbx_txl_append(&txn->tw.lifo_reclaimed, --gc_rid); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + cleaned_gc_slot += 1 /* mark GC cleanup is not needed. */; + + mdbx_trace("%s: append @%" PRIaTXN + " to lifo-reclaimed, cleaned-gc-slot = %u", + dbg_prefix_mode, gc_rid, cleaned_gc_slot); + } while (gc_rid && + MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) < prefer_max_scatter && + left > ((unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) - + reused_gc_slot) * + env->me_maxgc_ov1page); + if (reused_gc_slot && rc != MDBX_RESULT_TRUE) { + mdbx_trace("%s: restart innter-loop since reused_gc_slot %u > 0", + dbg_prefix_mode, reused_gc_slot); + continue; + } + } + + const unsigned i = + (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) - reused_gc_slot; + mdbx_tassert(txn, i > 0 && i <= MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)); + reservation_gc_id = txn->tw.lifo_reclaimed[i]; + mdbx_trace("%s: take @%" PRIaTXN " from lifo-reclaimed[%u]", + dbg_prefix_mode, reservation_gc_id, i); + } else { + mdbx_tassert(txn, txn->tw.lifo_reclaimed == NULL); + if (unlikely(gc_rid == 0)) { + gc_rid = mdbx_find_oldest(txn) - 1; + rc = mdbx_cursor_get(&mc, &key, NULL, MDBX_FIRST); + if (rc == MDBX_SUCCESS) { + if (unlikely(key.iov_len != sizeof(txnid_t))) { + rc = MDBX_CORRUPTED; + goto bailout; + } + txnid_t gc_first = unaligned_peek_u64(4, key.iov_base); + if (unlikely(gc_first < 1 || gc_first >= SAFE64_INVALID_THRESHOLD)) { + rc = MDBX_CORRUPTED; + goto bailout; + } + if (gc_rid >= gc_first) + gc_rid = gc_first - 1; + if (unlikely(gc_rid == 0)) { + mdbx_error("%s", "** no GC tail-space to store"); + rc = MDBX_PROBLEM; + goto bailout; + } + } else if (rc != MDBX_NOTFOUND) + goto bailout; + mdbx_tassert(txn, txn->tw.last_reclaimed == 0); + txn->tw.last_reclaimed = gc_rid; + } + reservation_gc_id = gc_rid--; + mdbx_trace("%s: take @%" PRIaTXN " from head-gc-id", dbg_prefix_mode, + reservation_gc_id); + } + ++reused_gc_slot; + + unsigned chunk = left; + if (unlikely(chunk > env->me_maxgc_ov1page)) { + const unsigned avail_gc_slots = + txn->tw.lifo_reclaimed + ? (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) - + reused_gc_slot + 1 + : (gc_rid < INT16_MAX) ? (unsigned)gc_rid : INT16_MAX; + if (avail_gc_slots > 1) { + if (chunk < env->me_maxgc_ov1page * 2) + chunk /= 2; + else { + const unsigned threshold = + env->me_maxgc_ov1page * ((avail_gc_slots < prefer_max_scatter) + ? avail_gc_slots + : prefer_max_scatter); + if (left < threshold) + chunk = env->me_maxgc_ov1page; + else { + const unsigned tail = left - threshold + env->me_maxgc_ov1page + 1; + unsigned span = 1; + unsigned avail = (unsigned)((pgno2bytes(env, span) - PAGEHDRSZ) / + sizeof(pgno_t)) /*- 1 + span */; + if (tail > avail) { + for (unsigned i = amount - span; i > 0; --i) { + if (MDBX_PNL_ASCENDING + ? (txn->tw.reclaimed_pglist[i] + span) + : (txn->tw.reclaimed_pglist[i] - span) == + txn->tw.reclaimed_pglist[i + span]) { + span += 1; + avail = (unsigned)((pgno2bytes(env, span) - PAGEHDRSZ) / + sizeof(pgno_t)) - + 1 + span; + if (avail >= tail) + break; + } + } + } + + chunk = (avail >= tail) ? tail - span + : (avail_gc_slots > 3 && + reused_gc_slot < prefer_max_scatter - 3) + ? avail - span + : tail; + } + } + } + } + mdbx_tassert(txn, chunk > 0); + + mdbx_trace("%s: rc_rid %" PRIaTXN ", reused_gc_slot %u, reservation-id " + "%" PRIaTXN, + dbg_prefix_mode, gc_rid, reused_gc_slot, reservation_gc_id); + + mdbx_trace("%s: chunk %u, gc-per-ovpage %u", dbg_prefix_mode, chunk, + env->me_maxgc_ov1page); + + mdbx_tassert(txn, reservation_gc_id < *env->me_oldest); + if (unlikely(reservation_gc_id < 1 || + reservation_gc_id >= *env->me_oldest)) { + mdbx_error("%s", "** internal error (reservation_gc_id)"); + rc = MDBX_PROBLEM; + goto bailout; + } + + key.iov_len = sizeof(reservation_gc_id); + key.iov_base = &reservation_gc_id; + data.iov_len = (chunk + 1) * sizeof(pgno_t); + mdbx_trace("%s.reserve: %u [%u...%u] @%" PRIaTXN, dbg_prefix_mode, chunk, + settled + 1, settled + chunk + 1, reservation_gc_id); + mdbx_prep_backlog_data(txn, &mc, data.iov_len); + rc = mdbx_cursor_put(&mc, &key, &data, MDBX_RESERVE | MDBX_NOOVERWRITE); + mdbx_tassert(txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, + txn->mt_next_pgno)); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + + clean_reserved_gc_pnl(env, data); + settled += chunk; + mdbx_trace("%s.settled %u (+%u), continue", dbg_prefix_mode, settled, + chunk); + + if (txn->tw.lifo_reclaimed && + unlikely(amount < MDBX_PNL_SIZE(txn->tw.reclaimed_pglist))) { + mdbx_notice("** restart: reclaimed-list growth %u -> %u", amount, + (unsigned)MDBX_PNL_SIZE(txn->tw.reclaimed_pglist)); + goto retry; + } + + continue; + } + + mdbx_tassert( + txn, + cleaned_gc_slot == + (txn->tw.lifo_reclaimed ? MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) : 0)); + + mdbx_trace("%s", " >> filling"); + /* Fill in the reserved records */ + filled_gc_slot = + txn->tw.lifo_reclaimed + ? (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) - reused_gc_slot + : reused_gc_slot; + rc = MDBX_SUCCESS; + mdbx_tassert( + txn, mdbx_pnl_check4assert(txn->tw.reclaimed_pglist, txn->mt_next_pgno)); + mdbx_tassert(txn, mdbx_dirtylist_check(txn)); + if (MDBX_PNL_SIZE(txn->tw.reclaimed_pglist)) { + MDBX_val key, data; + key.iov_len = data.iov_len = 0; /* avoid MSVC warning */ + key.iov_base = data.iov_base = NULL; + + const unsigned amount = MDBX_PNL_SIZE(txn->tw.reclaimed_pglist); + unsigned left = amount; + if (txn->tw.lifo_reclaimed == nullptr) { + mdbx_tassert(txn, lifo == 0); + rc = mdbx_cursor_first(&mc, &key, &data); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } else { + mdbx_tassert(txn, lifo != 0); + } + + while (true) { + txnid_t fill_gc_id; + mdbx_trace("%s: left %u of %u", dbg_prefix_mode, left, + (unsigned)MDBX_PNL_SIZE(txn->tw.reclaimed_pglist)); + if (txn->tw.lifo_reclaimed == nullptr) { + mdbx_tassert(txn, lifo == 0); + fill_gc_id = unaligned_peek_u64(4, key.iov_base); + if (filled_gc_slot-- == 0 || fill_gc_id > txn->tw.last_reclaimed) { + mdbx_notice( + "** restart: reserve depleted (filled_slot %u, fill_id %" PRIaTXN + " > last_reclaimed %" PRIaTXN, + filled_gc_slot, fill_gc_id, txn->tw.last_reclaimed); + goto retry; + } + } else { + mdbx_tassert(txn, lifo != 0); + if (++filled_gc_slot > + (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)) { + mdbx_notice("** restart: reserve depleted (filled_gc_slot %u > " + "lifo_reclaimed %u" PRIaTXN, + filled_gc_slot, + (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)); + goto retry; + } + fill_gc_id = txn->tw.lifo_reclaimed[filled_gc_slot]; + mdbx_trace("%s.seek-reservation @%" PRIaTXN " at lifo_reclaimed[%u]", + dbg_prefix_mode, fill_gc_id, filled_gc_slot); + key.iov_base = &fill_gc_id; + key.iov_len = sizeof(fill_gc_id); + rc = mdbx_cursor_get(&mc, &key, &data, MDBX_SET_KEY); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + mdbx_tassert(txn, cleaned_gc_slot == + (txn->tw.lifo_reclaimed + ? MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) + : 0)); + mdbx_tassert(txn, fill_gc_id > 0 && fill_gc_id < *env->me_oldest); + key.iov_base = &fill_gc_id; + key.iov_len = sizeof(fill_gc_id); + + mdbx_tassert(txn, data.iov_len >= sizeof(pgno_t) * 2); + mc.mc_flags |= C_GCFREEZE; + unsigned chunk = (unsigned)(data.iov_len / sizeof(pgno_t)) - 1; + if (unlikely(chunk > left)) { + mdbx_trace("%s: chunk %u > left %u, @%" PRIaTXN, dbg_prefix_mode, chunk, + left, fill_gc_id); + if ((loop < 5 && chunk - left > loop / 2) || + chunk - left > env->me_maxgc_ov1page) { + data.iov_len = (left + 1) * sizeof(pgno_t); + if (loop < 7) + mc.mc_flags &= ~C_GCFREEZE; + } + chunk = left; + } + rc = mdbx_cursor_put(&mc, &key, &data, MDBX_CURRENT | MDBX_RESERVE); + mc.mc_flags &= ~C_GCFREEZE; + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + clean_reserved_gc_pnl(env, data); + + if (unlikely(txn->tw.loose_count || + amount != MDBX_PNL_SIZE(txn->tw.reclaimed_pglist))) { + mdbx_notice("** restart: reclaimed-list changed (%u -> %u, %u)", amount, + MDBX_PNL_SIZE(txn->tw.reclaimed_pglist), + txn->tw.loose_count); + goto retry; + } + if (unlikely(txn->tw.lifo_reclaimed + ? cleaned_gc_slot < MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) + : cleaned_gc_id < txn->tw.last_reclaimed)) { + mdbx_notice("%s", "** restart: reclaimed-slots changed"); + goto retry; + } + + pgno_t *dst = data.iov_base; + *dst++ = chunk; + pgno_t *src = MDBX_PNL_BEGIN(txn->tw.reclaimed_pglist) + left - chunk; + memcpy(dst, src, chunk * sizeof(pgno_t)); + pgno_t *from = src, *to = src + chunk; + mdbx_trace("%s.fill: %u [ %u:%" PRIaPGNO "...%u:%" PRIaPGNO + "] @%" PRIaTXN, + dbg_prefix_mode, chunk, + (unsigned)(from - txn->tw.reclaimed_pglist), from[0], + (unsigned)(to - txn->tw.reclaimed_pglist), to[-1], fill_gc_id); + + left -= chunk; + if (mdbx_audit_enabled()) { + rc = mdbx_audit_ex(txn, retired_stored + amount - left, true); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } + if (left == 0) { + rc = MDBX_SUCCESS; + break; + } + + if (txn->tw.lifo_reclaimed == nullptr) { + mdbx_tassert(txn, lifo == 0); + rc = mdbx_cursor_next(&mc, &key, &data, MDBX_NEXT); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } else { + mdbx_tassert(txn, lifo != 0); + } + } + } + + mdbx_tassert(txn, rc == MDBX_SUCCESS); + if (unlikely(txn->tw.loose_count != 0 || + filled_gc_slot != + (txn->tw.lifo_reclaimed + ? (unsigned)MDBX_PNL_SIZE(txn->tw.lifo_reclaimed) + : 0))) { + mdbx_notice("** restart: reserve excess (filled-slot %u, loose-count %u)", + filled_gc_slot, txn->tw.loose_count); + goto retry; + } + + mdbx_tassert(txn, + txn->tw.lifo_reclaimed == NULL || + cleaned_gc_slot == MDBX_PNL_SIZE(txn->tw.lifo_reclaimed)); + +bailout: + txn->mt_cursors[FREE_DBI] = mc.mc_next; + +bailout_notracking: + MDBX_PNL_SIZE(txn->tw.reclaimed_pglist) = 0; + mdbx_trace("<<< %u loops, rc = %d", loop, rc); + return rc; +} + +static int mdbx_flush_iov(MDBX_txn *const txn, struct iovec *iov, + unsigned iov_items, size_t iov_off, + size_t iov_bytes) { + MDBX_env *const env = txn->mt_env; + int rc = mdbx_pwritev(env->me_fd, iov, iov_items, iov_off, iov_bytes); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_error("Write error: %s", mdbx_strerror(rc)); + txn->mt_flags |= MDBX_TXN_ERROR; + } + + for (unsigned i = 0; i < iov_items; i++) + mdbx_dpage_free(env, (MDBX_page *)iov[i].iov_base, + bytes2pgno(env, iov[i].iov_len)); + + return rc; +} + +/* Flush (some) dirty pages to the map, after clearing their dirty flag. + * [in] txn the transaction that's being committed + * [in] keep number of initial pages in dirtylist to keep dirty. + * Returns 0 on success, non-zero on failure. */ +static int mdbx_page_flush(MDBX_txn *txn, const unsigned keep) { + struct iovec iov[MDBX_COMMIT_PAGES]; + const MDBX_DPL dl = (keep || txn->tw.loose_count > 1) + ? mdbx_dpl_sort(txn->tw.dirtylist) + : txn->tw.dirtylist; + MDBX_env *const env = txn->mt_env; + pgno_t flush_begin = MAX_PAGENO; + pgno_t flush_end = MIN_PAGENO; + unsigned iov_items = 0; + size_t iov_bytes = 0; + size_t iov_off = 0; + unsigned r, w; + for (r = w = keep; ++r <= dl->length;) { + MDBX_page *dp = dl[r].ptr; + mdbx_tassert(txn, + dp->mp_pgno >= MIN_PAGENO && dp->mp_pgno < txn->mt_next_pgno); + mdbx_tassert(txn, dp->mp_flags & P_DIRTY); + + /* Don't flush this page yet */ + if (dp->mp_flags & (P_LOOSE | P_KEEP)) { + dp->mp_flags &= ~P_KEEP; + dl[++w] = dl[r]; + continue; + } + + const unsigned npages = IS_OVERFLOW(dp) ? dp->mp_pages : 1; + flush_begin = (flush_begin < dp->mp_pgno) ? flush_begin : dp->mp_pgno; + flush_end = + (flush_end > dp->mp_pgno + npages) ? flush_end : dp->mp_pgno + npages; + *env->me_unsynced_pages += npages; + dp->mp_flags &= ~P_DIRTY; + dp->mp_validator = 0 /* TODO */; + + if ((env->me_flags & MDBX_WRITEMAP) == 0) { + const size_t size = pgno2bytes(env, npages); + if (iov_off + iov_bytes != pgno2bytes(env, dp->mp_pgno) || + iov_items == ARRAY_LENGTH(iov) || iov_bytes + size > MAX_WRITE) { + if (iov_items) { + int rc = mdbx_flush_iov(txn, iov, iov_items, iov_off, iov_bytes); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; +#if MDBX_CPU_CACHE_MMAP_NONCOHERENT +#if defined(__linux__) || defined(__gnu_linux__) + if (mdbx_linux_kernel_version >= 0x02060b00) + /* Linux kernels older than version 2.6.11 ignore the addr and nbytes + * arguments, making this function fairly expensive. Therefore, the + * whole cache is always flushed. */ +#endif /* Linux */ + mdbx_invalidate_mmap_noncoherent_cache(env->me_map + iov_off, + iov_bytes); +#endif /* MDBX_CPU_CACHE_MMAP_NONCOHERENT */ + iov_items = 0; + iov_bytes = 0; + } + iov_off = pgno2bytes(env, dp->mp_pgno); + } + iov[iov_items].iov_base = dp; + iov[iov_items].iov_len = size; + iov_items += 1; + iov_bytes += size; + } + } + + if (iov_items) { + int rc = mdbx_flush_iov(txn, iov, iov_items, iov_off, iov_bytes); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + +#if MDBX_CPU_CACHE_MMAP_NONCOHERENT && \ + (defined(__linux__) || defined(__gnu_linux__)) + if ((env->me_flags & MDBX_WRITEMAP) == 0 && + mdbx_linux_kernel_version < 0x02060b00) + /* Linux kernels older than version 2.6.11 ignore the addr and nbytes + * arguments, making this function fairly expensive. Therefore, the + * whole cache is always flushed. */ + mdbx_invalidate_mmap_noncoherent_cache( + env->me_map + pgno2bytes(env, flush_begin), + pgno2bytes(env, flush_end - flush_begin)); +#endif /* MDBX_CPU_CACHE_MMAP_NONCOHERENT && Linux */ + + /* TODO: use flush_begin & flush_end for msync() & sync_file_range(). */ + (void)flush_begin; + (void)flush_end; + + txn->tw.dirtyroom += r - 1 - w; + dl->length = w; + mdbx_tassert(txn, txn->mt_parent || + txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + return MDBX_SUCCESS; +} + +/* Check for misused dbi handles */ +#define TXN_DBI_CHANGED(txn, dbi) \ + ((txn)->mt_dbiseqs[dbi] != (txn)->mt_env->me_dbiseqs[dbi]) + +/* Import DBI which opened after txn started into context */ +static __cold bool mdbx_txn_import_dbi(MDBX_txn *txn, MDBX_dbi dbi) { + MDBX_env *env = txn->mt_env; + if (dbi < CORE_DBS || dbi >= env->me_numdbs) + return false; + + mdbx_ensure(env, mdbx_fastmutex_acquire(&env->me_dbi_lock) == MDBX_SUCCESS); + const unsigned snap_numdbs = env->me_numdbs; + mdbx_compiler_barrier(); + for (unsigned i = CORE_DBS; i < snap_numdbs; ++i) { + if (i >= txn->mt_numdbs) + txn->mt_dbflags[i] = 0; + if (!(txn->mt_dbflags[i] & DB_USRVALID) && + (env->me_dbflags[i] & MDBX_VALID)) { + txn->mt_dbs[i].md_flags = env->me_dbflags[i] & PERSISTENT_FLAGS; + txn->mt_dbflags[i] = DB_VALID | DB_USRVALID | DB_STALE; + mdbx_tassert(txn, txn->mt_dbxs[i].md_cmp != NULL); + } + } + txn->mt_numdbs = snap_numdbs; + + mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); + return txn->mt_dbflags[dbi] & DB_USRVALID; +} + +/* Check txn and dbi arguments to a function */ +static __inline bool TXN_DBI_EXIST(MDBX_txn *txn, MDBX_dbi dbi, + unsigned validity) { + if (likely(dbi < txn->mt_numdbs && (txn->mt_dbflags[dbi] & validity))) + return true; + + return mdbx_txn_import_dbi(txn, dbi); +} + +int mdbx_txn_commit(MDBX_txn *txn) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED - MDBX_TXN_HAS_CHILD); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + MDBX_env *env = txn->mt_env; +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) { + env->me_flags |= MDBX_FATAL_ERROR; + return MDBX_PANIC; + } +#endif /* MDBX_TXN_CHECKPID */ + + /* mdbx_txn_end() mode for a commit which writes nothing */ + unsigned end_mode = + MDBX_END_EMPTY_COMMIT | MDBX_END_UPDATE | MDBX_END_SLOT | MDBX_END_FREE; + if (unlikely(F_ISSET(txn->mt_flags, MDBX_RDONLY))) + goto done; + + if (txn->mt_child) { + rc = mdbx_txn_commit(txn->mt_child); + mdbx_tassert(txn, txn->mt_child == NULL); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } + + if (unlikely(txn != env->me_txn)) { + mdbx_debug("%s", "attempt to commit unknown transaction"); + rc = MDBX_EINVAL; + goto fail; + } + + if (txn->mt_parent) { + MDBX_txn *const parent = txn->mt_parent; + mdbx_tassert(txn, mdbx_dirtylist_check(txn)); + + /* Preserve space for spill list to avoid parent's state corruption + * if allocation fails. */ + if (txn->tw.spill_pages && parent->tw.spill_pages) { + rc = mdbx_pnl_need(&parent->tw.spill_pages, + MDBX_PNL_SIZE(txn->tw.spill_pages)); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } + + //------------------------------------------------------------------------- + + parent->tw.lifo_reclaimed = txn->tw.lifo_reclaimed; + txn->tw.lifo_reclaimed = NULL; + + parent->tw.retired_pages = txn->tw.retired_pages; + txn->tw.retired_pages = NULL; + + mdbx_pnl_free(parent->tw.reclaimed_pglist); + parent->tw.reclaimed_pglist = txn->tw.reclaimed_pglist; + txn->tw.reclaimed_pglist = NULL; + parent->tw.last_reclaimed = txn->tw.last_reclaimed; + + parent->mt_geo = txn->mt_geo; + parent->mt_canary = txn->mt_canary; + parent->mt_flags = txn->mt_flags; + + /* Merge our cursors into parent's and close them */ + mdbx_cursors_eot(txn, 1); + + /* Update parent's DB table. */ + memcpy(parent->mt_dbs, txn->mt_dbs, txn->mt_numdbs * sizeof(MDBX_db)); + parent->mt_numdbs = txn->mt_numdbs; + parent->mt_dbflags[FREE_DBI] = txn->mt_dbflags[FREE_DBI]; + parent->mt_dbflags[MAIN_DBI] = txn->mt_dbflags[MAIN_DBI]; + for (unsigned i = CORE_DBS; i < txn->mt_numdbs; i++) { + /* preserve parent's DB_NEW status */ + parent->mt_dbflags[i] = + txn->mt_dbflags[i] | (parent->mt_dbflags[i] & (DB_CREAT | DB_FRESH)); + } + + /* Remove refunded pages from parent's dirty & spill lists */ + MDBX_DPL dst = mdbx_dpl_sort(parent->tw.dirtylist); + while (dst->length && dst[dst->length].pgno >= parent->mt_next_pgno) { + MDBX_page *mp = dst[dst->length].ptr; + if (mp && (txn->mt_env->me_flags & MDBX_WRITEMAP) == 0) + mdbx_dpage_free(txn->mt_env, mp, IS_OVERFLOW(mp) ? mp->mp_pages : 1); + dst->length -= 1; + } + parent->tw.dirtyroom += dst->sorted - dst->length; + dst->sorted = dst->length; + mdbx_tassert(parent, + parent->mt_parent || + parent->tw.dirtyroom + parent->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + + if (parent->tw.spill_pages && MDBX_PNL_SIZE(parent->tw.spill_pages) > 0 && + MDBX_PNL_MOST(parent->tw.spill_pages) >= parent->mt_next_pgno << 1) { + const MDBX_PNL ps = parent->tw.spill_pages; +#if MDBX_PNL_ASCENDING + unsigned i = MDBX_PNL_SIZE(ps); + assert(MDBX_PNL_MOST(ps) == MDBX_PNL_LAST(ps)); + do + i -= 1; + while (i && ps[i] >= parent->mt_next_pgno << 1); + MDBX_PNL_SIZE(ps) = i; +#else + assert(MDBX_PNL_MOST(ps) == MDBX_PNL_FIRST(ps)); + unsigned i = 1, len = MDBX_PNL_SIZE(ps); + while (i < len && ps[i + 1] >= parent->mt_next_pgno << 1) + ++i; + MDBX_PNL_SIZE(ps) = len -= i; + for (unsigned k = 1; k <= len; ++k) + ps[k] = ps[k + i]; +#endif + } + + /* Remove anything in our dirty list from parent's spill list */ + MDBX_DPL src = mdbx_dpl_sort(txn->tw.dirtylist); + if (likely(src->length > 0) && parent->tw.spill_pages && + MDBX_PNL_SIZE(parent->tw.spill_pages) > 0) { + MDBX_PNL sp = parent->tw.spill_pages; + assert(mdbx_pnl_check4assert(sp, txn->mt_next_pgno)); + + const unsigned len = MDBX_PNL_SIZE(parent->tw.spill_pages); + MDBX_PNL_SIZE(sp) = ~(pgno_t)0; + + /* Mark our dirty pages as deleted in parent spill list */ + unsigned r, w, i = 1; + w = r = len; + do { + pgno_t pn = src[i].pgno << 1; + while (pn > sp[r]) + r--; + if (pn == sp[r]) { + sp[r] = 1; + w = --r; + } + } while (++i <= src->length); + + /* Squash deleted pagenums if we deleted any */ + for (r = w; ++r <= len;) + if ((sp[r] & 1) == 0) + sp[++w] = sp[r]; + MDBX_PNL_SIZE(sp) = w; + assert(mdbx_pnl_check4assert(sp, txn->mt_next_pgno << 1)); + } + + /* Remove anything in our spill list from parent's dirty list */ + if (txn->tw.spill_pages && MDBX_PNL_SIZE(txn->tw.spill_pages) > 0) { + const MDBX_PNL sp = txn->tw.spill_pages; + mdbx_pnl_sort(sp); + /* Scanning in ascend order */ + const int step = MDBX_PNL_ASCENDING ? 1 : -1; + const int begin = MDBX_PNL_ASCENDING ? 1 : MDBX_PNL_SIZE(sp); + const int end = MDBX_PNL_ASCENDING ? MDBX_PNL_SIZE(sp) + 1 : 0; + mdbx_tassert(txn, sp[begin] <= sp[end - step]); + + unsigned r, w = r = mdbx_dpl_search(dst, sp[begin] >> 1); + mdbx_tassert(txn, dst->sorted == dst->length); + for (int i = begin; r <= dst->length;) { + mdbx_tassert(txn, (sp[i] & 1) == 0); + const pgno_t pgno = sp[i] >> 1; + if (dst[r].pgno < pgno) { + dst[w++] = dst[r++]; + } else if (dst[r].pgno > pgno) { + i += step; + if (i == end) + while (r <= dst->length) + dst[w++] = dst[r++]; + } else { + MDBX_page *dp = dst[r++].ptr; + if ((env->me_flags & MDBX_WRITEMAP) == 0) + mdbx_dpage_free(env, dp, IS_OVERFLOW(dp) ? dp->mp_pages : 1); + } + } + mdbx_tassert(txn, r == dst->length + 1); + dst->length = w; + parent->tw.dirtyroom += r - w; + } + assert(dst->sorted == dst->length); + mdbx_tassert(parent, + parent->mt_parent || + parent->tw.dirtyroom + parent->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + + unsigned d, s, l; + /* Find length of merging our dirty list with parent's */ + for (l = 0, d = dst->length, s = src->length; d > 0 && s > 0; ++l) { + const pgno_t s_pgno = src[s].pgno; + const pgno_t d_pgno = dst[d].pgno; + d -= d_pgno >= s_pgno; + s -= d_pgno <= s_pgno; + } + assert(dst->sorted == dst->length); + dst->sorted = l += d + s; + assert(dst->sorted >= dst->length); + parent->tw.dirtyroom -= dst->sorted - dst->length; + + /* Merge our dirty list into parent's */ + for (d = dst->length, s = src->length; d > 0 && s > 0; --l) { + if (dst[d].pgno > src[s].pgno) + dst[l] = dst[d--]; + else if (dst[d].pgno < src[s].pgno) + dst[l] = src[s--]; + else { + MDBX_page *dp = dst[d--].ptr; + if (dp && (env->me_flags & MDBX_WRITEMAP) == 0) + mdbx_dpage_free(env, dp, IS_OVERFLOW(dp) ? dp->mp_pgno : 1); + dst[l] = src[s--]; + } + } + if (s) { + do + dst[l--] = src[s--]; + while (s > 0); + } else if (d) { + do + dst[l--] = dst[d--]; + while (d > 0); + } + assert(l == 0); + dst->length = dst->sorted; + mdbx_free(txn->tw.dirtylist); + txn->tw.dirtylist = nullptr; + mdbx_tassert(parent, + parent->mt_parent || + parent->tw.dirtyroom + parent->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + + if (txn->tw.spill_pages) { + if (parent->tw.spill_pages) { + /* Must not fail since space was preserved above. */ + rc = mdbx_pnl_append_list(&parent->tw.spill_pages, txn->tw.spill_pages); + mdbx_assert(env, rc == MDBX_SUCCESS); + (void)rc; + mdbx_pnl_free(txn->tw.spill_pages); + mdbx_pnl_sort(parent->tw.spill_pages); + } else { + parent->tw.spill_pages = txn->tw.spill_pages; + } + } + if (parent->tw.spill_pages) + assert(mdbx_pnl_check4assert(parent->tw.spill_pages, + parent->mt_next_pgno << 1)); + + /* Append our loose page list to parent's */ + if (txn->tw.loose_pages) { + MDBX_page **lp = &parent->tw.loose_pages; + while (*lp) + lp = &(*lp)->mp_next; + *lp = txn->tw.loose_pages; + parent->tw.loose_count += txn->tw.loose_count; + } + if (txn->tw.retired2parent_pages) { + MDBX_page *mp = txn->tw.retired2parent_pages; + do { + MDBX_page *next = mp->mp_next; + rc = mdbx_page_loose(parent, mp); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + mp = next; + } while (mp); + } + + env->me_txn = parent; + parent->mt_child = NULL; + txn->mt_signature = 0; + mdbx_free(txn); + mdbx_tassert(parent, mdbx_dirtylist_check(parent)); + + /* Scan parent's loose page for suitable for refund */ + for (MDBX_page *mp = parent->tw.loose_pages; mp; mp = mp->mp_next) { + if (mp->mp_pgno == parent->mt_next_pgno - 1) { + mdbx_refund(parent); + break; + } + } + mdbx_tassert(parent, mdbx_dirtylist_check(parent)); + return MDBX_SUCCESS; + } + + mdbx_tassert(txn, txn->tw.dirtyroom + txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + mdbx_cursors_eot(txn, 0); + end_mode |= MDBX_END_EOTDONE; + + if (txn->tw.dirtylist->length == 0 && + (txn->mt_flags & (MDBX_TXN_DIRTY | MDBX_TXN_SPILLS)) == 0) + goto done; + + mdbx_debug("committing txn %" PRIaTXN " %p on mdbenv %p, root page %" PRIaPGNO + "/%" PRIaPGNO, + txn->mt_txnid, (void *)txn, (void *)env, + txn->mt_dbs[MAIN_DBI].md_root, txn->mt_dbs[FREE_DBI].md_root); + + /* Update DB root pointers */ + if (txn->mt_numdbs > CORE_DBS) { + MDBX_cursor mc; + MDBX_val data; + data.iov_len = sizeof(MDBX_db); + + rc = mdbx_cursor_init(&mc, txn, MAIN_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + for (MDBX_dbi i = CORE_DBS; i < txn->mt_numdbs; i++) { + if (txn->mt_dbflags[i] & DB_DIRTY) { + if (unlikely(TXN_DBI_CHANGED(txn, i))) { + rc = MDBX_BAD_DBI; + goto fail; + } + MDBX_db *db = &txn->mt_dbs[i]; + db->md_mod_txnid = txn->mt_txnid; + data.iov_base = db; + WITH_CURSOR_TRACKING(mc, + rc = mdbx_cursor_put(&mc, &txn->mt_dbxs[i].md_name, + &data, F_SUBDATA)); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } + } + } + + rc = mdbx_update_gc(txn); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + + if (mdbx_audit_enabled()) { + rc = mdbx_audit_ex(txn, MDBX_PNL_SIZE(txn->tw.retired_pages), true); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } + + rc = mdbx_page_flush(txn, 0); + if (likely(rc == MDBX_SUCCESS)) { + if (txn->mt_dbs[MAIN_DBI].md_flags & DB_DIRTY) + txn->mt_dbs[MAIN_DBI].md_mod_txnid = txn->mt_txnid; + + MDBX_meta meta, *head = mdbx_meta_head(env); + meta.mm_magic_and_version = head->mm_magic_and_version; + meta.mm_extra_flags = head->mm_extra_flags; + meta.mm_validator_id = head->mm_validator_id; + meta.mm_extra_pagehdr = head->mm_extra_pagehdr; + meta.mm_pages_retired = + head->mm_pages_retired + MDBX_PNL_SIZE(txn->tw.retired_pages); + + meta.mm_geo = txn->mt_geo; + meta.mm_dbs[FREE_DBI] = txn->mt_dbs[FREE_DBI]; + meta.mm_dbs[MAIN_DBI] = txn->mt_dbs[MAIN_DBI]; + meta.mm_canary = txn->mt_canary; + mdbx_meta_set_txnid(env, &meta, txn->mt_txnid); + + rc = mdbx_sync_locked( + env, env->me_flags | txn->mt_flags | MDBX_SHRINK_ALLOWED, &meta); + } + if (unlikely(rc != MDBX_SUCCESS)) { + env->me_flags |= MDBX_FATAL_ERROR; + goto fail; + } + + if (likely(env->me_lck)) + env->me_lck->mti_readers_refresh_flag = false; + end_mode = MDBX_END_COMMITTED | MDBX_END_UPDATE | MDBX_END_EOTDONE; + +done: + return mdbx_txn_end(txn, end_mode); + +fail: + mdbx_txn_abort(txn); + return rc; +} + +/* Read the environment parameters of a DB environment + * before mapping it into memory. */ +static int __cold mdbx_read_header(MDBX_env *env, MDBX_meta *dest, + uint64_t *filesize) { + int rc = mdbx_filesize(env->me_fd, filesize); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + memset(dest, 0, sizeof(MDBX_meta)); + dest->mm_datasync_sign = MDBX_DATASIGN_WEAK; + rc = MDBX_CORRUPTED; + + /* Read twice all meta pages so we can find the latest one. */ + unsigned loop_limit = NUM_METAS * 2; + for (unsigned loop_count = 0; loop_count < loop_limit; ++loop_count) { + /* We don't know the page size on first time. + * So, just guess it. */ + unsigned guess_pagesize = dest->mm_psize; + if (guess_pagesize == 0) + guess_pagesize = + (loop_count > NUM_METAS) ? env->me_psize : env->me_os_psize; + + const unsigned meta_number = loop_count % NUM_METAS; + const unsigned offset = guess_pagesize * meta_number; + + char buffer[MIN_PAGESIZE]; + unsigned retryleft = 42; + while (1) { + mdbx_trace("reading meta[%d]: offset %u, bytes %u, retry-left %u", + meta_number, offset, MIN_PAGESIZE, retryleft); + int err = mdbx_pread(env->me_fd, buffer, MIN_PAGESIZE, offset); + if (err != MDBX_SUCCESS) { + if (err == MDBX_ENODATA && offset == 0 && loop_count == 0 && + *filesize == 0 && (env->me_flags & MDBX_RDONLY) == 0) + mdbx_notice("read meta: empty file (%d, %s)", err, + mdbx_strerror(err)); + else + mdbx_error("read meta[%u,%u]: %i, %s", offset, MIN_PAGESIZE, err, + mdbx_strerror(err)); + return err; + } + + char again[MIN_PAGESIZE]; + err = mdbx_pread(env->me_fd, again, MIN_PAGESIZE, offset); + if (err != MDBX_SUCCESS) { + mdbx_error("read meta[%u,%u]: %i, %s", offset, MIN_PAGESIZE, err, + mdbx_strerror(err)); + return err; + } + + if (memcmp(buffer, again, MIN_PAGESIZE) == 0 || --retryleft == 0) + break; + + mdbx_verbose("meta[%u] was updated, re-read it", meta_number); + } + + MDBX_page *const page = (MDBX_page *)buffer; + MDBX_meta *const meta = page_meta(page); + if (meta->mm_magic_and_version != MDBX_DATA_MAGIC && + meta->mm_magic_and_version != MDBX_DATA_MAGIC_DEVEL) { + mdbx_error("meta[%u] has invalid magic/version %" PRIx64, meta_number, + meta->mm_magic_and_version); + return ((meta->mm_magic_and_version >> 8) != MDBX_MAGIC) + ? MDBX_INVALID + : MDBX_VERSION_MISMATCH; + } + + if (page->mp_pgno != meta_number) { + mdbx_error("meta[%u] has invalid pageno %" PRIaPGNO, meta_number, + page->mp_pgno); + return MDBX_INVALID; + } + + if (page->mp_flags != P_META) { + mdbx_error("page #%u not a meta-page", meta_number); + return MDBX_INVALID; + } + + if (!retryleft) { + mdbx_error("meta[%u] is too volatile, skip it", meta_number); + continue; + } + + /* LY: check pagesize */ + if (!is_powerof2(meta->mm_psize) || meta->mm_psize < MIN_PAGESIZE || + meta->mm_psize > MAX_PAGESIZE) { + mdbx_notice("meta[%u] has invalid pagesize (%u), skip it", meta_number, + meta->mm_psize); + rc = is_powerof2(meta->mm_psize) ? MDBX_VERSION_MISMATCH : MDBX_INVALID; + continue; + } + + if (meta_number == 0 && guess_pagesize != meta->mm_psize) { + dest->mm_psize = meta->mm_psize; + mdbx_verbose("meta[%u] took pagesize %u", meta_number, meta->mm_psize); + } + + if (safe64_read(&meta->mm_txnid_a) != safe64_read(&meta->mm_txnid_b)) { + mdbx_warning("meta[%u] not completely updated, skip it", meta_number); + continue; + } + + /* LY: check signature as a checksum */ + if (META_IS_STEADY(meta) && + meta->mm_datasync_sign != mdbx_meta_sign(meta)) { + mdbx_notice("meta[%u] has invalid steady-checksum (0x%" PRIx64 + " != 0x%" PRIx64 "), skip it", + meta_number, meta->mm_datasync_sign, mdbx_meta_sign(meta)); + continue; + } + + mdbx_debug("read meta%" PRIaPGNO " = root %" PRIaPGNO "/%" PRIaPGNO + ", geo %" PRIaPGNO "/%" PRIaPGNO "-%" PRIaPGNO "/%" PRIaPGNO + " +%u -%u, txn_id %" PRIaTXN ", %s", + page->mp_pgno, meta->mm_dbs[MAIN_DBI].md_root, + meta->mm_dbs[FREE_DBI].md_root, meta->mm_geo.lower, + meta->mm_geo.next, meta->mm_geo.now, meta->mm_geo.upper, + meta->mm_geo.grow, meta->mm_geo.shrink, + meta->mm_txnid_a.inconsistent, mdbx_durable_str(meta)); + + /* LY: check min-pages value */ + if (meta->mm_geo.lower < MIN_PAGENO || meta->mm_geo.lower > MAX_PAGENO) { + mdbx_notice("meta[%u] has invalid min-pages (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.lower); + rc = MDBX_INVALID; + continue; + } + + /* LY: check max-pages value */ + if (meta->mm_geo.upper < MIN_PAGENO || meta->mm_geo.upper > MAX_PAGENO || + meta->mm_geo.upper < meta->mm_geo.lower) { + mdbx_notice("meta[%u] has invalid max-pages (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.upper); + rc = MDBX_INVALID; + continue; + } + + /* LY: check last_pgno */ + if (meta->mm_geo.next < MIN_PAGENO || meta->mm_geo.next - 1 > MAX_PAGENO) { + mdbx_notice("meta[%u] has invalid next-pageno (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.next); + rc = MDBX_CORRUPTED; + continue; + } + + /* LY: check filesize & used_bytes */ + const uint64_t used_bytes = meta->mm_geo.next * (uint64_t)meta->mm_psize; + if (used_bytes > *filesize) { + /* Here could be a race with DB-shrinking performed by other process */ + rc = mdbx_filesize(env->me_fd, filesize); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + if (used_bytes > *filesize) { + mdbx_notice("meta[%u] used-bytes (%" PRIu64 + ") beyond filesize (%" PRIu64 "), skip it", + meta_number, used_bytes, *filesize); + rc = MDBX_CORRUPTED; + continue; + } + } + + /* LY: check mapsize limits */ + const uint64_t mapsize_min = meta->mm_geo.lower * (uint64_t)meta->mm_psize; + STATIC_ASSERT(MAX_MAPSIZE < PTRDIFF_MAX - MAX_PAGESIZE); + STATIC_ASSERT(MIN_MAPSIZE < MAX_MAPSIZE); + if (mapsize_min < MIN_MAPSIZE || mapsize_min > MAX_MAPSIZE) { + mdbx_notice("meta[%u] has invalid min-mapsize (%" PRIu64 "), skip it", + meta_number, mapsize_min); + rc = MDBX_VERSION_MISMATCH; + continue; + } + + const uint64_t mapsize_max = meta->mm_geo.upper * (uint64_t)meta->mm_psize; + STATIC_ASSERT(MIN_MAPSIZE < MAX_MAPSIZE); + if (mapsize_max > MAX_MAPSIZE || + MAX_PAGENO < roundup_powerof2((size_t)mapsize_max, env->me_os_psize) / + (size_t)meta->mm_psize) { + if (meta->mm_geo.next - 1 > MAX_PAGENO || used_bytes > MAX_MAPSIZE) { + mdbx_notice("meta[%u] has too large max-mapsize (%" PRIu64 "), skip it", + meta_number, mapsize_max); + rc = MDBX_TOO_LARGE; + continue; + } + + /* allow to open large DB from a 32-bit environment */ + mdbx_notice("meta[%u] has too large max-mapsize (%" PRIu64 "), " + "but size of used space still acceptable (%" PRIu64 ")", + meta_number, mapsize_max, used_bytes); + meta->mm_geo.upper = (pgno_t)(MAX_MAPSIZE / meta->mm_psize); + } + + /* LY: check and silently put mm_geo.now into [geo.lower...geo.upper]. + * + * Copy-with-compaction by previous version of libmdbx could produce DB-file + * less than meta.geo.lower bound, in case actual filling is low or no data + * at all. This is not a problem as there is no damage or loss of data. + * Therefore it is better not to consider such situation as an error, but + * silently correct it. */ + if (meta->mm_geo.now < meta->mm_geo.lower) + meta->mm_geo.now = meta->mm_geo.lower; + if (meta->mm_geo.now > meta->mm_geo.upper && + meta->mm_geo.next <= meta->mm_geo.upper) + meta->mm_geo.now = meta->mm_geo.upper; + + if (meta->mm_geo.next > meta->mm_geo.now) { + mdbx_notice("meta[%u] next-pageno (%" PRIaPGNO + ") is beyond end-pgno (%" PRIaPGNO "), skip it", + meta_number, meta->mm_geo.next, meta->mm_geo.now); + rc = MDBX_CORRUPTED; + continue; + } + + /* LY: GC root */ + if (meta->mm_dbs[FREE_DBI].md_root == P_INVALID) { + if (meta->mm_dbs[FREE_DBI].md_branch_pages || + meta->mm_dbs[FREE_DBI].md_depth || + meta->mm_dbs[FREE_DBI].md_entries || + meta->mm_dbs[FREE_DBI].md_leaf_pages || + meta->mm_dbs[FREE_DBI].md_overflow_pages) { + mdbx_notice("meta[%u] has false-empty GC, skip it", meta_number); + rc = MDBX_CORRUPTED; + continue; + } + } else if (meta->mm_dbs[FREE_DBI].md_root >= meta->mm_geo.next) { + mdbx_notice("meta[%u] has invalid GC-root %" PRIaPGNO ", skip it", + meta_number, meta->mm_dbs[FREE_DBI].md_root); + rc = MDBX_CORRUPTED; + continue; + } + + /* LY: MainDB root */ + if (meta->mm_dbs[MAIN_DBI].md_root == P_INVALID) { + if (meta->mm_dbs[MAIN_DBI].md_branch_pages || + meta->mm_dbs[MAIN_DBI].md_depth || + meta->mm_dbs[MAIN_DBI].md_entries || + meta->mm_dbs[MAIN_DBI].md_leaf_pages || + meta->mm_dbs[MAIN_DBI].md_overflow_pages) { + mdbx_notice("meta[%u] has false-empty maindb", meta_number); + rc = MDBX_CORRUPTED; + continue; + } + } else if (meta->mm_dbs[MAIN_DBI].md_root >= meta->mm_geo.next) { + mdbx_notice("meta[%u] has invalid maindb-root %" PRIaPGNO ", skip it", + meta_number, meta->mm_dbs[MAIN_DBI].md_root); + rc = MDBX_CORRUPTED; + continue; + } + + if (safe64_read(&meta->mm_txnid_a) == 0) { + mdbx_warning("meta[%u] has zero txnid, skip it", meta_number); + continue; + } + + if (mdbx_meta_ot(prefer_noweak, env, dest, meta)) { + *dest = *meta; + if (META_IS_WEAK(dest)) + loop_limit += 1; /* LY: should re-read to hush race with update */ + mdbx_verbose("latch meta[%u]", meta_number); + } + } + + if (META_IS_WEAK(dest)) { + mdbx_error("%s", "no usable meta-pages, database is corrupted"); + return rc; + } + + return MDBX_SUCCESS; +} + +static MDBX_page *__cold mdbx_meta_model(const MDBX_env *env, MDBX_page *model, + unsigned num) { + + mdbx_ensure(env, is_powerof2(env->me_psize)); + mdbx_ensure(env, env->me_psize >= MIN_PAGESIZE); + mdbx_ensure(env, env->me_psize <= MAX_PAGESIZE); + mdbx_ensure(env, env->me_dbgeo.lower >= MIN_MAPSIZE); + mdbx_ensure(env, env->me_dbgeo.upper <= MAX_MAPSIZE); + mdbx_ensure(env, env->me_dbgeo.now >= env->me_dbgeo.lower); + mdbx_ensure(env, env->me_dbgeo.now <= env->me_dbgeo.upper); + + memset(model, 0, sizeof(*model)); + model->mp_pgno = num; + model->mp_flags = P_META; + MDBX_meta *const model_meta = page_meta(model); + model_meta->mm_magic_and_version = MDBX_DATA_MAGIC; + + model_meta->mm_geo.lower = bytes2pgno(env, env->me_dbgeo.lower); + model_meta->mm_geo.upper = bytes2pgno(env, env->me_dbgeo.upper); + model_meta->mm_geo.grow = (uint16_t)bytes2pgno(env, env->me_dbgeo.grow); + model_meta->mm_geo.shrink = (uint16_t)bytes2pgno(env, env->me_dbgeo.shrink); + model_meta->mm_geo.now = bytes2pgno(env, env->me_dbgeo.now); + model_meta->mm_geo.next = NUM_METAS; + + mdbx_ensure(env, model_meta->mm_geo.lower >= MIN_PAGENO); + mdbx_ensure(env, model_meta->mm_geo.upper <= MAX_PAGENO); + mdbx_ensure(env, model_meta->mm_geo.now >= model_meta->mm_geo.lower); + mdbx_ensure(env, model_meta->mm_geo.now <= model_meta->mm_geo.upper); + mdbx_ensure(env, model_meta->mm_geo.next >= MIN_PAGENO); + mdbx_ensure(env, model_meta->mm_geo.next <= model_meta->mm_geo.now); + mdbx_ensure(env, + model_meta->mm_geo.grow == bytes2pgno(env, env->me_dbgeo.grow)); + mdbx_ensure(env, model_meta->mm_geo.shrink == + bytes2pgno(env, env->me_dbgeo.shrink)); + + model_meta->mm_psize = env->me_psize; + model_meta->mm_flags = (uint16_t)env->me_flags; + model_meta->mm_flags |= + MDBX_INTEGERKEY; /* this is mm_dbs[FREE_DBI].md_flags */ + model_meta->mm_dbs[FREE_DBI].md_root = P_INVALID; + model_meta->mm_dbs[MAIN_DBI].md_root = P_INVALID; + mdbx_meta_set_txnid(env, model_meta, MIN_TXNID + num); + model_meta->mm_datasync_sign = mdbx_meta_sign(model_meta); + return (MDBX_page *)((uint8_t *)model + env->me_psize); +} + +/* Fill in most of the zeroed meta-pages for an empty database environment. + * Return pointer to recenly (head) meta-page. */ +static MDBX_meta *__cold mdbx_init_metas(const MDBX_env *env, void *buffer) { + MDBX_page *page0 = (MDBX_page *)buffer; + MDBX_page *page1 = mdbx_meta_model(env, page0, 0); + MDBX_page *page2 = mdbx_meta_model(env, page1, 1); + mdbx_meta_model(env, page2, 2); + mdbx_assert(env, !mdbx_meta_eq(env, page_meta(page0), page_meta(page1))); + mdbx_assert(env, !mdbx_meta_eq(env, page_meta(page1), page_meta(page2))); + mdbx_assert(env, !mdbx_meta_eq(env, page_meta(page2), page_meta(page0))); + return page_meta(page2); +} + +static int mdbx_sync_locked(MDBX_env *env, unsigned flags, + MDBX_meta *const pending) { + mdbx_assert(env, ((env->me_flags ^ flags) & MDBX_WRITEMAP) == 0); + MDBX_meta *const meta0 = METAPAGE(env, 0); + MDBX_meta *const meta1 = METAPAGE(env, 1); + MDBX_meta *const meta2 = METAPAGE(env, 2); + MDBX_meta *const head = mdbx_meta_head(env); + + mdbx_assert(env, mdbx_meta_eq_mask(env) == 0); + mdbx_assert(env, + pending < METAPAGE(env, 0) || pending > METAPAGE(env, NUM_METAS)); + mdbx_assert(env, (env->me_flags & (MDBX_RDONLY | MDBX_FATAL_ERROR)) == 0); + mdbx_assert(env, !META_IS_STEADY(head) || *env->me_unsynced_pages != 0); + mdbx_assert(env, pending->mm_geo.next <= pending->mm_geo.now); + + if (flags & (MDBX_NOSYNC | MDBX_MAPASYNC)) { + /* Check auto-sync conditions */ + const pgno_t autosync_threshold = *env->me_autosync_threshold; + const uint64_t autosync_period = *env->me_autosync_period; + if ((autosync_threshold && *env->me_unsynced_pages >= autosync_threshold) || + (autosync_period && + mdbx_osal_monotime() - *env->me_sync_timestamp >= autosync_period)) + flags &= MDBX_WRITEMAP | MDBX_SHRINK_ALLOWED; /* force steady */ + } + + pgno_t shrink = 0; + if (flags & MDBX_SHRINK_ALLOWED) { + /* LY: check conditions to discard unused pages */ + const pgno_t largest_pgno = mdbx_find_largest( + env, (head->mm_geo.next > pending->mm_geo.next) ? head->mm_geo.next + : pending->mm_geo.next); + mdbx_assert(env, largest_pgno >= NUM_METAS); +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) + const pgno_t edge = env->me_poison_edge; + if (edge > largest_pgno) { + env->me_poison_edge = largest_pgno; + VALGRIND_MAKE_MEM_NOACCESS(env->me_map + pgno2bytes(env, largest_pgno), + pgno2bytes(env, edge - largest_pgno)); + ASAN_POISON_MEMORY_REGION(env->me_map + pgno2bytes(env, largest_pgno), + pgno2bytes(env, edge - largest_pgno)); + } +#endif /* MDBX_USE_VALGRIND */ +#if defined(MADV_DONTNEED) + const size_t largest_aligned2os_bytes = + pgno_align2os_bytes(env, largest_pgno); + const pgno_t largest_aligned2os_pgno = + bytes2pgno(env, largest_aligned2os_bytes); + const pgno_t prev_discarded_pgno = *env->me_discarded_tail; + if (prev_discarded_pgno > + largest_aligned2os_pgno + + /* 1M threshold to avoid unreasonable madvise() call */ + bytes2pgno(env, MEGABYTE)) { + mdbx_notice("open-MADV_%s %u..%u", "DONTNEED", *env->me_discarded_tail, + largest_pgno); + *env->me_discarded_tail = largest_aligned2os_pgno; + const size_t prev_discarded_bytes = + pgno2bytes(env, prev_discarded_pgno) & ~(env->me_os_psize - 1); + mdbx_ensure(env, prev_discarded_bytes > largest_aligned2os_bytes); + int advise = MADV_DONTNEED; +#if defined(MADV_FREE) && \ + 0 /* MADV_FREE works for only anon vma at the moment */ + if ((env->me_flags & MDBX_WRITEMAP) && + mdbx_linux_kernel_version > 0x04050000) + advise = MADV_FREE; +#endif /* MADV_FREE */ + int err = madvise(env->me_map + largest_aligned2os_bytes, + prev_discarded_bytes - largest_aligned2os_bytes, advise) + ? errno + : MDBX_SUCCESS; + mdbx_assert(env, err == MDBX_SUCCESS); + (void)err; + } +#endif /* MADV_FREE || MADV_DONTNEED */ + + /* LY: check conditions to shrink datafile */ + const pgno_t backlog_gap = + pending->mm_dbs[FREE_DBI].md_depth + mdbx_backlog_extragap(env); + if (pending->mm_geo.shrink && pending->mm_geo.now - pending->mm_geo.next > + pending->mm_geo.shrink + backlog_gap) { + if (pending->mm_geo.now > largest_pgno && + pending->mm_geo.now - largest_pgno > + pending->mm_geo.shrink + backlog_gap) { + const pgno_t aligner = pending->mm_geo.grow ? pending->mm_geo.grow + : pending->mm_geo.shrink; + const pgno_t with_backlog_gap = largest_pgno + backlog_gap; + const pgno_t aligned = pgno_align2os_pgno( + env, with_backlog_gap + aligner - with_backlog_gap % aligner); + const pgno_t bottom = + (aligned > pending->mm_geo.lower) ? aligned : pending->mm_geo.lower; + if (pending->mm_geo.now > bottom) { + flags &= MDBX_WRITEMAP | MDBX_SHRINK_ALLOWED; /* force steady */ + shrink = pending->mm_geo.now - bottom; + pending->mm_geo.now = bottom; + if (mdbx_meta_txnid_stable(env, head) == + pending->mm_txnid_a.inconsistent) + mdbx_meta_set_txnid( + env, pending, + safe64_txnid_next(pending->mm_txnid_a.inconsistent)); + } + } + } + } + + /* LY: step#1 - sync previously written/updated data-pages */ + int rc = *env->me_unsynced_pages ? MDBX_RESULT_TRUE /* carry non-steady */ + : MDBX_RESULT_FALSE /* carry steady */; + if (rc != MDBX_RESULT_FALSE && (flags & MDBX_NOSYNC) == 0) { + mdbx_assert(env, ((flags ^ env->me_flags) & MDBX_WRITEMAP) == 0); + MDBX_meta *const recent_steady_meta = mdbx_meta_steady(env); + if (flags & MDBX_WRITEMAP) { + const size_t usedbytes = pgno_align2os_bytes(env, pending->mm_geo.next); + rc = mdbx_msync(&env->me_dxb_mmap, 0, usedbytes, flags & MDBX_MAPASYNC); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + rc = MDBX_RESULT_TRUE /* carry non-steady */; + if ((flags & MDBX_MAPASYNC) == 0) { + if (unlikely(pending->mm_geo.next > recent_steady_meta->mm_geo.now)) { + rc = mdbx_filesync(env->me_fd, MDBX_SYNC_SIZE); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } + rc = MDBX_RESULT_FALSE /* carry steady */; + } + } else { + rc = mdbx_filesync(env->me_fd, + (pending->mm_geo.next > recent_steady_meta->mm_geo.now) + ? MDBX_SYNC_DATA | MDBX_SYNC_SIZE + : MDBX_SYNC_DATA); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } + } + + /* Steady or Weak */ + if (rc == MDBX_RESULT_FALSE /* carry steady */) { + pending->mm_datasync_sign = mdbx_meta_sign(pending); + *env->me_unsynced_pages = 0; + *env->me_sync_timestamp = mdbx_osal_monotime(); + } else { + assert(rc == MDBX_RESULT_TRUE /* carry non-steady */); + pending->mm_datasync_sign = F_ISSET(env->me_flags, MDBX_UTTERLY_NOSYNC) + ? MDBX_DATASIGN_NONE + : MDBX_DATASIGN_WEAK; + } + + MDBX_meta *target = nullptr; + if (mdbx_meta_txnid_stable(env, head) == pending->mm_txnid_a.inconsistent) { + mdbx_assert(env, memcmp(&head->mm_dbs, &pending->mm_dbs, + sizeof(head->mm_dbs)) == 0); + mdbx_assert(env, memcmp(&head->mm_canary, &pending->mm_canary, + sizeof(head->mm_canary)) == 0); + mdbx_assert(env, memcmp(&head->mm_geo, &pending->mm_geo, + sizeof(pending->mm_geo)) == 0); + if (!META_IS_STEADY(head) && META_IS_STEADY(pending)) + target = head; + else { + mdbx_ensure(env, mdbx_meta_eq(env, head, pending)); + mdbx_debug("%s", "skip update meta"); + return MDBX_SUCCESS; + } + } else if (head == meta0) + target = mdbx_meta_ancient(prefer_steady, env, meta1, meta2); + else if (head == meta1) + target = mdbx_meta_ancient(prefer_steady, env, meta0, meta2); + else { + mdbx_assert(env, head == meta2); + target = mdbx_meta_ancient(prefer_steady, env, meta0, meta1); + } + + /* LY: step#2 - update meta-page. */ + mdbx_debug("writing meta%" PRIaPGNO " = root %" PRIaPGNO "/%" PRIaPGNO + ", geo %" PRIaPGNO "/%" PRIaPGNO "-%" PRIaPGNO "/%" PRIaPGNO + " +%u -%u, txn_id %" PRIaTXN ", %s", + data_page(target)->mp_pgno, pending->mm_dbs[MAIN_DBI].md_root, + pending->mm_dbs[FREE_DBI].md_root, pending->mm_geo.lower, + pending->mm_geo.next, pending->mm_geo.now, pending->mm_geo.upper, + pending->mm_geo.grow, pending->mm_geo.shrink, + pending->mm_txnid_a.inconsistent, mdbx_durable_str(pending)); + + mdbx_debug("meta0: %s, %s, txn_id %" PRIaTXN ", root %" PRIaPGNO + "/%" PRIaPGNO, + (meta0 == head) ? "head" : (meta0 == target) ? "tail" : "stay", + mdbx_durable_str(meta0), mdbx_meta_txnid_fluid(env, meta0), + meta0->mm_dbs[MAIN_DBI].md_root, meta0->mm_dbs[FREE_DBI].md_root); + mdbx_debug("meta1: %s, %s, txn_id %" PRIaTXN ", root %" PRIaPGNO + "/%" PRIaPGNO, + (meta1 == head) ? "head" : (meta1 == target) ? "tail" : "stay", + mdbx_durable_str(meta1), mdbx_meta_txnid_fluid(env, meta1), + meta1->mm_dbs[MAIN_DBI].md_root, meta1->mm_dbs[FREE_DBI].md_root); + mdbx_debug("meta2: %s, %s, txn_id %" PRIaTXN ", root %" PRIaPGNO + "/%" PRIaPGNO, + (meta2 == head) ? "head" : (meta2 == target) ? "tail" : "stay", + mdbx_durable_str(meta2), mdbx_meta_txnid_fluid(env, meta2), + meta2->mm_dbs[MAIN_DBI].md_root, meta2->mm_dbs[FREE_DBI].md_root); + + mdbx_assert(env, !mdbx_meta_eq(env, pending, meta0)); + mdbx_assert(env, !mdbx_meta_eq(env, pending, meta1)); + mdbx_assert(env, !mdbx_meta_eq(env, pending, meta2)); + + mdbx_assert(env, ((env->me_flags ^ flags) & MDBX_WRITEMAP) == 0); + mdbx_ensure(env, target == head || mdbx_meta_txnid_stable(env, target) < + pending->mm_txnid_a.inconsistent); + if (env->me_flags & MDBX_WRITEMAP) { + mdbx_jitter4testing(true); + if (likely(target != head)) { + /* LY: 'invalidate' the meta. */ + mdbx_meta_update_begin(env, target, pending->mm_txnid_a.inconsistent); + target->mm_datasync_sign = MDBX_DATASIGN_WEAK; +#ifndef NDEBUG + /* debug: provoke failure to catch a violators, but don't touch mm_psize + * and mm_flags to allow readers catch actual pagesize. */ + uint8_t *provoke_begin = (uint8_t *)&target->mm_dbs[FREE_DBI].md_root; + uint8_t *provoke_end = (uint8_t *)&target->mm_datasync_sign; + memset(provoke_begin, 0xCC, provoke_end - provoke_begin); + mdbx_jitter4testing(false); +#endif + + /* LY: update info */ + target->mm_geo = pending->mm_geo; + target->mm_dbs[FREE_DBI] = pending->mm_dbs[FREE_DBI]; + target->mm_dbs[MAIN_DBI] = pending->mm_dbs[MAIN_DBI]; + target->mm_canary = pending->mm_canary; + target->mm_pages_retired = pending->mm_pages_retired; + mdbx_jitter4testing(true); + mdbx_flush_noncoherent_cpu_writeback(); + + /* LY: 'commit' the meta */ + mdbx_meta_update_end(env, target, pending->mm_txnid_b.inconsistent); + mdbx_jitter4testing(true); + } else { + /* dangerous case (target == head), only mm_datasync_sign could + * me updated, check assertions once again */ + mdbx_ensure(env, mdbx_meta_txnid_stable(env, head) == + pending->mm_txnid_a.inconsistent && + !META_IS_STEADY(head) && META_IS_STEADY(pending)); + mdbx_ensure(env, memcmp(&head->mm_geo, &pending->mm_geo, + sizeof(head->mm_geo)) == 0); + mdbx_ensure(env, memcmp(&head->mm_dbs, &pending->mm_dbs, + sizeof(head->mm_dbs)) == 0); + mdbx_ensure(env, memcmp(&head->mm_canary, &pending->mm_canary, + sizeof(head->mm_canary)) == 0); + } + target->mm_datasync_sign = pending->mm_datasync_sign; + mdbx_flush_noncoherent_cpu_writeback(); + mdbx_jitter4testing(true); + } else { + rc = mdbx_pwrite(env->me_fd, pending, sizeof(MDBX_meta), + (uint8_t *)target - env->me_map); + if (unlikely(rc != MDBX_SUCCESS)) { + undo: + mdbx_debug("%s", "write failed, disk error?"); + /* On a failure, the pagecache still contains the new data. + * Try write some old data back, to prevent it from being used. */ + mdbx_pwrite(env->me_fd, (void *)target, sizeof(MDBX_meta), + (uint8_t *)target - env->me_map); + goto fail; + } + mdbx_invalidate_mmap_noncoherent_cache(target, sizeof(MDBX_meta)); + } + + /* LY: step#3 - sync meta-pages. */ + mdbx_assert(env, ((env->me_flags ^ flags) & MDBX_WRITEMAP) == 0); + if ((flags & (MDBX_NOSYNC | MDBX_NOMETASYNC)) == 0) { + mdbx_assert(env, ((flags ^ env->me_flags) & MDBX_WRITEMAP) == 0); + if (flags & MDBX_WRITEMAP) { + const size_t offset = (uint8_t *)data_page(head) - env->me_dxb_mmap.dxb; + const size_t paged_offset = offset & ~(env->me_os_psize - 1); + const size_t paged_length = roundup_powerof2( + env->me_psize + offset - paged_offset, env->me_os_psize); + rc = mdbx_msync(&env->me_dxb_mmap, paged_offset, paged_length, + flags & MDBX_MAPASYNC); + if (unlikely(rc != MDBX_SUCCESS)) + goto fail; + } else { + rc = mdbx_filesync(env->me_fd, MDBX_SYNC_DATA | MDBX_SYNC_IODQ); + if (rc != MDBX_SUCCESS) + goto undo; + } + *env->me_meta_sync_txnid = (uint32_t)pending->mm_txnid_a.inconsistent; + } + + /* LY: shrink datafile if needed */ + if (unlikely(shrink)) { + mdbx_verbose("shrink to %" PRIaPGNO " pages (-%" PRIaPGNO ")", + pending->mm_geo.now, shrink); + rc = mdbx_mapresize(env, pending->mm_geo.now, pending->mm_geo.upper); + if (MDBX_IS_ERROR(rc)) + goto fail; + } + + return MDBX_SUCCESS; + +fail: + env->me_flags |= MDBX_FATAL_ERROR; + return rc; +} + +int __cold mdbx_env_get_maxkeysize(MDBX_env *env) { + if (!env || env->me_signature != MDBX_ME_SIGNATURE || !env->me_maxkey_limit) + return (MDBX_EINVAL > 0) ? -MDBX_EINVAL : MDBX_EINVAL; + return env->me_maxkey_limit; +} + +#define mdbx_nodemax(pagesize) \ + (((((pagesize)-PAGEHDRSZ) / MDBX_MINKEYS) & ~(uintptr_t)1) - sizeof(indx_t)) + +#define mdbx_maxkey(nodemax) ((nodemax)-NODESIZE - sizeof(MDBX_db)) + +#define mdbx_maxgc_ov1page(pagesize) \ + (((pagesize)-PAGEHDRSZ) / sizeof(pgno_t) - 1) + +static void __cold mdbx_setup_pagesize(MDBX_env *env, const size_t pagesize) { + STATIC_ASSERT(PTRDIFF_MAX > MAX_MAPSIZE); + STATIC_ASSERT(MIN_PAGESIZE > sizeof(MDBX_page) + sizeof(MDBX_meta)); + mdbx_ensure(env, is_powerof2(pagesize)); + mdbx_ensure(env, pagesize >= MIN_PAGESIZE); + mdbx_ensure(env, pagesize <= MAX_PAGESIZE); + env->me_psize = (unsigned)pagesize; + + STATIC_ASSERT(mdbx_maxgc_ov1page(MIN_PAGESIZE) > 4); + STATIC_ASSERT(mdbx_maxgc_ov1page(MAX_PAGESIZE) < MDBX_DPL_TXNFULL); + const intptr_t maxgc_ov1page = (pagesize - PAGEHDRSZ) / sizeof(pgno_t) - 1; + mdbx_ensure(env, + maxgc_ov1page > 42 && maxgc_ov1page < (intptr_t)MDBX_DPL_TXNFULL); + env->me_maxgc_ov1page = (unsigned)maxgc_ov1page; + + STATIC_ASSERT(mdbx_nodemax(MIN_PAGESIZE) > 42); + STATIC_ASSERT(mdbx_nodemax(MAX_PAGESIZE) < UINT16_MAX); + const intptr_t nodemax = mdbx_nodemax(pagesize); + mdbx_ensure(env, nodemax > 42 && nodemax < UINT16_MAX && nodemax % 2 == 0); + env->me_nodemax = (unsigned)nodemax; + + STATIC_ASSERT(mdbx_maxkey(MIN_PAGESIZE) > 42); + STATIC_ASSERT(mdbx_maxkey(MIN_PAGESIZE) < MIN_PAGESIZE); + STATIC_ASSERT(mdbx_maxkey(MAX_PAGESIZE) > 42); + STATIC_ASSERT(mdbx_maxkey(MAX_PAGESIZE) < MAX_PAGESIZE); + const intptr_t maxkey_limit = mdbx_maxkey(env->me_nodemax); + mdbx_ensure(env, maxkey_limit > 42 && (size_t)maxkey_limit < pagesize && + maxkey_limit % 2 == 0); + env->me_maxkey_limit = (unsigned)maxkey_limit; + + env->me_psize2log = log2n(pagesize); + mdbx_assert(env, pgno2bytes(env, 1) == pagesize); + mdbx_assert(env, bytes2pgno(env, pagesize + pagesize) == 2); +} + +int __cold mdbx_env_create(MDBX_env **penv) { + MDBX_env *env = mdbx_calloc(1, sizeof(MDBX_env)); + if (unlikely(!env)) + return MDBX_ENOMEM; + + env->me_maxreaders = DEFAULT_READERS; + env->me_maxdbs = env->me_numdbs = CORE_DBS; + env->me_fd = INVALID_HANDLE_VALUE; + env->me_lfd = INVALID_HANDLE_VALUE; + env->me_pid = mdbx_getpid(); + + int rc; + const size_t os_psize = mdbx_syspagesize(); + if (unlikely(!is_powerof2(os_psize) || os_psize < MIN_PAGESIZE)) { + mdbx_error("unsuitable system pagesize %" PRIuPTR, os_psize); + rc = MDBX_INCOMPATIBLE; + goto bailout; + } + env->me_os_psize = (unsigned)os_psize; + mdbx_setup_pagesize(env, env->me_os_psize); + + rc = mdbx_fastmutex_init(&env->me_dbi_lock); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + +#if defined(_WIN32) || defined(_WIN64) + mdbx_srwlock_Init(&env->me_remap_guard); + InitializeCriticalSection(&env->me_windowsbug_lock); +#else + rc = mdbx_fastmutex_init(&env->me_remap_guard); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_fastmutex_destroy(&env->me_dbi_lock); + goto bailout; + } + rc = mdbx_fastmutex_init(&env->me_lckless_stub.wmutex); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_fastmutex_destroy(&env->me_remap_guard); + mdbx_fastmutex_destroy(&env->me_dbi_lock); + goto bailout; + } +#endif /* Windows */ + + VALGRIND_CREATE_MEMPOOL(env, 0, 0); + env->me_signature = MDBX_ME_SIGNATURE; + *penv = env; + return MDBX_SUCCESS; + +bailout: + mdbx_free(env); + *penv = nullptr; + return rc; +} + +__cold LIBMDBX_API int +mdbx_env_set_geometry(MDBX_env *env, intptr_t size_lower, intptr_t size_now, + intptr_t size_upper, intptr_t growth_step, + intptr_t shrink_threshold, intptr_t pagesize) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) + env->me_flags |= MDBX_FATAL_ERROR; +#endif /* MDBX_TXN_CHECKPID */ + + if (unlikely(env->me_flags & MDBX_FATAL_ERROR)) + return MDBX_PANIC; + + const bool inside_txn = + (env->me_txn0 && env->me_txn0->mt_owner == mdbx_thread_self()); + +#if MDBX_DEBUG + if (growth_step < 0) + growth_step = 1; + if (shrink_threshold < 0) + shrink_threshold = 1; +#endif + + bool need_unlock = false; + int rc = MDBX_PROBLEM; + if (env->me_map) { + /* env already mapped */ + if (unlikely(env->me_flags & MDBX_RDONLY)) + return MDBX_EACCESS; + + if (!inside_txn) { + int err = mdbx_txn_lock(env, false); + if (unlikely(err != MDBX_SUCCESS)) + return err; + need_unlock = true; + } + MDBX_meta *head = mdbx_meta_head(env); + if (!inside_txn) { + env->me_txn0->mt_txnid = meta_txnid(env, head, false); + mdbx_find_oldest(env->me_txn0); + } + + /* get untouched params from DB */ + if (pagesize < 0) + pagesize = env->me_psize; + if (size_lower < 0) + size_lower = pgno2bytes(env, head->mm_geo.lower); + if (size_now < 0) + size_now = pgno2bytes(env, head->mm_geo.now); + if (size_upper < 0) + size_upper = pgno2bytes(env, head->mm_geo.upper); + if (growth_step < 0) + growth_step = pgno2bytes(env, head->mm_geo.grow); + if (shrink_threshold < 0) + shrink_threshold = pgno2bytes(env, head->mm_geo.shrink); + + if (pagesize != (intptr_t)env->me_psize) { + rc = MDBX_EINVAL; + goto bailout; + } + const size_t usedbytes = + pgno2bytes(env, mdbx_find_largest(env, head->mm_geo.next)); + if ((size_t)size_upper < usedbytes) { + rc = MDBX_MAP_FULL; + goto bailout; + } + if ((size_t)size_now < usedbytes) + size_now = usedbytes; + } else { + /* env NOT yet mapped */ + if (unlikely(inside_txn)) + return MDBX_PANIC; + + if (pagesize < 0) { + pagesize = env->me_os_psize; + if ((uintptr_t)pagesize > MAX_PAGESIZE) + pagesize = MAX_PAGESIZE; + mdbx_assert(env, (uintptr_t)pagesize >= MIN_PAGESIZE); + } + } + + if (pagesize == 0) + pagesize = MIN_PAGESIZE; + else if (pagesize == INTPTR_MAX) + pagesize = MAX_PAGESIZE; + + if (pagesize < (intptr_t)MIN_PAGESIZE || pagesize > (intptr_t)MAX_PAGESIZE || + !is_powerof2(pagesize)) { + rc = MDBX_EINVAL; + goto bailout; + } + + if (size_lower <= 0) { + size_lower = MIN_MAPSIZE; + if (MIN_MAPSIZE / pagesize < MIN_PAGENO) + size_lower = MIN_PAGENO * pagesize; + } + + if (size_now <= 0) { + size_now = DEFAULT_MAPSIZE; + if (size_now < size_lower) + size_now = size_lower; + if (size_upper >= size_lower && size_now > size_upper) + size_now = size_upper; + } + + if (size_upper <= 0) { + if ((size_t)size_now >= MAX_MAPSIZE / 2) + size_upper = MAX_MAPSIZE; + else if (MAX_MAPSIZE != MAX_MAPSIZE32 && + (size_t)size_now >= MAX_MAPSIZE32 / 2 && + (size_t)size_now <= MAX_MAPSIZE32 / 4 * 3) + size_upper = MAX_MAPSIZE32; + else { + size_upper = size_now + size_now; + if ((size_t)size_upper < DEFAULT_MAPSIZE * 2) + size_upper = DEFAULT_MAPSIZE * 2; + } + if ((size_t)size_upper / pagesize > MAX_PAGENO) + size_upper = pagesize * MAX_PAGENO; + } + + if (unlikely(size_lower < (intptr_t)MIN_MAPSIZE || size_lower > size_upper)) { + rc = MDBX_EINVAL; + goto bailout; + } + + if ((uint64_t)size_lower / pagesize < MIN_PAGENO) { + rc = MDBX_EINVAL; + goto bailout; + } + + if (unlikely((size_t)size_upper > MAX_MAPSIZE || + (uint64_t)size_upper / pagesize > MAX_PAGENO)) { + rc = MDBX_TOO_LARGE; + goto bailout; + } + + size_lower = roundup_powerof2(size_lower, env->me_os_psize); + size_upper = roundup_powerof2(size_upper, env->me_os_psize); + size_now = roundup_powerof2(size_now, env->me_os_psize); + + /* LY: подбираем значение size_upper: + * - кратное размеру системной страницы + * - без нарушения MAX_MAPSIZE и MAX_PAGENO */ + while (unlikely((size_t)size_upper > MAX_MAPSIZE || + (uint64_t)size_upper / pagesize > MAX_PAGENO)) { + if ((size_t)size_upper < env->me_os_psize + MIN_MAPSIZE || + (size_t)size_upper < env->me_os_psize * (MIN_PAGENO + 1)) { + /* паранойа на случай переполнения при невероятных значениях */ + rc = MDBX_EINVAL; + goto bailout; + } + size_upper -= env->me_os_psize; + if ((size_t)size_upper < (size_t)size_lower) + size_lower = size_upper; + } + mdbx_assert(env, (size_upper - size_lower) % env->me_os_psize == 0); + + if (size_now < size_lower) + size_now = size_lower; + if (size_now > size_upper) + size_now = size_upper; + + if (growth_step < 0) { + growth_step = ((size_t)(size_upper - size_lower)) / 42; + if (growth_step > size_lower) + growth_step = size_lower; + if (growth_step < 65536) + growth_step = 65536; + if ((size_t)growth_step > MEGABYTE * 16) + growth_step = MEGABYTE * 16; + } + if (growth_step == 0 && shrink_threshold > 0) + growth_step = 1; + growth_step = roundup_powerof2(growth_step, env->me_os_psize); + if (bytes2pgno(env, growth_step) > UINT16_MAX) + growth_step = pgno2bytes(env, UINT16_MAX); + + if (shrink_threshold < 0) + shrink_threshold = growth_step + growth_step; + shrink_threshold = roundup_powerof2(shrink_threshold, env->me_os_psize); + if (bytes2pgno(env, shrink_threshold) > UINT16_MAX) + shrink_threshold = pgno2bytes(env, UINT16_MAX); + + /* save user's geo-params for future open/create */ + env->me_dbgeo.lower = size_lower; + env->me_dbgeo.now = size_now; + env->me_dbgeo.upper = size_upper; + env->me_dbgeo.grow = growth_step; + env->me_dbgeo.shrink = shrink_threshold; + rc = MDBX_SUCCESS; + + mdbx_ensure(env, pagesize >= MIN_PAGESIZE); + mdbx_ensure(env, pagesize <= MAX_PAGESIZE); + mdbx_ensure(env, is_powerof2(pagesize)); + mdbx_ensure(env, is_powerof2(env->me_os_psize)); + + mdbx_ensure(env, env->me_dbgeo.lower >= MIN_MAPSIZE); + mdbx_ensure(env, env->me_dbgeo.lower / pagesize >= MIN_PAGENO); + mdbx_ensure(env, env->me_dbgeo.lower % pagesize == 0); + mdbx_ensure(env, env->me_dbgeo.lower % env->me_os_psize == 0); + + mdbx_ensure(env, env->me_dbgeo.upper <= MAX_MAPSIZE); + mdbx_ensure(env, env->me_dbgeo.upper / pagesize <= MAX_PAGENO); + mdbx_ensure(env, env->me_dbgeo.upper % pagesize == 0); + mdbx_ensure(env, env->me_dbgeo.upper % env->me_os_psize == 0); + + mdbx_ensure(env, env->me_dbgeo.now >= env->me_dbgeo.lower); + mdbx_ensure(env, env->me_dbgeo.now <= env->me_dbgeo.upper); + mdbx_ensure(env, env->me_dbgeo.now % pagesize == 0); + mdbx_ensure(env, env->me_dbgeo.now % env->me_os_psize == 0); + + mdbx_ensure(env, env->me_dbgeo.grow % pagesize == 0); + mdbx_ensure(env, env->me_dbgeo.grow % env->me_os_psize == 0); + mdbx_ensure(env, env->me_dbgeo.shrink % pagesize == 0); + mdbx_ensure(env, env->me_dbgeo.shrink % env->me_os_psize == 0); + + if (env->me_map) { + /* apply new params to opened environment */ + mdbx_ensure(env, pagesize == (intptr_t)env->me_psize); + MDBX_meta meta; + MDBX_meta *head = nullptr; + const mdbx_geo_t *current_geo; + if (inside_txn) { + current_geo = &env->me_txn->mt_geo; + } else { + head = mdbx_meta_head(env); + meta = *head; + current_geo = &meta.mm_geo; + } + + mdbx_geo_t new_geo; + new_geo.lower = bytes2pgno(env, env->me_dbgeo.lower); + new_geo.now = bytes2pgno(env, env->me_dbgeo.now); + new_geo.upper = bytes2pgno(env, env->me_dbgeo.upper); + new_geo.grow = (uint16_t)bytes2pgno(env, env->me_dbgeo.grow); + new_geo.shrink = (uint16_t)bytes2pgno(env, env->me_dbgeo.shrink); + new_geo.next = current_geo->next; + + mdbx_ensure(env, + pgno_align2os_bytes(env, new_geo.lower) == env->me_dbgeo.lower); + mdbx_ensure(env, + pgno_align2os_bytes(env, new_geo.upper) == env->me_dbgeo.upper); + mdbx_ensure(env, + pgno_align2os_bytes(env, new_geo.now) == env->me_dbgeo.now); + mdbx_ensure(env, + pgno_align2os_bytes(env, new_geo.grow) == env->me_dbgeo.grow); + mdbx_ensure(env, pgno_align2os_bytes(env, new_geo.shrink) == + env->me_dbgeo.shrink); + + mdbx_ensure(env, env->me_dbgeo.lower >= MIN_MAPSIZE); + mdbx_ensure(env, new_geo.lower >= MIN_PAGENO); + mdbx_ensure(env, env->me_dbgeo.upper <= MAX_MAPSIZE); + mdbx_ensure(env, new_geo.upper <= MAX_PAGENO); + mdbx_ensure(env, new_geo.now >= new_geo.next); + mdbx_ensure(env, new_geo.upper >= new_geo.now); + mdbx_ensure(env, new_geo.now >= new_geo.lower); + + if (memcmp(current_geo, &new_geo, sizeof(mdbx_geo_t)) != 0) { +#if defined(_WIN32) || defined(_WIN64) + /* Was DB shrinking disabled before and now it will be enabled? */ + if (new_geo.lower < new_geo.upper && new_geo.shrink && + !(current_geo->lower < current_geo->upper && current_geo->shrink)) { + if (!env->me_lck) { + rc = MDBX_EPERM; + goto bailout; + } + rc = mdbx_rdt_lock(env); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + + /* Check if there are any reading threads that do not use the SRWL */ + const size_t CurrentTid = GetCurrentThreadId(); + const MDBX_reader *const begin = env->me_lck->mti_readers; + const MDBX_reader *const end = begin + env->me_lck->mti_numreaders; + for (const MDBX_reader *reader = begin; reader < end; ++reader) { + if (reader->mr_pid == env->me_pid && reader->mr_tid && + reader->mr_tid != CurrentTid) { + /* At least one thread may don't use SRWL */ + rc = MDBX_EPERM; + break; + } + } + + mdbx_rdt_unlock(env); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + } +#endif + + if (new_geo.now != current_geo->now || + new_geo.upper != current_geo->upper) { + rc = mdbx_mapresize(env, new_geo.now, new_geo.upper); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + mdbx_assert(env, (head == nullptr) == inside_txn); + if (head) + head = /* base address could be changed */ mdbx_meta_head(env); + } + if (inside_txn) { + env->me_txn->mt_geo = new_geo; + if ((env->me_txn->mt_flags & MDBX_TXN_DIRTY) == 0) { + env->me_txn->mt_flags |= MDBX_TXN_DIRTY; + *env->me_unsynced_pages += 1; + } + } else { + *env->me_unsynced_pages += 1; + mdbx_meta_set_txnid( + env, &meta, safe64_txnid_next(mdbx_meta_txnid_stable(env, head))); + rc = mdbx_sync_locked(env, env->me_flags, &meta); + } + } + } else if (pagesize != (intptr_t)env->me_psize) { + mdbx_setup_pagesize(env, pagesize); + } + +bailout: + if (need_unlock) + mdbx_txn_unlock(env); + return rc; +} + +int __cold mdbx_env_set_mapsize(MDBX_env *env, size_t size) { + return mdbx_env_set_geometry(env, size, size, size, -1, -1, -1); +} + +int __cold mdbx_env_set_maxdbs(MDBX_env *env, MDBX_dbi dbs) { + if (unlikely(dbs > MAX_DBI)) + return MDBX_EINVAL; + + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(env->me_map)) + return MDBX_EPERM; + + env->me_maxdbs = dbs + CORE_DBS; + return MDBX_SUCCESS; +} + +int __cold mdbx_env_set_maxreaders(MDBX_env *env, unsigned readers) { + if (unlikely(readers < 1 || readers > MDBX_READERS_LIMIT)) + return MDBX_EINVAL; + + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(env->me_map)) + return MDBX_EPERM; + + env->me_maxreaders = readers; + return MDBX_SUCCESS; +} + +int __cold mdbx_env_get_maxreaders(MDBX_env *env, unsigned *readers) { + if (!env || !readers) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + *readers = env->me_maxreaders; + return MDBX_SUCCESS; +} + +/* Further setup required for opening an MDBX environment */ +static int __cold mdbx_setup_dxb(MDBX_env *env, const int lck_rc) { + uint64_t filesize_before; + MDBX_meta meta; + int rc = MDBX_RESULT_FALSE; + int err = mdbx_read_header(env, &meta, &filesize_before); + if (unlikely(err != MDBX_SUCCESS)) { + if (lck_rc != /* lck exclusive */ MDBX_RESULT_TRUE || err != MDBX_ENODATA || + (env->me_flags & MDBX_RDONLY) != 0) + return err; + + mdbx_debug("%s", "create new database"); + rc = /* new database */ MDBX_RESULT_TRUE; + + if (!env->me_dbgeo.now) { + /* set defaults if not configured */ + err = mdbx_env_set_mapsize(env, DEFAULT_MAPSIZE); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + + void *buffer = mdbx_calloc(NUM_METAS, env->me_psize); + if (!buffer) + return MDBX_ENOMEM; + + meta = *mdbx_init_metas(env, buffer); + err = mdbx_pwrite(env->me_fd, buffer, env->me_psize * NUM_METAS, 0); + mdbx_free(buffer); + if (unlikely(err != MDBX_SUCCESS)) + return err; + + err = mdbx_ftruncate(env->me_fd, filesize_before = env->me_dbgeo.now); + if (unlikely(err != MDBX_SUCCESS)) + return err; + +#ifndef NDEBUG /* just for checking */ + err = mdbx_read_header(env, &meta, &filesize_before); + if (unlikely(err != MDBX_SUCCESS)) + return err; +#endif + } + + mdbx_verbose("header: root %" PRIaPGNO "/%" PRIaPGNO ", geo %" PRIaPGNO + "/%" PRIaPGNO "-%" PRIaPGNO "/%" PRIaPGNO + " +%u -%u, txn_id %" PRIaTXN ", %s", + meta.mm_dbs[MAIN_DBI].md_root, meta.mm_dbs[FREE_DBI].md_root, + meta.mm_geo.lower, meta.mm_geo.next, meta.mm_geo.now, + meta.mm_geo.upper, meta.mm_geo.grow, meta.mm_geo.shrink, + meta.mm_txnid_a.inconsistent, mdbx_durable_str(&meta)); + + mdbx_setup_pagesize(env, meta.mm_psize); + const size_t used_bytes = pgno2bytes(env, meta.mm_geo.next); + const size_t used_aligned2os_bytes = + roundup_powerof2(used_bytes, env->me_os_psize); + if ((env->me_flags & MDBX_RDONLY) /* readonly */ + || lck_rc != MDBX_RESULT_TRUE /* not exclusive */) { + /* use present params from db */ + const size_t pagesize = meta.mm_psize; + err = mdbx_env_set_geometry( + env, meta.mm_geo.lower * pagesize, meta.mm_geo.now * pagesize, + meta.mm_geo.upper * pagesize, meta.mm_geo.grow * pagesize, + meta.mm_geo.shrink * pagesize, meta.mm_psize); + if (unlikely(err != MDBX_SUCCESS)) { + mdbx_error("%s", "could not use present dbsize-params from db"); + return MDBX_INCOMPATIBLE; + } + } else if (env->me_dbgeo.now) { + /* silently growth to last used page */ + if (env->me_dbgeo.now < used_aligned2os_bytes) + env->me_dbgeo.now = used_aligned2os_bytes; + if (env->me_dbgeo.upper < used_aligned2os_bytes) + env->me_dbgeo.upper = used_aligned2os_bytes; + + /* apply preconfigured params, but only if substantial changes: + * - upper or lower limit changes + * - shrink threshold or growth step + * But ignore change just a 'now/current' size. */ + if (bytes_align2os_bytes(env, env->me_dbgeo.upper) != + pgno2bytes(env, meta.mm_geo.upper) || + bytes_align2os_bytes(env, env->me_dbgeo.lower) != + pgno2bytes(env, meta.mm_geo.lower) || + bytes_align2os_bytes(env, env->me_dbgeo.shrink) != + pgno2bytes(env, meta.mm_geo.shrink) || + bytes_align2os_bytes(env, env->me_dbgeo.grow) != + pgno2bytes(env, meta.mm_geo.grow)) { + + if (env->me_dbgeo.shrink && env->me_dbgeo.now > used_bytes) + /* pre-shrink if enabled */ + env->me_dbgeo.now = used_bytes + env->me_dbgeo.shrink - + used_bytes % env->me_dbgeo.shrink; + + err = mdbx_env_set_geometry(env, env->me_dbgeo.lower, env->me_dbgeo.now, + env->me_dbgeo.upper, env->me_dbgeo.grow, + env->me_dbgeo.shrink, meta.mm_psize); + if (unlikely(err != MDBX_SUCCESS)) { + mdbx_error("%s", "could not apply preconfigured dbsize-params to db"); + return MDBX_INCOMPATIBLE; + } + + /* update meta fields */ + meta.mm_geo.now = bytes2pgno(env, env->me_dbgeo.now); + meta.mm_geo.lower = bytes2pgno(env, env->me_dbgeo.lower); + meta.mm_geo.upper = bytes2pgno(env, env->me_dbgeo.upper); + meta.mm_geo.grow = (uint16_t)bytes2pgno(env, env->me_dbgeo.grow); + meta.mm_geo.shrink = (uint16_t)bytes2pgno(env, env->me_dbgeo.shrink); + + mdbx_verbose("amended: root %" PRIaPGNO "/%" PRIaPGNO ", geo %" PRIaPGNO + "/%" PRIaPGNO "-%" PRIaPGNO "/%" PRIaPGNO + " +%u -%u, txn_id %" PRIaTXN ", %s", + meta.mm_dbs[MAIN_DBI].md_root, meta.mm_dbs[FREE_DBI].md_root, + meta.mm_geo.lower, meta.mm_geo.next, meta.mm_geo.now, + meta.mm_geo.upper, meta.mm_geo.grow, meta.mm_geo.shrink, + meta.mm_txnid_a.inconsistent, mdbx_durable_str(&meta)); + } else { + /* fetch back 'now/current' size, since it was ignored during comparison + * and may differ. */ + env->me_dbgeo.now = pgno_align2os_bytes(env, meta.mm_geo.now); + } + mdbx_ensure(env, meta.mm_geo.now >= meta.mm_geo.next); + } else { + /* geo-params are not pre-configured by user, + * get current values from the meta. */ + env->me_dbgeo.now = pgno2bytes(env, meta.mm_geo.now); + env->me_dbgeo.lower = pgno2bytes(env, meta.mm_geo.lower); + env->me_dbgeo.upper = pgno2bytes(env, meta.mm_geo.upper); + env->me_dbgeo.grow = pgno2bytes(env, meta.mm_geo.grow); + env->me_dbgeo.shrink = pgno2bytes(env, meta.mm_geo.shrink); + } + + mdbx_ensure(env, + pgno_align2os_bytes(env, meta.mm_geo.now) == env->me_dbgeo.now); + mdbx_ensure(env, env->me_dbgeo.now >= used_bytes); + if (unlikely(filesize_before != env->me_dbgeo.now)) { + if (lck_rc != /* lck exclusive */ MDBX_RESULT_TRUE) { + mdbx_verbose("filesize mismatch (expect %" PRIuPTR "b/%" PRIaPGNO + "p, have %" PRIu64 "b/%" PRIaPGNO "p), " + "assume other process working", + env->me_dbgeo.now, bytes2pgno(env, env->me_dbgeo.now), + filesize_before, bytes2pgno(env, (size_t)filesize_before)); + } else { + mdbx_notice("filesize mismatch (expect %" PRIuSIZE "b/%" PRIaPGNO + "p, have %" PRIu64 "b/%" PRIaPGNO "p)", + env->me_dbgeo.now, bytes2pgno(env, env->me_dbgeo.now), + filesize_before, bytes2pgno(env, (size_t)filesize_before)); + if (filesize_before < used_bytes) { + mdbx_error("last-page beyond end-of-file (last %" PRIaPGNO + ", have %" PRIaPGNO ")", + meta.mm_geo.next, bytes2pgno(env, (size_t)filesize_before)); + return MDBX_CORRUPTED; + } + + if (env->me_flags & MDBX_RDONLY) { + if (filesize_before & (env->me_os_psize - 1)) { + mdbx_error("%s", "filesize should be rounded-up to system page"); + return MDBX_WANNA_RECOVERY; + } + mdbx_warning("%s", "ignore filesize mismatch in readonly-mode"); + } else { + mdbx_verbose("will resize datafile to %" PRIuSIZE " bytes, %" PRIaPGNO + " pages", + env->me_dbgeo.now, bytes2pgno(env, env->me_dbgeo.now)); + } + } + } + + err = mdbx_mmap(env->me_flags, &env->me_dxb_mmap, env->me_dbgeo.now, + env->me_dbgeo.upper, lck_rc); + if (unlikely(err != MDBX_SUCCESS)) + return err; + +#if defined(MADV_DODUMP) && defined(MADV_DONTDUMP) + const size_t meta_length = pgno2bytes(env, NUM_METAS); + (void)madvise(env->me_map, meta_length, MADV_DODUMP); + (void)madvise(env->me_map + meta_length, env->me_dxb_mmap.limit - meta_length, + (mdbx_runtime_flags & MDBX_DBG_DUMP) ? MADV_DODUMP + : MADV_DONTDUMP); +#endif + + *env->me_discarded_tail = bytes2pgno(env, used_aligned2os_bytes); + if (used_aligned2os_bytes < env->me_dxb_mmap.current) { +#if defined(MADV_REMOVE) + if (lck_rc && (env->me_flags & MDBX_WRITEMAP) != 0) { + mdbx_notice("open-MADV_%s %u..%u", "REMOVE", *env->me_discarded_tail, + bytes2pgno(env, env->me_dxb_mmap.current)); + (void)madvise(env->me_map + used_aligned2os_bytes, + env->me_dxb_mmap.current - used_aligned2os_bytes, + MADV_REMOVE); + } +#endif /* MADV_REMOVE */ +#if defined(MADV_DONTNEED) + mdbx_notice("open-MADV_%s %u..%u", "DONTNEED", *env->me_discarded_tail, + bytes2pgno(env, env->me_dxb_mmap.current)); + (void)madvise(env->me_map + used_aligned2os_bytes, + env->me_dxb_mmap.current - used_aligned2os_bytes, + MADV_DONTNEED); +#elif defined(POSIX_MADV_DONTNEED) + (void)madvise(env->me_map + used_aligned2os_bytes, + env->me_dxb_mmap.current - used_aligned2os_bytes, + POSIX_MADV_DONTNEED); +#elif defined(POSIX_FADV_DONTNEED) + (void)posix_fadvise(env->me_fd, used_aligned2os_bytes, + env->me_dxb_mmap.current - used_aligned2os_bytes, + POSIX_FADV_DONTNEED); +#endif /* MADV_DONTNEED */ + } + +#ifdef MDBX_USE_VALGRIND + env->me_valgrind_handle = + VALGRIND_CREATE_BLOCK(env->me_map, env->me_dxb_mmap.limit, "mdbx"); +#endif + + const bool readahead = (env->me_flags & MDBX_NORDAHEAD) == 0 && + mdbx_is_readahead_reasonable(env->me_dxb_mmap.current, + 0) == MDBX_RESULT_TRUE; + err = mdbx_set_readahead(env, 0, env->me_dxb_mmap.current, readahead); + if (err != MDBX_SUCCESS) + return err; + + mdbx_assert(env, used_bytes >= pgno2bytes(env, NUM_METAS) && + used_bytes <= env->me_dxb_mmap.limit); +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) + VALGRIND_MAKE_MEM_NOACCESS(env->me_map + used_bytes, + env->me_dxb_mmap.limit - used_bytes); + ASAN_POISON_MEMORY_REGION(env->me_map + used_bytes, + env->me_dxb_mmap.limit - used_bytes); + env->me_poison_edge = bytes2pgno(env, env->me_dxb_mmap.limit); +#endif /* MDBX_USE_VALGRIND */ + + /* NOTE: AddressSanitizer (at least GCC 7.x, 8.x) could generate + * false-positive alarm here. I have no other explanation for this + * except due to an internal ASAN error, as the problem is reproduced + * in a single-threaded application under the active assert() above. */ + const unsigned meta_clash_mask = mdbx_meta_eq_mask(env); + if (meta_clash_mask) { + mdbx_error("meta-pages are clashed: mask 0x%d", meta_clash_mask); + return MDBX_WANNA_RECOVERY; + } + + while (1) { + MDBX_meta *head = mdbx_meta_head(env); + const txnid_t head_txnid = mdbx_meta_txnid_fluid(env, head); + if (head_txnid == meta.mm_txnid_a.inconsistent) + break; + + if (lck_rc == /* lck exclusive */ MDBX_RESULT_TRUE) { + mdbx_assert(env, META_IS_STEADY(&meta) && !META_IS_STEADY(head)); + if (env->me_flags & MDBX_RDONLY) { + mdbx_error("rollback needed: (from head %" PRIaTXN + " to steady %" PRIaTXN "), but unable in read-only mode", + head_txnid, meta.mm_txnid_a.inconsistent); + return MDBX_WANNA_RECOVERY /* LY: could not recovery/rollback */; + } + + const MDBX_meta *const meta0 = METAPAGE(env, 0); + const MDBX_meta *const meta1 = METAPAGE(env, 1); + const MDBX_meta *const meta2 = METAPAGE(env, 2); + txnid_t undo_txnid = 0 /* zero means undo is unneeded */; + while ( + (head != meta0 && mdbx_meta_txnid_fluid(env, meta0) == undo_txnid) || + (head != meta1 && mdbx_meta_txnid_fluid(env, meta1) == undo_txnid) || + (head != meta2 && mdbx_meta_txnid_fluid(env, meta2) == undo_txnid)) + undo_txnid = safe64_txnid_next(undo_txnid); + if (unlikely(undo_txnid >= meta.mm_txnid_a.inconsistent)) { + mdbx_fatal("rollback failed: no suitable txnid (0,1,2) < %" PRIaTXN, + meta.mm_txnid_a.inconsistent); + return MDBX_PANIC /* LY: could not recovery/rollback */; + } + + /* LY: rollback weak checkpoint */ + mdbx_trace("rollback: from %" PRIaTXN ", to %" PRIaTXN " as %" PRIaTXN, + head_txnid, meta.mm_txnid_a.inconsistent, undo_txnid); + mdbx_ensure(env, head_txnid == mdbx_meta_txnid_stable(env, head)); + + if (env->me_flags & MDBX_WRITEMAP) { + /* It is possible to update txnid without safe64_write(), + * since DB opened exclusive for now */ + head->mm_txnid_a.inconsistent = undo_txnid; + head->mm_datasync_sign = MDBX_DATASIGN_WEAK; + head->mm_txnid_b.inconsistent = undo_txnid; + const size_t offset = (uint8_t *)data_page(head) - env->me_dxb_mmap.dxb; + const size_t paged_offset = offset & ~(env->me_os_psize - 1); + const size_t paged_length = roundup_powerof2( + env->me_psize + offset - paged_offset, env->me_os_psize); + err = mdbx_msync(&env->me_dxb_mmap, paged_offset, paged_length, false); + } else { + MDBX_meta rollback = *head; + mdbx_meta_set_txnid(env, &rollback, undo_txnid); + rollback.mm_datasync_sign = MDBX_DATASIGN_WEAK; + err = mdbx_pwrite(env->me_fd, &rollback, sizeof(MDBX_meta), + (uint8_t *)head - (uint8_t *)env->me_map); + } + if (err) { + mdbx_error("error %d rollback from %" PRIaTXN ", to %" PRIaTXN + " as %" PRIaTXN, + err, head_txnid, meta.mm_txnid_a.inconsistent, undo_txnid); + return err; + } + + mdbx_invalidate_mmap_noncoherent_cache(env->me_map, + pgno2bytes(env, NUM_METAS)); + mdbx_ensure(env, undo_txnid == mdbx_meta_txnid_fluid(env, head)); + mdbx_ensure(env, 0 == mdbx_meta_eq_mask(env)); + continue; + } + + if (!env->me_lck) { + /* LY: without-lck (read-only) mode, so it is imposible that other + * process made weak checkpoint. */ + mdbx_error("%s", "without-lck, unable recovery/rollback"); + return MDBX_WANNA_RECOVERY; + } + + /* LY: assume just have a collision with other running process, + * or someone make a weak checkpoint */ + mdbx_verbose("%s", "assume collision or online weak checkpoint"); + break; + } + + const MDBX_meta *head = mdbx_meta_head(env); + if (lck_rc == /* lck exclusive */ MDBX_RESULT_TRUE) { + /* re-check size after mmap */ + if ((env->me_dxb_mmap.current & (env->me_os_psize - 1)) != 0 || + env->me_dxb_mmap.current < used_bytes) { + mdbx_error("unacceptable/unexpected datafile size %" PRIuPTR, + env->me_dxb_mmap.current); + return MDBX_PROBLEM; + } + if (env->me_dxb_mmap.current != env->me_dbgeo.now && + (env->me_flags & MDBX_RDONLY) == 0) { + meta.mm_geo.now = bytes2pgno(env, env->me_dxb_mmap.current); + mdbx_verbose("update meta-geo to filesize %" PRIuPTR " bytes, %" PRIaPGNO + " pages", + env->me_dxb_mmap.current, meta.mm_geo.now); + } + + if (memcmp(&meta.mm_geo, &head->mm_geo, sizeof(meta.mm_geo))) { + const txnid_t txnid = mdbx_meta_txnid_stable(env, head); + const txnid_t next_txnid = safe64_txnid_next(txnid); + mdbx_verbose("updating meta.geo: " + "from l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN "), " + "to l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN ")", + head->mm_geo.lower, head->mm_geo.now, head->mm_geo.upper, + head->mm_geo.shrink, head->mm_geo.grow, txnid, + meta.mm_geo.lower, meta.mm_geo.now, meta.mm_geo.upper, + meta.mm_geo.shrink, meta.mm_geo.grow, next_txnid); + + mdbx_ensure(env, mdbx_meta_eq(env, &meta, head)); + mdbx_meta_set_txnid(env, &meta, next_txnid); + *env->me_unsynced_pages += 1; + err = mdbx_sync_locked(env, env->me_flags | MDBX_SHRINK_ALLOWED, &meta); + if (err) { + mdbx_error("error %d, while updating meta.geo: " + "from l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN "), " + "to l%" PRIaPGNO "-n%" PRIaPGNO "-u%" PRIaPGNO + "/s%u-g%u (txn#%" PRIaTXN ")", + err, head->mm_geo.lower, head->mm_geo.now, + head->mm_geo.upper, head->mm_geo.shrink, head->mm_geo.grow, + txnid, meta.mm_geo.lower, meta.mm_geo.now, meta.mm_geo.upper, + meta.mm_geo.shrink, meta.mm_geo.grow, next_txnid); + return err; + } + } + } + + return rc; +} + +/******************************************************************************/ + +/* Open and/or initialize the lock region for the environment. */ +static int __cold mdbx_setup_lck(MDBX_env *env, char *lck_pathname, + mode_t mode) { + mdbx_assert(env, env->me_fd != INVALID_HANDLE_VALUE); + mdbx_assert(env, env->me_lfd == INVALID_HANDLE_VALUE); + + int err = mdbx_openfile(lck_pathname, O_RDWR | O_CREAT, mode, &env->me_lfd, + (env->me_flags & MDBX_EXCLUSIVE) ? true : false); + if (err != MDBX_SUCCESS) { + if (!(err == MDBX_ENOFILE && (env->me_flags & MDBX_EXCLUSIVE)) && + !((err == MDBX_EROFS || err == MDBX_EACCESS || err == MDBX_EPERM) && + (env->me_flags & MDBX_RDONLY))) + return err; + + /* LY: without-lck mode (e.g. exclusive or on read-only filesystem) */ + /* beginning of a locked section ---------------------------------------- */ + lcklist_lock(); + mdbx_assert(env, env->me_lcklist_next == nullptr); + env->me_lfd = INVALID_HANDLE_VALUE; + const int rc = mdbx_lck_seize(env); + if (MDBX_IS_ERROR(rc)) { + /* Calling lcklist_detach_locked() is required to restore POSIX-filelock + * and this job will be done by mdbx_env_close0(). */ + lcklist_unlock(); + return rc; + } + /* insert into inprocess lck-list */ + env->me_lcklist_next = inprocess_lcklist_head; + inprocess_lcklist_head = env; + lcklist_unlock(); + /* end of a locked section ---------------------------------------------- */ + + env->me_oldest = &env->me_lckless_stub.oldest; + env->me_sync_timestamp = &env->me_lckless_stub.sync_timestamp; + env->me_autosync_period = &env->me_lckless_stub.autosync_period; + env->me_unsynced_pages = &env->me_lckless_stub.autosync_pending; + env->me_autosync_threshold = &env->me_lckless_stub.autosync_threshold; + env->me_discarded_tail = &env->me_lckless_stub.discarded_tail; + env->me_meta_sync_txnid = &env->me_lckless_stub.meta_sync_txnid; + env->me_maxreaders = UINT_MAX; +#ifdef MDBX_OSAL_LOCK + env->me_wmutex = &env->me_lckless_stub.wmutex; +#endif + mdbx_debug("lck-setup:%s%s%s", " lck-less", + (env->me_flags & MDBX_RDONLY) ? " readonly" : "", + (rc == MDBX_RESULT_TRUE) ? " exclusive" : " cooperative"); + return rc; + } + + /* beginning of a locked section ------------------------------------------ */ + lcklist_lock(); + mdbx_assert(env, env->me_lcklist_next == nullptr); + + /* Try to get exclusive lock. If we succeed, then + * nobody is using the lock region and we should initialize it. */ + err = mdbx_lck_seize(env); + if (MDBX_IS_ERROR(err)) { + bailout: + /* Calling lcklist_detach_locked() is required to restore POSIX-filelock + * and this job will be done by mdbx_env_close0(). */ + lcklist_unlock(); + return err; + } + + MDBX_env *inprocess_neighbor = nullptr; + if (err == MDBX_RESULT_TRUE) { + err = uniq_check(&env->me_lck_mmap, &inprocess_neighbor); + if (MDBX_IS_ERROR(err)) + goto bailout; + if (inprocess_neighbor && + ((mdbx_runtime_flags & MDBX_DBG_LEGACY_MULTIOPEN) == 0 || + (inprocess_neighbor->me_flags & MDBX_EXCLUSIVE) != 0)) { + err = MDBX_BUSY; + goto bailout; + } + } + const int lck_seize_rc = err; + + mdbx_debug("lck-setup:%s%s%s", " with-lck", + (env->me_flags & MDBX_RDONLY) ? " readonly" : "", + (lck_seize_rc == MDBX_RESULT_TRUE) ? " exclusive" + : " cooperative"); + + uint64_t size; + err = mdbx_filesize(env->me_lfd, &size); + if (unlikely(err != MDBX_SUCCESS)) + goto bailout; + + if (lck_seize_rc == MDBX_RESULT_TRUE) { + size = roundup_powerof2(env->me_maxreaders * sizeof(MDBX_reader) + + sizeof(MDBX_lockinfo), + env->me_os_psize); +#ifndef NDEBUG + err = mdbx_ftruncate(env->me_lfd, 0); + if (unlikely(err != MDBX_SUCCESS)) + goto bailout; +#endif + mdbx_jitter4testing(false); + } else { + if (env->me_flags & MDBX_EXCLUSIVE) { + err = MDBX_BUSY; + goto bailout; + } + if (size > INT_MAX || (size & (env->me_os_psize - 1)) != 0 || + size < env->me_os_psize) { + mdbx_error("lck-file has invalid size %" PRIu64 " bytes", size); + err = MDBX_PROBLEM; + goto bailout; + } + } + + const size_t maxreaders = + ((size_t)size - sizeof(MDBX_lockinfo)) / sizeof(MDBX_reader); + if (size > 65536 || maxreaders < 2 || maxreaders > MDBX_READERS_LIMIT) { + mdbx_error("lck-size too big (up to %" PRIuPTR " readers)", maxreaders); + err = MDBX_PROBLEM; + goto bailout; + } + env->me_maxreaders = (unsigned)maxreaders; + + err = mdbx_mmap(MDBX_WRITEMAP, &env->me_lck_mmap, (size_t)size, (size_t)size, + lck_seize_rc); + if (unlikely(err != MDBX_SUCCESS)) + goto bailout; + +#ifdef MADV_DODUMP + (void)madvise(env->me_lck, size, MADV_DODUMP); +#endif + +#ifdef MADV_WILLNEED + if (madvise(env->me_lck, size, MADV_WILLNEED) < 0) + goto bailout; +#endif + + if (lck_seize_rc == MDBX_RESULT_TRUE) { + /* LY: exlcusive mode, reset lck */ + memset(env->me_lck, 0, (size_t)size); + mdbx_jitter4testing(false); + env->me_lck->mti_magic_and_version = MDBX_LOCK_MAGIC; + env->me_lck->mti_os_and_format = MDBX_LOCK_FORMAT; + } else { + if (env->me_lck->mti_magic_and_version != MDBX_LOCK_MAGIC) { + mdbx_error("%s", "lock region has invalid magic/version"); + err = ((env->me_lck->mti_magic_and_version >> 8) != MDBX_MAGIC) + ? MDBX_INVALID + : MDBX_VERSION_MISMATCH; + goto bailout; + } + if (env->me_lck->mti_os_and_format != MDBX_LOCK_FORMAT) { + mdbx_error("lock region has os/format 0x%" PRIx32 ", expected 0x%" PRIx32, + env->me_lck->mti_os_and_format, MDBX_LOCK_FORMAT); + err = MDBX_VERSION_MISMATCH; + goto bailout; + } + } + + err = mdbx_lck_init(env, inprocess_neighbor, lck_seize_rc); + if (MDBX_IS_ERROR(err)) + goto bailout; + + mdbx_ensure(env, env->me_lcklist_next == nullptr); + /* insert into inprocess lck-list */ + env->me_lcklist_next = inprocess_lcklist_head; + inprocess_lcklist_head = env; + lcklist_unlock(); + /* end of a locked section ------------------------------------------------ */ + + mdbx_assert(env, !MDBX_IS_ERROR(lck_seize_rc)); + env->me_oldest = &env->me_lck->mti_oldest_reader; + env->me_sync_timestamp = &env->me_lck->mti_sync_timestamp; + env->me_autosync_period = &env->me_lck->mti_autosync_period; + env->me_unsynced_pages = &env->me_lck->mti_unsynced_pages; + env->me_autosync_threshold = &env->me_lck->mti_autosync_threshold; + env->me_discarded_tail = &env->me_lck->mti_discarded_tail; + env->me_meta_sync_txnid = &env->me_lck->mti_meta_sync_txnid; +#ifdef MDBX_OSAL_LOCK + env->me_wmutex = &env->me_lck->mti_wmutex; +#endif + return lck_seize_rc; +} + +__cold int mdbx_is_readahead_reasonable(size_t volume, intptr_t redundancy) { + if (volume <= 1024 * 1024 * 4ul) + return MDBX_RESULT_TRUE; + + const intptr_t pagesize = mdbx_syspagesize(); + if (unlikely(pagesize < MIN_PAGESIZE || !is_powerof2(pagesize))) + return MDBX_INCOMPATIBLE; + +#if defined(_WIN32) || defined(_WIN64) + MEMORYSTATUSEX info; + memset(&info, 0, sizeof(info)); + info.dwLength = sizeof(info); + if (!GlobalMemoryStatusEx(&info)) + return GetLastError(); +#endif + const int log2page = log2n(pagesize); + +#if defined(_WIN32) || defined(_WIN64) + const intptr_t total_ram_pages = (intptr_t)(info.ullTotalPhys >> log2page); +#elif defined(_SC_PHYS_PAGES) + const intptr_t total_ram_pages = sysconf(_SC_PHYS_PAGES); + if (total_ram_pages == -1) + return errno; +#elif defined(_SC_AIX_REALMEM) + const intptr_t total_ram_Kb = sysconf(_SC_AIX_REALMEM); + if (total_ram_Kb == -1) + return errno; + const intptr_t total_ram_pages = (total_ram_Kb << 10) >> log2page; +#elif defined(HW_USERMEM) || defined(HW_PHYSMEM64) || defined(HW_MEMSIZE) || \ + defined(HW_PHYSMEM) + size_t ram, len = sizeof(ram); + static const int mib[] = { + CTL_HW, +#if defined(HW_USERMEM) + HW_USERMEM +#elif defined(HW_PHYSMEM64) + HW_PHYSMEM64 +#elif defined(HW_MEMSIZE) + HW_MEMSIZE +#else + HW_PHYSMEM +#endif + }; + if (sysctl( +#ifdef SYSCTL_LEGACY_NONCONST_MIB + (int *) +#endif + mib, + ARRAY_LENGTH(mib), &ram, &len, NULL, 0) != 0) + return errno; + if (len != sizeof(ram)) + return MDBX_ENOSYS; + const intptr_t total_ram_pages = (intptr_t)(ram >> log2page); +#else +#error "FIXME: Get User-accessible or physical RAM" +#endif + if (total_ram_pages < 1) + return MDBX_ENOSYS; + + const intptr_t volume_pages = (volume + pagesize - 1) >> log2page; + const intptr_t redundancy_pages = + (redundancy < 0) ? -(intptr_t)((-redundancy + pagesize - 1) >> log2page) + : (intptr_t)(redundancy + pagesize - 1) >> log2page; + if (volume_pages >= total_ram_pages || + volume_pages + redundancy_pages >= total_ram_pages) + return MDBX_RESULT_FALSE; + +#if defined(_WIN32) || defined(_WIN64) + const intptr_t avail_ram_pages = (intptr_t)(info.ullAvailPhys >> log2page); +#elif defined(_SC_AVPHYS_PAGES) + const intptr_t avail_ram_pages = sysconf(_SC_AVPHYS_PAGES); + if (avail_ram_pages == -1) + return errno; +#elif defined(__MACH__) + mach_msg_type_number_t count = HOST_VM_INFO_COUNT; + vm_statistics_data_t vmstat; + mach_port_t mport = mach_host_self(); + kern_return_t kerr = host_statistics(mach_host_self(), HOST_VM_INFO, + (host_info_t)&vmstat, &count); + mach_port_deallocate(mach_task_self(), mport); + if (unlikely(kerr != KERN_SUCCESS)) + return MDBX_ENOSYS; + const intptr_t avail_ram_pages = vmstat.free_count; +#elif defined(VM_TOTAL) || defined(VM_METER) + struct vmtotal info; + size_t len = sizeof(info); + static const int mib[] = { + CTL_VM, +#if defined(VM_TOTAL) + VM_TOTAL +#elif defined(VM_METER) + VM_METER +#endif + }; + if (sysctl( +#ifdef SYSCTL_LEGACY_NONCONST_MIB + (int *) +#endif + mib, + ARRAY_LENGTH(mib), &info, &len, NULL, 0) != 0) + return errno; + if (len != sizeof(info)) + return MDBX_ENOSYS; + const intptr_t avail_ram_pages = info.t_free; +#else +#error "FIXME: Get Available RAM" +#endif + if (avail_ram_pages < 1) + return MDBX_ENOSYS; + + return (volume_pages + redundancy_pages >= avail_ram_pages) + ? MDBX_RESULT_FALSE + : MDBX_RESULT_TRUE; +} + +/* Only a subset of the mdbx_env flags can be changed + * at runtime. Changing other flags requires closing the + * environment and re-opening it with the new flags. */ +#define CHANGEABLE \ + (MDBX_NOSYNC | MDBX_NOMETASYNC | MDBX_MAPASYNC | MDBX_NOMEMINIT | \ + MDBX_COALESCE | MDBX_PAGEPERTURB) +#define CHANGELESS \ + (MDBX_NOSUBDIR | MDBX_RDONLY | MDBX_WRITEMAP | MDBX_NOTLS | MDBX_NORDAHEAD | \ + MDBX_LIFORECLAIM | MDBX_EXCLUSIVE) + +#if VALID_FLAGS & PERSISTENT_FLAGS & (CHANGEABLE | CHANGELESS) +#error "Persistent DB flags & env flags overlap, but both go in mm_flags" +#endif + +int __cold mdbx_env_open(MDBX_env *env, const char *path, unsigned flags, + mode_t mode) { + if (unlikely(!env || !path)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (flags & ~(CHANGEABLE | CHANGELESS)) + return MDBX_EINVAL; + + if (env->me_fd != INVALID_HANDLE_VALUE || + (env->me_flags & MDBX_ENV_ACTIVE) != 0) + return MDBX_EPERM; + + size_t len_full, len = strlen(path); + if (flags & MDBX_NOSUBDIR) { + len_full = len + sizeof(MDBX_LOCK_SUFFIX) + len + 1; + } else { + len_full = len + sizeof(MDBX_LOCKNAME) + len + sizeof(MDBX_DATANAME); + } + char *lck_pathname = mdbx_malloc(len_full); + if (!lck_pathname) + return MDBX_ENOMEM; + + char *dxb_pathname; + if (flags & MDBX_NOSUBDIR) { + dxb_pathname = lck_pathname + len + sizeof(MDBX_LOCK_SUFFIX); + sprintf(lck_pathname, "%s" MDBX_LOCK_SUFFIX, path); + strcpy(dxb_pathname, path); + } else { + dxb_pathname = lck_pathname + len + sizeof(MDBX_LOCKNAME); + sprintf(lck_pathname, "%s" MDBX_LOCKNAME, path); + sprintf(dxb_pathname, "%s" MDBX_DATANAME, path); + } + + int rc = MDBX_SUCCESS; + flags |= env->me_flags; + if (flags & MDBX_RDONLY) { + /* LY: silently ignore irrelevant flags when + * we're only getting read access */ + flags &= ~(MDBX_WRITEMAP | MDBX_MAPASYNC | MDBX_NOSYNC | MDBX_NOMETASYNC | + MDBX_COALESCE | MDBX_LIFORECLAIM | MDBX_NOMEMINIT); + } else { + env->me_dirtylist = mdbx_calloc(MDBX_DPL_TXNFULL + 1, sizeof(MDBX_DP)); + if (!env->me_dirtylist) + rc = MDBX_ENOMEM; + } + + const uint32_t saved_me_flags = env->me_flags; + env->me_flags = (flags & ~MDBX_FATAL_ERROR) | MDBX_ENV_ACTIVE; + if (rc) + goto bailout; + + env->me_path = mdbx_strdup(path); + env->me_dbxs = mdbx_calloc(env->me_maxdbs, sizeof(MDBX_dbx)); + env->me_dbflags = mdbx_calloc(env->me_maxdbs, sizeof(env->me_dbflags[0])); + env->me_dbiseqs = mdbx_calloc(env->me_maxdbs, sizeof(env->me_dbiseqs[0])); + if (!(env->me_dbxs && env->me_path && env->me_dbflags && env->me_dbiseqs)) { + rc = MDBX_ENOMEM; + goto bailout; + } + env->me_dbxs[FREE_DBI].md_cmp = + mdbx_cmp_int_align4; /* aligned MDBX_INTEGERKEY */ + + int oflags; + if (F_ISSET(flags, MDBX_RDONLY)) + oflags = O_RDONLY; + else if (mode != 0) { + if ((flags & MDBX_NOSUBDIR) == 0) { +#if defined(_WIN32) || defined(_WIN64) + if (!CreateDirectoryA(path, nullptr)) { + rc = GetLastError(); + if (rc != ERROR_ALREADY_EXISTS) + goto bailout; + } +#else + const mode_t dir_mode = + (/* inherit read/write permissions for group and others */ mode & + (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) | + /* always add read/write/search for owner */ S_IRWXU | + ((mode & S_IRGRP) ? /* +search if readable by group */ S_IXGRP : 0) | + ((mode & S_IROTH) ? /* +search if readable by others */ S_IXOTH : 0); + if (mkdir(path, dir_mode)) { + rc = errno; + if (rc != EEXIST) + goto bailout; + } +#endif + } + oflags = O_RDWR | O_CREAT; + } else + oflags = O_RDWR; + + rc = mdbx_openfile(dxb_pathname, oflags, mode, &env->me_fd, + (env->me_flags & MDBX_EXCLUSIVE) ? true : false); + if (rc != MDBX_SUCCESS) + goto bailout; + + const int lck_rc = mdbx_setup_lck(env, lck_pathname, mode); + if (MDBX_IS_ERROR(lck_rc)) { + rc = lck_rc; + goto bailout; + } + + const int dxb_rc = mdbx_setup_dxb(env, lck_rc); + if (MDBX_IS_ERROR(dxb_rc)) { + rc = dxb_rc; + goto bailout; + } + + mdbx_debug("opened dbenv %p", (void *)env); + if (env->me_lck) { + const unsigned mode_flags = + MDBX_WRITEMAP | MDBX_NOSYNC | MDBX_NOMETASYNC | MDBX_MAPASYNC; + if (lck_rc == MDBX_RESULT_TRUE) { + env->me_lck->mti_envmode = env->me_flags & (mode_flags | MDBX_RDONLY); + rc = mdbx_lck_downgrade(env); + mdbx_debug("lck-downgrade-%s: rc %i", + (env->me_flags & MDBX_EXCLUSIVE) ? "partial" : "full", rc); + if (rc != MDBX_SUCCESS) + goto bailout; + } else { + rc = mdbx_reader_check0(env, false, NULL); + if (MDBX_IS_ERROR(rc)) + goto bailout; + if ((env->me_flags & MDBX_RDONLY) == 0) { + while (env->me_lck->mti_envmode == MDBX_RDONLY) { + if (atomic_cas32(&env->me_lck->mti_envmode, MDBX_RDONLY, + env->me_flags & mode_flags)) + break; + atomic_yield(); + } + if ((env->me_lck->mti_envmode ^ env->me_flags) & mode_flags) { + mdbx_error("%s", "current mode/flags incompatible with requested"); + rc = MDBX_INCOMPATIBLE; + goto bailout; + } + } + } + + if ((env->me_flags & MDBX_NOTLS) == 0) { + rc = mdbx_rthc_alloc(&env->me_txkey, &env->me_lck->mti_readers[0], + &env->me_lck->mti_readers[env->me_maxreaders]); + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + env->me_flags |= MDBX_ENV_TXKEY; + } + } + + if ((flags & MDBX_RDONLY) == 0) { + rc = MDBX_ENOMEM; + MDBX_txn *txn; + int tsize = sizeof(MDBX_txn), + size = + tsize + env->me_maxdbs * (sizeof(MDBX_db) + sizeof(MDBX_cursor *) + + sizeof(unsigned) + 1); + if ((env->me_pbuf = mdbx_calloc( + 1 /* page buffer */ + 1 /* page killer bufer */, env->me_psize)) && + (txn = mdbx_calloc(1, size))) { + txn->mt_dbs = (MDBX_db *)((char *)txn + tsize); + txn->mt_cursors = (MDBX_cursor **)(txn->mt_dbs + env->me_maxdbs); + txn->mt_dbiseqs = (unsigned *)(txn->mt_cursors + env->me_maxdbs); + txn->mt_dbflags = (uint8_t *)(txn->mt_dbiseqs + env->me_maxdbs); + txn->mt_env = env; + txn->mt_dbxs = env->me_dbxs; + txn->mt_flags = MDBX_TXN_FINISHED; + env->me_txn0 = txn; + txn->tw.retired_pages = mdbx_pnl_alloc(MDBX_PNL_INITIAL); + txn->tw.reclaimed_pglist = mdbx_pnl_alloc(MDBX_PNL_INITIAL); + if (txn->tw.retired_pages && txn->tw.reclaimed_pglist) + rc = MDBX_SUCCESS; + } + } + +#if MDBX_DEBUG + if (rc == MDBX_SUCCESS) { + MDBX_meta *meta = mdbx_meta_head(env); + MDBX_db *db = &meta->mm_dbs[MAIN_DBI]; + + mdbx_debug("opened database version %u, pagesize %u", + (uint8_t)meta->mm_magic_and_version, env->me_psize); + mdbx_debug("using meta page %" PRIaPGNO ", txn %" PRIaTXN, + data_page(meta)->mp_pgno, mdbx_meta_txnid_fluid(env, meta)); + mdbx_debug("depth: %u", db->md_depth); + mdbx_debug("entries: %" PRIu64, db->md_entries); + mdbx_debug("branch pages: %" PRIaPGNO, db->md_branch_pages); + mdbx_debug("leaf pages: %" PRIaPGNO, db->md_leaf_pages); + mdbx_debug("overflow pages: %" PRIaPGNO, db->md_overflow_pages); + mdbx_debug("root: %" PRIaPGNO, db->md_root); + mdbx_debug("schema_altered: %" PRIaTXN, db->md_mod_txnid); + } +#endif + +bailout: + if (rc) { + rc = mdbx_env_close0(env) ? MDBX_PANIC : rc; + env->me_flags = saved_me_flags | MDBX_FATAL_ERROR; + } + mdbx_free(lck_pathname); + return rc; +} + +/* Destroy resources from mdbx_env_open(), clear our readers & DBIs */ +static int __cold mdbx_env_close0(MDBX_env *env) { + if (!(env->me_flags & MDBX_ENV_ACTIVE)) { + mdbx_ensure(env, env->me_lcklist_next == nullptr); + return MDBX_SUCCESS; + } + + env->me_flags &= ~MDBX_ENV_ACTIVE; + env->me_oldest = nullptr; + env->me_sync_timestamp = nullptr; + env->me_autosync_period = nullptr; + env->me_unsynced_pages = nullptr; + env->me_autosync_threshold = nullptr; + env->me_discarded_tail = nullptr; + env->me_meta_sync_txnid = nullptr; + if (env->me_flags & MDBX_ENV_TXKEY) + mdbx_rthc_remove(env->me_txkey); + + lcklist_lock(); + const int rc = lcklist_detach_locked(env); + lcklist_unlock(); + + if (env->me_map) { + mdbx_munmap(&env->me_dxb_mmap); +#ifdef MDBX_USE_VALGRIND + VALGRIND_DISCARD(env->me_valgrind_handle); + env->me_valgrind_handle = -1; +#endif + } + if (env->me_fd != INVALID_HANDLE_VALUE) { + (void)mdbx_closefile(env->me_fd); + env->me_fd = INVALID_HANDLE_VALUE; + } + + if (env->me_lck) + mdbx_munmap(&env->me_lck_mmap); + + if (env->me_lfd != INVALID_HANDLE_VALUE) { + (void)mdbx_closefile(env->me_lfd); + env->me_lfd = INVALID_HANDLE_VALUE; + } + + if (env->me_dbxs) { + for (unsigned i = env->me_maxdbs; --i >= CORE_DBS;) + mdbx_free(env->me_dbxs[i].md_name.iov_base); + mdbx_free(env->me_dbxs); + } + mdbx_free(env->me_pbuf); + mdbx_free(env->me_dbiseqs); + mdbx_free(env->me_dbflags); + mdbx_free(env->me_path); + mdbx_free(env->me_dirtylist); + if (env->me_txn0) { + mdbx_txl_free(env->me_txn0->tw.lifo_reclaimed); + mdbx_pnl_free(env->me_txn0->tw.retired_pages); + mdbx_pnl_free(env->me_txn0->tw.spill_pages); + mdbx_pnl_free(env->me_txn0->tw.reclaimed_pglist); + mdbx_free(env->me_txn0); + } + env->me_flags = 0; + return rc; +} + +int __cold mdbx_env_close_ex(MDBX_env *env, int dont_sync) { + MDBX_page *dp; + int rc = MDBX_SUCCESS; + + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + +#if MDBX_TXN_CHECKPID || !(defined(_WIN32) || defined(_WIN64)) + /* Check the PID even if MDBX_TXN_CHECKPID=0 on non-Windows + * platforms (i.e. where fork() is available). + * This is required to legitimize a call after fork() + * from a child process, that should be allowed to free resources. */ + if (unlikely(env->me_pid != mdbx_getpid())) + env->me_flags |= MDBX_FATAL_ERROR; +#endif /* MDBX_TXN_CHECKPID */ + + if ((env->me_flags & (MDBX_RDONLY | MDBX_FATAL_ERROR)) == 0) { + if (env->me_txn0 && env->me_txn0->mt_owner && + env->me_txn0->mt_owner != mdbx_thread_self()) + return MDBX_BUSY; + if (!dont_sync) { +#if defined(_WIN32) || defined(_WIN64) + /* On windows, without blocking is impossible to determine whether another + * process is running a writing transaction or not. + * Because in the "owner died" condition kernel don't release + * file lock immediately. */ + rc = mdbx_env_sync_ex(env, true, false); + rc = (rc == MDBX_RESULT_TRUE) ? MDBX_SUCCESS : rc; +#else + rc = mdbx_env_sync_ex(env, true, true); + rc = (rc == MDBX_BUSY || rc == EAGAIN || rc == EACCES || rc == EBUSY || + rc == EWOULDBLOCK || rc == MDBX_RESULT_TRUE) + ? MDBX_SUCCESS + : rc; +#endif + } + } + + while ((dp = env->me_dpages) != NULL) { + ASAN_UNPOISON_MEMORY_REGION(&dp->mp_next, sizeof(dp->mp_next)); + VALGRIND_MAKE_MEM_DEFINED(&dp->mp_next, sizeof(dp->mp_next)); + env->me_dpages = dp->mp_next; + mdbx_free(dp); + } + VALGRIND_DESTROY_MEMPOOL(env); + + rc = mdbx_env_close0(env) ? MDBX_PANIC : rc; + mdbx_ensure(env, mdbx_fastmutex_destroy(&env->me_dbi_lock) == MDBX_SUCCESS); +#if defined(_WIN32) || defined(_WIN64) + /* me_remap_guard don't have destructor (Slim Reader/Writer Lock) */ + DeleteCriticalSection(&env->me_windowsbug_lock); +#else + mdbx_ensure(env, + mdbx_fastmutex_destroy(&env->me_remap_guard) == MDBX_SUCCESS); +#endif /* Windows */ + +#ifdef MDBX_OSAL_LOCK + mdbx_ensure(env, mdbx_fastmutex_destroy(&env->me_lckless_stub.wmutex) == + MDBX_SUCCESS); +#endif + + mdbx_ensure(env, env->me_lcklist_next == nullptr); + env->me_pid = 0; + env->me_signature = 0; + mdbx_free(env); + + return rc; +} + +__cold int mdbx_env_close(MDBX_env *env) { + return mdbx_env_close_ex(env, false); +} + +/* Compare two items pointing at aligned unsigned int's. */ +static int __hot mdbx_cmp_int_align4(const MDBX_val *a, const MDBX_val *b) { + mdbx_assert(NULL, a->iov_len == b->iov_len); + switch (a->iov_len) { + case 4: + return CMP2INT(unaligned_peek_u32(4, a->iov_base), + unaligned_peek_u32(4, b->iov_base)); + case 8: + return CMP2INT(unaligned_peek_u64(4, a->iov_base), + unaligned_peek_u64(4, b->iov_base)); + default: + mdbx_assert_fail(NULL, "invalid size for INTEGERKEY/INTEGERDUP", __func__, + __LINE__); + return 0; + } +} + +/* Compare two items pointing at 2-byte aligned unsigned int's. */ +static int __hot mdbx_cmp_int_align2(const MDBX_val *a, const MDBX_val *b) { + mdbx_assert(NULL, a->iov_len == b->iov_len); + switch (a->iov_len) { + case 4: + return CMP2INT(unaligned_peek_u32(2, a->iov_base), + unaligned_peek_u32(2, b->iov_base)); + case 8: + return CMP2INT(unaligned_peek_u64(2, a->iov_base), + unaligned_peek_u64(2, b->iov_base)); + default: + mdbx_assert_fail(NULL, "invalid size for INTEGERKEY/INTEGERDUP", __func__, + __LINE__); + return 0; + } +} + +/* Compare two items pointing at unsigneds of unknown alignment. + * + * This is also set as MDBX_INTEGERDUP|MDBX_DUPFIXED's MDBX_dbx.md_dcmp. */ +static int __hot mdbx_cmp_int_unaligned(const MDBX_val *a, const MDBX_val *b) { + mdbx_assert(NULL, a->iov_len == b->iov_len); + switch (a->iov_len) { + case 4: + return CMP2INT(unaligned_peek_u32(1, a->iov_base), + unaligned_peek_u32(1, b->iov_base)); + case 8: + return CMP2INT(unaligned_peek_u64(1, a->iov_base), + unaligned_peek_u64(1, b->iov_base)); + default: + mdbx_assert_fail(NULL, "invalid size for INTEGERKEY/INTEGERDUP", __func__, + __LINE__); + return 0; + } +} + +/* Compare two items lexically */ +static int __hot mdbx_cmp_memn(const MDBX_val *a, const MDBX_val *b) { + if (a->iov_len == b->iov_len) + return memcmp(a->iov_base, b->iov_base, a->iov_len); + + const int diff_len = (a->iov_len < b->iov_len) ? -1 : 1; + const size_t shortest = (a->iov_len < b->iov_len) ? a->iov_len : b->iov_len; + int diff_data = memcmp(a->iov_base, b->iov_base, shortest); + return likely(diff_data) ? diff_data : diff_len; +} + +/* Compare two items in reverse byte order */ +static int __hot mdbx_cmp_memnr(const MDBX_val *a, const MDBX_val *b) { + const uint8_t *pa = (const uint8_t *)a->iov_base + a->iov_len; + const uint8_t *pb = (const uint8_t *)b->iov_base + b->iov_len; + const size_t shortest = (a->iov_len < b->iov_len) ? a->iov_len : b->iov_len; + + const uint8_t *const end = pa - shortest; + while (pa != end) { + int diff = *--pa - *--pb; + if (likely(diff)) + return diff; + } + return CMP2INT(a->iov_len, b->iov_len); +} + +/* Search for key within a page, using binary search. + * Returns the smallest entry larger or equal to the key. + * If exactp is non-null, stores whether the found entry was an exact match + * in *exactp (1 or 0). + * Updates the cursor index with the index of the found entry. + * If no entry larger or equal to the key is found, returns NULL. */ +static MDBX_node *__hot mdbx_node_search(MDBX_cursor *mc, MDBX_val *key, + int *exactp) { + int low, high; + int rc = 0; + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + MDBX_node *node = nullptr; + MDBX_val nodekey; + MDBX_cmp_func *cmp; + DKBUF; + + const unsigned nkeys = page_numkeys(mp); + + mdbx_debug("searching %u keys in %s %spage %" PRIaPGNO, nkeys, + IS_LEAF(mp) ? "leaf" : "branch", IS_SUBP(mp) ? "sub-" : "", + mp->mp_pgno); + + low = IS_LEAF(mp) ? 0 : 1; + high = nkeys - 1; + cmp = mc->mc_dbx->md_cmp; + + /* Branch pages have no data, so if using integer keys, + * alignment is guaranteed. Use faster mdbx_cmp_int_ai. + */ + if (cmp == mdbx_cmp_int_align2 && IS_BRANCH(mp)) + cmp = mdbx_cmp_int_align4; + + unsigned i = 0; + if (IS_LEAF2(mp)) { + mdbx_cassert(mc, mp->mp_leaf2_ksize == mc->mc_db->md_xsize); + nodekey.iov_len = mp->mp_leaf2_ksize; + node = (MDBX_node *)(intptr_t)-1; /* fake */ + while (low <= high) { + i = (low + high) >> 1; + nodekey.iov_base = page_leaf2key(mp, i, nodekey.iov_len); + mdbx_cassert(mc, (char *)mp + mc->mc_txn->mt_env->me_psize >= + (char *)nodekey.iov_base + nodekey.iov_len); + rc = cmp(key, &nodekey); + mdbx_debug("found leaf index %u [%s], rc = %i", i, DKEY(&nodekey), rc); + if (rc == 0) + break; + if (rc > 0) + low = i + 1; + else + high = i - 1; + } + } else { + while (low <= high) { + i = (low + high) >> 1; + + node = page_node(mp, i); + nodekey.iov_len = node_ks(node); + nodekey.iov_base = node_key(node); + mdbx_cassert(mc, (char *)mp + mc->mc_txn->mt_env->me_psize >= + (char *)nodekey.iov_base + nodekey.iov_len); + + rc = cmp(key, &nodekey); + if (IS_LEAF(mp)) + mdbx_debug("found leaf index %u [%s], rc = %i", i, DKEY(&nodekey), rc); + else + mdbx_debug("found branch index %u [%s -> %" PRIaPGNO "], rc = %i", i, + DKEY(&nodekey), node_pgno(node), rc); + if (rc == 0) + break; + if (rc > 0) + low = i + 1; + else + high = i - 1; + } + } + + if (rc > 0) /* Found entry is less than the key. */ + i++; /* Skip to get the smallest entry larger than key. */ + + if (exactp) + *exactp = (rc == 0 && nkeys > 0); + /* store the key index */ + mdbx_cassert(mc, i <= UINT16_MAX); + mc->mc_ki[mc->mc_top] = (indx_t)i; + if (i >= nkeys) + /* There is no entry larger or equal to the key. */ + return NULL; + + /* page_node is fake for LEAF2 */ + return IS_LEAF2(mp) ? node : page_node(mp, i); +} + +#if 0 /* unused for now */ +static void mdbx_cursor_adjust(MDBX_cursor *mc, func) { + MDBX_cursor *m2; + + for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2 = m2->mc_next) { + if (m2->mc_pg[m2->mc_top] == mc->mc_pg[mc->mc_top]) { + func(mc, m2); + } + } +} +#endif + +/* Pop a page off the top of the cursor's stack. */ +static void mdbx_cursor_pop(MDBX_cursor *mc) { + if (mc->mc_snum) { + mdbx_debug("popped page %" PRIaPGNO " off db %d cursor %p", + mc->mc_pg[mc->mc_top]->mp_pgno, DDBI(mc), (void *)mc); + + mc->mc_snum--; + if (mc->mc_snum) { + mc->mc_top--; + } else { + mc->mc_flags &= ~C_INITIALIZED; + } + } +} + +/* Push a page onto the top of the cursor's stack. + * Set MDBX_TXN_ERROR on failure. */ +static int mdbx_cursor_push(MDBX_cursor *mc, MDBX_page *mp) { + mdbx_debug("pushing page %" PRIaPGNO " on db %d cursor %p", mp->mp_pgno, + DDBI(mc), (void *)mc); + + if (unlikely(mc->mc_snum >= CURSOR_STACK)) { + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_CURSOR_FULL; + } + + mdbx_cassert(mc, mc->mc_snum < UINT16_MAX); + mc->mc_top = mc->mc_snum++; + mc->mc_pg[mc->mc_top] = mp; + mc->mc_ki[mc->mc_top] = 0; + + return MDBX_SUCCESS; +} + +/* Find the address of the page corresponding to a given page number. + * Set MDBX_TXN_ERROR on failure. + * + * [in] mc the cursor accessing the page. + * [in] pgno the page number for the page to retrieve. + * [out] ret address of a pointer where the page's address will be + * stored. + * [out] lvl dirtylist inheritance level of found page. 1=current txn, + * 0=mapped page. + * + * Returns 0 on success, non-zero on failure. */ +__hot static int mdbx_page_get(MDBX_cursor *mc, pgno_t pgno, MDBX_page **ret, + int *lvl) { + MDBX_txn *txn = mc->mc_txn; + if (unlikely(pgno >= txn->mt_next_pgno)) { + mdbx_debug("page %" PRIaPGNO " not found", pgno); + txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PAGE_NOTFOUND; + } + + MDBX_env *env = txn->mt_env; + MDBX_page *p = NULL; + int level; + if ((txn->mt_flags & (MDBX_RDONLY | MDBX_WRITEMAP)) == 0) { + level = 1; + do { + /* Spilled pages were dirtied in this txn and flushed + * because the dirty list got full. Bring this page + * back in from the map (but don't unspill it here, + * leave that unless page_touch happens again). */ + if (txn->tw.spill_pages && mdbx_pnl_exist(txn->tw.spill_pages, pgno << 1)) + goto mapped; + p = mdbx_dpl_find(txn->tw.dirtylist, pgno); + if (p) + goto done; + level++; + } while ((txn = txn->mt_parent) != NULL); + } + level = 0; + +mapped: + p = pgno2page(env, pgno); + +done: + txn = nullptr /* avoid future use */; + if (unlikely(p->mp_pgno != pgno)) { + mdbx_error("mismatch pgno %" PRIaPGNO " (actual) != %" PRIaPGNO + " (expected)", + p->mp_pgno, pgno); + return MDBX_CORRUPTED; + } + + if (unlikely(p->mp_upper < p->mp_lower || ((p->mp_lower | p->mp_upper) & 1) || + PAGEHDRSZ + p->mp_upper > env->me_psize) && + !IS_OVERFLOW(p)) { + mdbx_error("invalid page lower(%u)/upper(%u), pg-limit %u", p->mp_lower, + p->mp_upper, page_space(env)); + return MDBX_CORRUPTED; + } + /* TODO: more checks here, including p->mp_validator */ + + if (mdbx_audit_enabled()) { + int err = mdbx_page_check(env, p, true); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + + *ret = p; + if (lvl) + *lvl = level; + return MDBX_SUCCESS; +} + +/* Finish mdbx_page_search() / mdbx_page_search_lowest(). + * The cursor is at the root page, set up the rest of it. */ +__hot static int mdbx_page_search_root(MDBX_cursor *mc, MDBX_val *key, + int flags) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + int rc; + DKBUF; + + while (IS_BRANCH(mp)) { + MDBX_node *node; + int i; + + mdbx_debug("branch page %" PRIaPGNO " has %u keys", mp->mp_pgno, + page_numkeys(mp)); + /* Don't assert on branch pages in the GC. We can get here + * while in the process of rebalancing a GC branch page; we must + * let that proceed. ITS#8336 */ + mdbx_cassert(mc, !mc->mc_dbi || page_numkeys(mp) > 1); + mdbx_debug("found index 0 to page %" PRIaPGNO, node_pgno(page_node(mp, 0))); + + if (flags & (MDBX_PS_FIRST | MDBX_PS_LAST)) { + i = 0; + if (flags & MDBX_PS_LAST) { + i = page_numkeys(mp) - 1; + /* if already init'd, see if we're already in right place */ + if (mc->mc_flags & C_INITIALIZED) { + if (mc->mc_ki[mc->mc_top] == i) { + mc->mc_top = mc->mc_snum++; + mp = mc->mc_pg[mc->mc_top]; + goto ready; + } + } + } + } else { + int exact; + node = mdbx_node_search(mc, key, &exact); + if (node == NULL) + i = page_numkeys(mp) - 1; + else { + i = mc->mc_ki[mc->mc_top]; + if (!exact) { + mdbx_cassert(mc, i > 0); + i--; + } + } + mdbx_debug("following index %u for key [%s]", i, DKEY(key)); + } + + mdbx_cassert(mc, i < (int)page_numkeys(mp)); + node = page_node(mp, i); + + if (unlikely((rc = mdbx_page_get(mc, node_pgno(node), &mp, NULL)) != 0)) + return rc; + + mc->mc_ki[mc->mc_top] = (indx_t)i; + if (unlikely(rc = mdbx_cursor_push(mc, mp))) + return rc; + + ready: + if (flags & MDBX_PS_MODIFY) { + if (unlikely((rc = mdbx_page_touch(mc)) != 0)) + return rc; + mp = mc->mc_pg[mc->mc_top]; + } + } + + if (unlikely(!IS_LEAF(mp))) { + mdbx_debug("internal error, index points to a page with 0x%02x flags!?", + mp->mp_flags); + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_CORRUPTED; + } + + mdbx_debug("found leaf page %" PRIaPGNO " for key [%s]", mp->mp_pgno, + DKEY(key)); + mc->mc_flags |= C_INITIALIZED; + mc->mc_flags &= ~C_EOF; + + return MDBX_SUCCESS; +} + +static int mdbx_fetch_sdb(MDBX_txn *txn, MDBX_dbi dbi) { + MDBX_cursor mc; + if (unlikely(TXN_DBI_CHANGED(txn, dbi))) + return MDBX_BAD_DBI; + int rc = mdbx_cursor_init(&mc, txn, MAIN_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_page_search(&mc, &txn->mt_dbxs[dbi].md_name, 0); + if (unlikely(rc != MDBX_SUCCESS)) + return (rc == MDBX_NOTFOUND) ? MDBX_BAD_DBI : rc; + + MDBX_val data; + int exact = 0; + MDBX_node *node = mdbx_node_search(&mc, &txn->mt_dbxs[dbi].md_name, &exact); + if (unlikely(!exact)) + return MDBX_BAD_DBI; + if (unlikely((node_flags(node) & (F_DUPDATA | F_SUBDATA)) != F_SUBDATA)) + return MDBX_INCOMPATIBLE; /* not a named DB */ + rc = mdbx_node_read(&mc, node, &data); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(data.iov_len < sizeof(MDBX_db))) + return MDBX_INCOMPATIBLE; /* not a named DB */ + + uint16_t md_flags = UNALIGNED_PEEK_16(data.iov_base, MDBX_db, md_flags); + /* The txn may not know this DBI, or another process may + * have dropped and recreated the DB with other flags. */ + if (unlikely((txn->mt_dbs[dbi].md_flags & PERSISTENT_FLAGS) != md_flags)) + return MDBX_INCOMPATIBLE; + + memcpy(&txn->mt_dbs[dbi], data.iov_base, sizeof(MDBX_db)); + txn->mt_dbflags[dbi] &= ~DB_STALE; + return MDBX_SUCCESS; +} + +/* Search for the lowest key under the current branch page. + * This just bypasses a numkeys check in the current page + * before calling mdbx_page_search_root(), because the callers + * are all in situations where the current page is known to + * be underfilled. */ +__hot static int mdbx_page_search_lowest(MDBX_cursor *mc) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + mdbx_cassert(mc, IS_BRANCH(mp)); + MDBX_node *node = page_node(mp, 0); + int rc; + + if (unlikely((rc = mdbx_page_get(mc, node_pgno(node), &mp, NULL)) != 0)) + return rc; + + mc->mc_ki[mc->mc_top] = 0; + if (unlikely(rc = mdbx_cursor_push(mc, mp))) + return rc; + return mdbx_page_search_root(mc, NULL, MDBX_PS_FIRST); +} + +/* Search for the page a given key should be in. + * Push it and its parent pages on the cursor stack. + * + * [in,out] mc the cursor for this operation. + * [in] key the key to search for, or NULL for first/last page. + * [in] flags If MDBX_PS_MODIFY is set, visited pages in the DB + * are touched (updated with new page numbers). + * If MDBX_PS_FIRST or MDBX_PS_LAST is set, find first or last + * leaf. + * This is used by mdbx_cursor_first() and mdbx_cursor_last(). + * If MDBX_PS_ROOTONLY set, just fetch root node, no further + * lookups. + * + * Returns 0 on success, non-zero on failure. */ +__hot static int mdbx_page_search(MDBX_cursor *mc, MDBX_val *key, int flags) { + int rc; + pgno_t root; + + /* Make sure the txn is still viable, then find the root from + * the txn's db table and set it as the root of the cursor's stack. */ + if (unlikely(mc->mc_txn->mt_flags & MDBX_TXN_BLOCKED)) { + mdbx_debug("%s", "transaction has failed, must abort"); + return MDBX_BAD_TXN; + } + + /* Make sure we're using an up-to-date root */ + if (unlikely(*mc->mc_dbflag & DB_STALE)) { + rc = mdbx_fetch_sdb(mc->mc_txn, mc->mc_dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + root = mc->mc_db->md_root; + + if (unlikely(root == P_INVALID)) { /* Tree is empty. */ + mdbx_debug("%s", "tree is empty"); + return MDBX_NOTFOUND; + } + + mdbx_cassert(mc, root >= NUM_METAS); + if (!mc->mc_pg[0] || mc->mc_pg[0]->mp_pgno != root) + if (unlikely((rc = mdbx_page_get(mc, root, &mc->mc_pg[0], NULL)) != 0)) + return rc; + + mc->mc_snum = 1; + mc->mc_top = 0; + + mdbx_debug("db %d root page %" PRIaPGNO " has flags 0x%X", DDBI(mc), root, + mc->mc_pg[0]->mp_flags); + + if (flags & MDBX_PS_MODIFY) { + if (unlikely(rc = mdbx_page_touch(mc))) + return rc; + } + + if (flags & MDBX_PS_ROOTONLY) + return MDBX_SUCCESS; + + return mdbx_page_search_root(mc, key, flags); +} + +/* Return the data associated with a given node. + * + * [in] mc The cursor for this operation. + * [in] leaf The node being read. + * [out] data Updated to point to the node's data. + * + * Returns 0 on success, non-zero on failure. */ +static __inline int mdbx_node_read(MDBX_cursor *mc, MDBX_node *node, + MDBX_val *data) { + data->iov_len = node_ds(node); + data->iov_base = node_data(node); + if (unlikely(F_ISSET(node_flags(node), F_BIGDATA))) { + /* Read overflow data. */ + MDBX_page *omp; /* overflow page */ + int rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL); + if (unlikely((rc != MDBX_SUCCESS))) { + mdbx_debug("read overflow page %" PRIaPGNO " failed", + node_largedata_pgno(node)); + return rc; + } + data->iov_base = page_data(omp); + } + return MDBX_SUCCESS; +} + +int mdbx_get(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data) { + DKBUF; + mdbx_debug("===> get db %u key [%s]", dbi, DKEY(key)); + + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!key || !data)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + MDBX_cursor_couple cx; + rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + int exact = 0; + return mdbx_cursor_set(&cx.outer, key, data, MDBX_SET, &exact); +} + +int mdbx_get_nearest(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, + MDBX_val *data) { + DKBUF; + mdbx_debug("===> get db %u key [%s]", dbi, DKEY(key)); + + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!key || !data)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED)) + return MDBX_BAD_TXN; + + MDBX_cursor_couple cx; + rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + MDBX_val save_data = *data; + int exact = 0; + rc = mdbx_cursor_set(&cx.outer, key, data, MDBX_SET_RANGE, &exact); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (exact && (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) != 0) { + *data = save_data; + exact = 0; + rc = mdbx_cursor_set(&cx.outer, key, data, MDBX_GET_BOTH_RANGE, &exact); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + return exact ? MDBX_SUCCESS : MDBX_RESULT_TRUE; +} + +int mdbx_get_ex(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, + size_t *values_count) { + DKBUF; + mdbx_debug("===> get db %u key [%s]", dbi, DKEY(key)); + + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!key || !data)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + MDBX_cursor_couple cx; + rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + int exact = 0; + rc = mdbx_cursor_set(&cx.outer, key, data, MDBX_SET_KEY, &exact); + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_NOTFOUND && values_count) + *values_count = 0; + return rc; + } + + if (values_count) { + *values_count = 1; + if (cx.outer.mc_xcursor != NULL) { + MDBX_node *node = page_node(cx.outer.mc_pg[cx.outer.mc_top], + cx.outer.mc_ki[cx.outer.mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + mdbx_tassert(txn, cx.outer.mc_xcursor == &cx.inner && + (cx.inner.mx_cursor.mc_flags & C_INITIALIZED)); + *values_count = + (sizeof(*values_count) >= sizeof(cx.inner.mx_db.md_entries) || + cx.inner.mx_db.md_entries <= PTRDIFF_MAX) + ? (size_t)cx.inner.mx_db.md_entries + : PTRDIFF_MAX; + } + } + } + return MDBX_SUCCESS; +} + +/* Find a sibling for a page. + * Replaces the page at the top of the cursor's stack with the specified + * sibling, if one exists. + * + * [in] mc The cursor for this operation. + * [in] move_right Non-zero if the right sibling is requested, + * otherwise the left sibling. + * + * Returns 0 on success, non-zero on failure. */ +static int mdbx_cursor_sibling(MDBX_cursor *mc, int move_right) { + int rc; + MDBX_node *indx; + MDBX_page *mp; + + if (unlikely(mc->mc_snum < 2)) + return MDBX_NOTFOUND; /* root has no siblings */ + + mdbx_cursor_pop(mc); + mdbx_debug("parent page is page %" PRIaPGNO ", index %u", + mc->mc_pg[mc->mc_top]->mp_pgno, mc->mc_ki[mc->mc_top]); + + if (move_right + ? (mc->mc_ki[mc->mc_top] + 1u >= page_numkeys(mc->mc_pg[mc->mc_top])) + : (mc->mc_ki[mc->mc_top] == 0)) { + mdbx_debug("no more keys left, moving to %s sibling", + move_right ? "right" : "left"); + if (unlikely((rc = mdbx_cursor_sibling(mc, move_right)) != MDBX_SUCCESS)) { + /* undo cursor_pop before returning */ + mc->mc_top++; + mc->mc_snum++; + return rc; + } + } else { + if (move_right) + mc->mc_ki[mc->mc_top]++; + else + mc->mc_ki[mc->mc_top]--; + mdbx_debug("just moving to %s index key %u", move_right ? "right" : "left", + mc->mc_ki[mc->mc_top]); + } + mdbx_cassert(mc, IS_BRANCH(mc->mc_pg[mc->mc_top])); + + indx = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (unlikely((rc = mdbx_page_get(mc, node_pgno(indx), &mp, NULL)) != 0)) { + /* mc will be inconsistent if caller does mc_snum++ as above */ + mc->mc_flags &= ~(C_INITIALIZED | C_EOF); + return rc; + } + + rc = mdbx_cursor_push(mc, mp); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + if (!move_right) + mc->mc_ki[mc->mc_top] = (indx_t)page_numkeys(mp) - 1; + + return MDBX_SUCCESS; +} + +/* Move the cursor to the next data item. */ +static int mdbx_cursor_next(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, + MDBX_cursor_op op) { + MDBX_page *mp; + MDBX_node *node; + int rc; + + if ((mc->mc_flags & C_DEL) && op == MDBX_NEXT_DUP) + return MDBX_NOTFOUND; + + if (!(mc->mc_flags & C_INITIALIZED)) + return mdbx_cursor_first(mc, key, data); + + mp = mc->mc_pg[mc->mc_top]; + if (mc->mc_flags & C_EOF) { + if (mc->mc_ki[mc->mc_top] + 1u >= page_numkeys(mp)) + return MDBX_NOTFOUND; + mc->mc_flags ^= C_EOF; + } + + if (mc->mc_db->md_flags & MDBX_DUPSORT) { + node = page_node(mp, mc->mc_ki[mc->mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + if (op == MDBX_NEXT || op == MDBX_NEXT_DUP) { + rc = + mdbx_cursor_next(&mc->mc_xcursor->mx_cursor, data, NULL, MDBX_NEXT); + if (op != MDBX_NEXT || rc != MDBX_NOTFOUND) { + if (likely(rc == MDBX_SUCCESS)) + get_key_optional(node, key); + return rc; + } + } + } else { + mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + if (op == MDBX_NEXT_DUP) + return MDBX_NOTFOUND; + } + } + + mdbx_debug("cursor_next: top page is %" PRIaPGNO " in cursor %p", mp->mp_pgno, + (void *)mc); + if (mc->mc_flags & C_DEL) { + mc->mc_flags ^= C_DEL; + goto skip; + } + + if (mc->mc_ki[mc->mc_top] + 1u >= page_numkeys(mp)) { + mdbx_debug("%s", "=====> move to next sibling page"); + if (unlikely((rc = mdbx_cursor_sibling(mc, 1)) != MDBX_SUCCESS)) { + mc->mc_flags |= C_EOF; + return rc; + } + mp = mc->mc_pg[mc->mc_top]; + mdbx_debug("next page is %" PRIaPGNO ", key index %u", mp->mp_pgno, + mc->mc_ki[mc->mc_top]); + } else + mc->mc_ki[mc->mc_top]++; + +skip: + mdbx_debug("==> cursor points to page %" PRIaPGNO + " with %u keys, key index %u", + mp->mp_pgno, page_numkeys(mp), mc->mc_ki[mc->mc_top]); + + if (IS_LEAF2(mp)) { + if (likely(key)) { + key->iov_len = mc->mc_db->md_xsize; + key->iov_base = page_leaf2key(mp, mc->mc_ki[mc->mc_top], key->iov_len); + } + return MDBX_SUCCESS; + } + + mdbx_cassert(mc, IS_LEAF(mp)); + node = page_node(mp, mc->mc_ki[mc->mc_top]); + + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + if (data) { + if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + return rc; + + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + } + + get_key_optional(node, key); + return MDBX_SUCCESS; +} + +/* Move the cursor to the previous data item. */ +static int mdbx_cursor_prev(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, + MDBX_cursor_op op) { + MDBX_page *mp; + MDBX_node *node; + int rc; + + if ((mc->mc_flags & C_DEL) && op == MDBX_PREV_DUP) + return MDBX_NOTFOUND; + + if (!(mc->mc_flags & C_INITIALIZED)) { + rc = mdbx_cursor_last(mc, key, data); + if (unlikely(rc)) + return rc; + mc->mc_ki[mc->mc_top]++; + } + + mp = mc->mc_pg[mc->mc_top]; + if ((mc->mc_db->md_flags & MDBX_DUPSORT) && + mc->mc_ki[mc->mc_top] < page_numkeys(mp)) { + node = page_node(mp, mc->mc_ki[mc->mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + if (op == MDBX_PREV || op == MDBX_PREV_DUP) { + rc = + mdbx_cursor_prev(&mc->mc_xcursor->mx_cursor, data, NULL, MDBX_PREV); + if (op != MDBX_PREV || rc != MDBX_NOTFOUND) { + if (likely(rc == MDBX_SUCCESS)) { + get_key_optional(node, key); + mc->mc_flags &= ~C_EOF; + } + return rc; + } + } + } else { + mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + if (op == MDBX_PREV_DUP) + return MDBX_NOTFOUND; + } + } + + mdbx_debug("cursor_prev: top page is %" PRIaPGNO " in cursor %p", mp->mp_pgno, + (void *)mc); + + mc->mc_flags &= ~(C_EOF | C_DEL); + + if (mc->mc_ki[mc->mc_top] == 0) { + mdbx_debug("%s", "=====> move to prev sibling page"); + if ((rc = mdbx_cursor_sibling(mc, 0)) != MDBX_SUCCESS) { + return rc; + } + mp = mc->mc_pg[mc->mc_top]; + mc->mc_ki[mc->mc_top] = (indx_t)page_numkeys(mp) - 1; + mdbx_debug("prev page is %" PRIaPGNO ", key index %u", mp->mp_pgno, + mc->mc_ki[mc->mc_top]); + } else + mc->mc_ki[mc->mc_top]--; + + mdbx_debug("==> cursor points to page %" PRIaPGNO + " with %u keys, key index %u", + mp->mp_pgno, page_numkeys(mp), mc->mc_ki[mc->mc_top]); + + if (IS_LEAF2(mp)) { + if (likely(key)) { + key->iov_len = mc->mc_db->md_xsize; + key->iov_base = page_leaf2key(mp, mc->mc_ki[mc->mc_top], key->iov_len); + } + return MDBX_SUCCESS; + } + + mdbx_cassert(mc, IS_LEAF(mp)); + node = page_node(mp, mc->mc_ki[mc->mc_top]); + + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + if (data) { + if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + return rc; + + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + } + + get_key_optional(node, key); + return MDBX_SUCCESS; +} + +/* Set the cursor on a specific data item. */ +__hot static int mdbx_cursor_set(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, + MDBX_cursor_op op, int *exactp) { + int rc; + MDBX_page *mp; + MDBX_node *node = NULL; + DKBUF; + + if ((mc->mc_db->md_flags & MDBX_INTEGERKEY) && + unlikely(key->iov_len != sizeof(uint32_t) && + key->iov_len != sizeof(uint64_t))) { + mdbx_cassert(mc, !"key-size is invalid for MDBX_INTEGERKEY"); + return MDBX_BAD_VALSIZE; + } + + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + + /* See if we're already on the right page */ + if (mc->mc_flags & C_INITIALIZED) { + MDBX_val nodekey; + + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); + mp = mc->mc_pg[mc->mc_top]; + if (!page_numkeys(mp)) { + mc->mc_ki[mc->mc_top] = 0; + return MDBX_NOTFOUND; + } + if (IS_LEAF2(mp)) { + nodekey.iov_len = mc->mc_db->md_xsize; + nodekey.iov_base = page_leaf2key(mp, 0, nodekey.iov_len); + } else { + node = page_node(mp, 0); + get_key(node, &nodekey); + } + rc = mc->mc_dbx->md_cmp(key, &nodekey); + if (unlikely(rc == 0)) { + /* Probably happens rarely, but first node on the page + * was the one we wanted. */ + mc->mc_ki[mc->mc_top] = 0; + if (exactp) + *exactp = 1; + goto set1; + } + if (rc > 0) { + const unsigned nkeys = page_numkeys(mp); + unsigned i; + if (nkeys > 1) { + if (IS_LEAF2(mp)) { + nodekey.iov_base = page_leaf2key(mp, nkeys - 1, nodekey.iov_len); + } else { + node = page_node(mp, nkeys - 1); + get_key(node, &nodekey); + } + rc = mc->mc_dbx->md_cmp(key, &nodekey); + if (rc == 0) { + /* last node was the one we wanted */ + mdbx_cassert(mc, nkeys >= 1 && nkeys <= UINT16_MAX + 1); + mc->mc_ki[mc->mc_top] = (indx_t)(nkeys - 1); + if (exactp) + *exactp = 1; + goto set1; + } + if (rc < 0) { + if (mc->mc_ki[mc->mc_top] < page_numkeys(mp)) { + /* This is definitely the right page, skip search_page */ + if (IS_LEAF2(mp)) { + nodekey.iov_base = + page_leaf2key(mp, mc->mc_ki[mc->mc_top], nodekey.iov_len); + } else { + node = page_node(mp, mc->mc_ki[mc->mc_top]); + get_key(node, &nodekey); + } + rc = mc->mc_dbx->md_cmp(key, &nodekey); + if (rc == 0) { + /* current node was the one we wanted */ + if (exactp) + *exactp = 1; + goto set1; + } + } + rc = 0; + mc->mc_flags &= ~C_EOF; + goto set2; + } + } + /* If any parents have right-sibs, search. + * Otherwise, there's nothing further. */ + for (i = 0; i < mc->mc_top; i++) + if (mc->mc_ki[i] < page_numkeys(mc->mc_pg[i]) - 1) + break; + if (i == mc->mc_top) { + /* There are no other pages */ + mdbx_cassert(mc, nkeys <= UINT16_MAX); + mc->mc_ki[mc->mc_top] = (uint16_t)nkeys; + return MDBX_NOTFOUND; + } + } + if (!mc->mc_top) { + /* There are no other pages */ + mc->mc_ki[mc->mc_top] = 0; + if (op == MDBX_SET_RANGE && !exactp) { + rc = 0; + goto set1; + } else + return MDBX_NOTFOUND; + } + } else { + mc->mc_pg[0] = 0; + } + + rc = mdbx_page_search(mc, key, 0); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + mp = mc->mc_pg[mc->mc_top]; + mdbx_cassert(mc, IS_LEAF(mp)); + +set2: + node = mdbx_node_search(mc, key, exactp); + if (exactp != NULL && !*exactp) { + /* MDBX_SET specified and not an exact match. */ + return MDBX_NOTFOUND; + } + + if (node == NULL) { + mdbx_debug("%s", "===> inexact leaf not found, goto sibling"); + if (unlikely((rc = mdbx_cursor_sibling(mc, 1)) != MDBX_SUCCESS)) { + mc->mc_flags |= C_EOF; + return rc; /* no entries matched */ + } + mp = mc->mc_pg[mc->mc_top]; + mdbx_cassert(mc, IS_LEAF(mp)); + node = page_node(mp, 0); + } + +set1: + mc->mc_flags |= C_INITIALIZED; + mc->mc_flags &= ~C_EOF; + + if (IS_LEAF2(mp)) { + if (op == MDBX_SET_RANGE || op == MDBX_SET_KEY) { + key->iov_len = mc->mc_db->md_xsize; + key->iov_base = page_leaf2key(mp, mc->mc_ki[mc->mc_top], key->iov_len); + } + return MDBX_SUCCESS; + } + + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + if (likely(data)) { + if (F_ISSET(node_flags(node), F_DUPDATA)) { + if (op == MDBX_SET || op == MDBX_SET_KEY || op == MDBX_SET_RANGE) { + rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + } else { + int ex2, *ex2p; + if (op == MDBX_GET_BOTH) { + ex2p = &ex2; + ex2 = 0; + } else { + ex2p = NULL; + } + rc = mdbx_cursor_set(&mc->mc_xcursor->mx_cursor, data, NULL, + MDBX_SET_RANGE, ex2p); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + } else if (op == MDBX_GET_BOTH || op == MDBX_GET_BOTH_RANGE) { + MDBX_val olddata; + if (unlikely((rc = mdbx_node_read(mc, node, &olddata)) != MDBX_SUCCESS)) + return rc; + if (unlikely(mc->mc_dbx->md_dcmp == NULL)) + return MDBX_EINVAL; + rc = mc->mc_dbx->md_dcmp(data, &olddata); + if (rc) { + if (op != MDBX_GET_BOTH_RANGE || rc > 0) + return MDBX_NOTFOUND; + rc = 0; + } + *data = olddata; + } else { + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + return rc; + } + } + + /* The key already matches in all other cases */ + if (op == MDBX_SET_RANGE || op == MDBX_SET_KEY) + get_key_optional(node, key); + + mdbx_debug("==> cursor placed on key [%s], data [%s]", DKEY(key), DVAL(data)); + return rc; +} + +/* Move the cursor to the first item in the database. */ +static int mdbx_cursor_first(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { + int rc; + + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + + if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) { + rc = mdbx_page_search(mc, NULL, MDBX_PS_FIRST); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); + + mc->mc_flags |= C_INITIALIZED; + mc->mc_flags &= ~C_EOF; + mc->mc_ki[mc->mc_top] = 0; + + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + key->iov_len = mc->mc_db->md_xsize; + key->iov_base = page_leaf2key(mc->mc_pg[mc->mc_top], 0, key->iov_len); + return MDBX_SUCCESS; + } + + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], 0); + if (likely(data)) { + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + if (unlikely(rc)) + return rc; + } else { + if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + return rc; + } + } + get_key_optional(node, key); + return MDBX_SUCCESS; +} + +/* Move the cursor to the last item in the database. */ +static int mdbx_cursor_last(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data) { + int rc; + + if (mc->mc_xcursor) + mc->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + + if (likely((mc->mc_flags & (C_EOF | C_DEL)) != C_EOF)) { + if (!(mc->mc_flags & C_INITIALIZED) || mc->mc_top) { + rc = mdbx_page_search(mc, NULL, MDBX_PS_LAST); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); + } + + mc->mc_ki[mc->mc_top] = (indx_t)page_numkeys(mc->mc_pg[mc->mc_top]) - 1; + mc->mc_flags |= C_INITIALIZED | C_EOF; + + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + key->iov_len = mc->mc_db->md_xsize; + key->iov_base = page_leaf2key(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], + key->iov_len); + return MDBX_SUCCESS; + } + + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (likely(data)) { + if (F_ISSET(node_flags(node), F_DUPDATA)) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_cursor_last(&mc->mc_xcursor->mx_cursor, data, NULL); + if (unlikely(rc)) + return rc; + } else { + if (unlikely((rc = mdbx_node_read(mc, node, data)) != MDBX_SUCCESS)) + return rc; + } + } + + get_key_optional(node, key); + return MDBX_SUCCESS; +} + +int mdbx_cursor_get(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, + MDBX_cursor_op op) { + if (unlikely(mc == NULL)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = check_txn(mc->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + int exact = 0; + int (*mfunc)(MDBX_cursor * mc, MDBX_val * key, MDBX_val * data); + switch (op) { + case MDBX_GET_CURRENT: { + if (unlikely(!(mc->mc_flags & C_INITIALIZED))) + return MDBX_EINVAL; + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + const unsigned nkeys = page_numkeys(mp); + if (mc->mc_ki[mc->mc_top] >= nkeys) { + mdbx_cassert(mc, nkeys <= UINT16_MAX); + mc->mc_ki[mc->mc_top] = (uint16_t)nkeys; + return MDBX_NOTFOUND; + } + mdbx_cassert(mc, nkeys > 0); + + rc = MDBX_SUCCESS; + if (IS_LEAF2(mp)) { + key->iov_len = mc->mc_db->md_xsize; + key->iov_base = page_leaf2key(mp, mc->mc_ki[mc->mc_top], key->iov_len); + } else { + MDBX_node *node = page_node(mp, mc->mc_ki[mc->mc_top]); + get_key_optional(node, key); + if (data) { + if (F_ISSET(node_flags(node), F_DUPDATA)) { + if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_cursor_first(&mc->mc_xcursor->mx_cursor, data, NULL); + if (unlikely(rc)) + return rc; + } + rc = mdbx_cursor_get(&mc->mc_xcursor->mx_cursor, data, NULL, + MDBX_GET_CURRENT); + } else { + rc = mdbx_node_read(mc, node, data); + } + if (unlikely(rc)) + return rc; + } + } + break; + } + case MDBX_GET_BOTH: + case MDBX_GET_BOTH_RANGE: + if (unlikely(data == NULL)) + return MDBX_EINVAL; + if (unlikely(mc->mc_xcursor == NULL)) + return MDBX_INCOMPATIBLE; + /* fall through */ + __fallthrough; + case MDBX_SET: + case MDBX_SET_KEY: + case MDBX_SET_RANGE: + if (unlikely(key == NULL)) + return MDBX_EINVAL; + rc = mdbx_cursor_set(mc, key, data, op, + op == MDBX_SET_RANGE ? NULL : &exact); + break; + case MDBX_GET_MULTIPLE: + if (unlikely(data == NULL || !(mc->mc_flags & C_INITIALIZED))) + return MDBX_EINVAL; + if (unlikely(!(mc->mc_db->md_flags & MDBX_DUPFIXED))) + return MDBX_INCOMPATIBLE; + rc = MDBX_SUCCESS; + if (!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) || + (mc->mc_xcursor->mx_cursor.mc_flags & C_EOF)) + break; + goto fetchm; + case MDBX_NEXT_MULTIPLE: + if (unlikely(data == NULL)) + return MDBX_EINVAL; + if (unlikely(!(mc->mc_db->md_flags & MDBX_DUPFIXED))) + return MDBX_INCOMPATIBLE; + rc = mdbx_cursor_next(mc, key, data, MDBX_NEXT_DUP); + if (rc == MDBX_SUCCESS) { + if (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) { + MDBX_cursor *mx; + fetchm: + mx = &mc->mc_xcursor->mx_cursor; + data->iov_len = + page_numkeys(mx->mc_pg[mx->mc_top]) * mx->mc_db->md_xsize; + data->iov_base = page_data(mx->mc_pg[mx->mc_top]); + mx->mc_ki[mx->mc_top] = (indx_t)page_numkeys(mx->mc_pg[mx->mc_top]) - 1; + } else { + rc = MDBX_NOTFOUND; + } + } + break; + case MDBX_PREV_MULTIPLE: + if (data == NULL) + return MDBX_EINVAL; + if (!(mc->mc_db->md_flags & MDBX_DUPFIXED)) + return MDBX_INCOMPATIBLE; + rc = MDBX_SUCCESS; + if (!(mc->mc_flags & C_INITIALIZED)) + rc = mdbx_cursor_last(mc, key, data); + if (rc == MDBX_SUCCESS) { + MDBX_cursor *mx = &mc->mc_xcursor->mx_cursor; + if (mx->mc_flags & C_INITIALIZED) { + rc = mdbx_cursor_sibling(mx, 0); + if (rc == MDBX_SUCCESS) + goto fetchm; + } else { + rc = MDBX_NOTFOUND; + } + } + break; + case MDBX_NEXT: + case MDBX_NEXT_DUP: + case MDBX_NEXT_NODUP: + rc = mdbx_cursor_next(mc, key, data, op); + break; + case MDBX_PREV: + case MDBX_PREV_DUP: + case MDBX_PREV_NODUP: + rc = mdbx_cursor_prev(mc, key, data, op); + break; + case MDBX_FIRST: + rc = mdbx_cursor_first(mc, key, data); + break; + case MDBX_FIRST_DUP: + mfunc = mdbx_cursor_first; + mmove: + if (unlikely(data == NULL || !(mc->mc_flags & C_INITIALIZED))) + return MDBX_EINVAL; + if (unlikely(mc->mc_xcursor == NULL)) + return MDBX_INCOMPATIBLE; + if (mc->mc_ki[mc->mc_top] >= page_numkeys(mc->mc_pg[mc->mc_top])) { + mc->mc_ki[mc->mc_top] = (indx_t)page_numkeys(mc->mc_pg[mc->mc_top]); + return MDBX_NOTFOUND; + } + { + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (!F_ISSET(node_flags(node), F_DUPDATA)) { + get_key_optional(node, key); + rc = mdbx_node_read(mc, node, data); + break; + } + } + if (unlikely(!(mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED))) + return MDBX_EINVAL; + rc = mfunc(&mc->mc_xcursor->mx_cursor, data, NULL); + break; + case MDBX_LAST: + rc = mdbx_cursor_last(mc, key, data); + break; + case MDBX_LAST_DUP: + mfunc = mdbx_cursor_last; + goto mmove; + default: + mdbx_debug("unhandled/unimplemented cursor operation %u", op); + return MDBX_EINVAL; + } + + mc->mc_flags &= ~C_DEL; + return rc; +} + +/* Touch all the pages in the cursor stack. Set mc_top. + * Makes sure all the pages are writable, before attempting a write operation. + * [in] mc The cursor to operate on. */ +static int mdbx_cursor_touch(MDBX_cursor *mc) { + int rc = MDBX_SUCCESS; + + if (mc->mc_dbi >= CORE_DBS && + (*mc->mc_dbflag & (DB_DIRTY | DB_DUPDATA)) == 0) { + mdbx_cassert(mc, (mc->mc_flags & C_RECLAIMING) == 0); + /* Touch DB record of named DB */ + MDBX_cursor_couple cx; + if (TXN_DBI_CHANGED(mc->mc_txn, mc->mc_dbi)) + return MDBX_BAD_DBI; + rc = mdbx_cursor_init(&cx.outer, mc->mc_txn, MAIN_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_page_search(&cx.outer, &mc->mc_dbx->md_name, MDBX_PS_MODIFY); + if (unlikely(rc)) + return rc; + *mc->mc_dbflag |= DB_DIRTY; + } + mc->mc_top = 0; + if (mc->mc_snum) { + do { + rc = mdbx_page_touch(mc); + } while (!rc && ++(mc->mc_top) < mc->mc_snum); + mc->mc_top = mc->mc_snum - 1; + } + return rc; +} + +int mdbx_cursor_put(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, + unsigned flags) { + MDBX_env *env; + MDBX_page *fp, *sub_root = NULL; + uint16_t fp_flags; + MDBX_val xdata, *rdata, dkey, olddata; + MDBX_db nested_dupdb; + unsigned mcount = 0, dcount = 0, nospill; + size_t nsize; + int rc2; + unsigned nflags; + DKBUF; + + if (unlikely(mc == NULL || key == NULL)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = check_txn_rw(mc->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + env = mc->mc_txn->mt_env; + + /* Check this first so counter will always be zero on any early failures. */ + if (flags & MDBX_MULTIPLE) { + if (unlikely(!F_ISSET(mc->mc_db->md_flags, MDBX_DUPFIXED))) + return MDBX_INCOMPATIBLE; + if (unlikely(data[1].iov_len >= INT_MAX)) + return MDBX_EINVAL; + dcount = (unsigned)data[1].iov_len; + data[1].iov_len = 0; + } + + if (flags & MDBX_RESERVE) { + if (unlikely(mc->mc_db->md_flags & (MDBX_DUPSORT | MDBX_REVERSEDUP))) + return MDBX_INCOMPATIBLE; + data->iov_base = nullptr; + } + + nospill = flags & MDBX_NOSPILL; + flags &= ~MDBX_NOSPILL; + + if (unlikely(mc->mc_txn->mt_flags & (MDBX_RDONLY | MDBX_TXN_BLOCKED))) + return (mc->mc_txn->mt_flags & MDBX_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; + + if (unlikely(key->iov_len > env->me_maxkey_limit)) + return MDBX_BAD_VALSIZE; + + if (unlikely(data->iov_len > ((mc->mc_db->md_flags & MDBX_DUPSORT) + ? env->me_maxkey_limit + : MDBX_MAXDATASIZE))) + return MDBX_BAD_VALSIZE; + + if ((mc->mc_db->md_flags & MDBX_INTEGERKEY) && + unlikely(key->iov_len != sizeof(uint32_t) && + key->iov_len != sizeof(uint64_t))) { + mdbx_cassert(mc, !"key-size is invalid for MDBX_INTEGERKEY"); + return MDBX_BAD_VALSIZE; + } + + if ((mc->mc_db->md_flags & MDBX_INTEGERDUP) && + unlikely(data->iov_len != sizeof(uint32_t) && + data->iov_len != sizeof(uint64_t))) { + mdbx_cassert(mc, !"data-size is invalid MDBX_INTEGERDUP"); + return MDBX_BAD_VALSIZE; + } + + mdbx_debug("==> put db %d key [%s], size %" PRIuPTR + ", data [%s] size %" PRIuPTR, + DDBI(mc), DKEY(key), key ? key->iov_len : 0, + DVAL((flags & MDBX_RESERVE) ? nullptr : data), data->iov_len); + + int dupdata_flag = 0; + if ((flags & MDBX_CURRENT) != 0 && (mc->mc_flags & C_SUB) == 0) { + /* Опция MDBX_CURRENT означает, что запрошено обновление текущей записи, + * на которой сейчас стоит курсор. Проверяем что переданный ключ совпадает + * со значением в текущей позиции курсора. + * Здесь проще вызвать mdbx_cursor_get(), так как для обслуживания таблиц + * с MDBX_DUPSORT также требуется текущий размер данных. */ + MDBX_val current_key, current_data; + rc = mdbx_cursor_get(mc, ¤t_key, ¤t_data, MDBX_GET_CURRENT); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + if (mc->mc_dbx->md_cmp(key, ¤t_key) != 0) + return MDBX_EKEYMISMATCH; + + if (F_ISSET(mc->mc_db->md_flags, MDBX_DUPSORT)) { + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + mdbx_cassert(mc, + mc->mc_xcursor != NULL && + (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED)); + /* Если за ключом более одного значения, либо если размер данных + * отличается, то вместо inplace обновления требуется удаление и + * последующая вставка. */ + if (mc->mc_xcursor->mx_db.md_entries > 1 || + current_data.iov_len != data->iov_len) { + rc = mdbx_cursor_del(mc, 0); + if (rc != MDBX_SUCCESS) + return rc; + flags -= MDBX_CURRENT; + } + } else if (unlikely(node_size(key, data) > env->me_nodemax)) { + rc = mdbx_cursor_del(mc, 0); + if (rc != MDBX_SUCCESS) + return rc; + flags -= MDBX_CURRENT; + } + } + } + + if (mc->mc_db->md_root == P_INVALID) { + /* new database, cursor has nothing to point to */ + mc->mc_snum = 0; + mc->mc_top = 0; + mc->mc_flags &= ~C_INITIALIZED; + rc = MDBX_NO_ROOT; + } else if ((flags & MDBX_CURRENT) == 0) { + int exact = 0; + MDBX_val d2; + if (flags & MDBX_APPEND) { + MDBX_val k2; + rc = mdbx_cursor_last(mc, &k2, &d2); + if (rc == 0) { + rc = mc->mc_dbx->md_cmp(key, &k2); + if (rc > 0) { + rc = MDBX_NOTFOUND; + mc->mc_ki[mc->mc_top]++; + } else if (unlikely(rc < 0 || (flags & MDBX_APPENDDUP) == 0)) { + /* new key is <= last key */ + rc = MDBX_EKEYMISMATCH; + } + } + } else { + rc = mdbx_cursor_set(mc, key, &d2, MDBX_SET, &exact); + } + if ((flags & MDBX_NOOVERWRITE) && + (rc == MDBX_SUCCESS || rc == MDBX_EKEYMISMATCH)) { + mdbx_debug("duplicate key [%s]", DKEY(key)); + *data = d2; + return MDBX_KEYEXIST; + } + if (rc && unlikely(rc != MDBX_NOTFOUND)) + return rc; + } + + mc->mc_flags &= ~C_DEL; + + /* Cursor is positioned, check for room in the dirty list */ + if (!nospill) { + if (flags & MDBX_MULTIPLE) { + rdata = &xdata; + xdata.iov_len = data->iov_len * dcount; + } else { + rdata = data; + } + if (unlikely(rc2 = mdbx_page_spill(mc, key, rdata))) + return rc2; + } + + if (rc == MDBX_NO_ROOT) { + MDBX_page *np; + /* new database, write a root leaf page */ + mdbx_debug("%s", "allocating new root leaf page"); + if (unlikely(rc2 = mdbx_page_new(mc, P_LEAF, 1, &np))) { + return rc2; + } + rc2 = mdbx_cursor_push(mc, np); + if (unlikely(rc2 != MDBX_SUCCESS)) + return rc2; + mc->mc_db->md_root = np->mp_pgno; + mc->mc_db->md_depth++; + *mc->mc_dbflag |= DB_DIRTY; + if ((mc->mc_db->md_flags & (MDBX_DUPSORT | MDBX_DUPFIXED)) == MDBX_DUPFIXED) + np->mp_flags |= P_LEAF2; + mc->mc_flags |= C_INITIALIZED; + } else { + /* make sure all cursor pages are writable */ + rc2 = mdbx_cursor_touch(mc); + if (unlikely(rc2)) + return rc2; + } + + bool insert_key, insert_data, do_sub = false; + insert_key = insert_data = (rc != MDBX_SUCCESS); + if (insert_key) { + /* The key does not exist */ + mdbx_debug("inserting key at index %i", mc->mc_ki[mc->mc_top]); + if ((mc->mc_db->md_flags & MDBX_DUPSORT) && + node_size(key, data) > env->me_nodemax) { + /* Too big for a node, insert in sub-DB. Set up an empty + * "old sub-page" for prep_subDB to expand to a full page. */ + fp_flags = P_LEAF | P_DIRTY; + fp = env->me_pbuf; + fp->mp_leaf2_ksize = (uint16_t)data->iov_len; /* used if MDBX_DUPFIXED */ + fp->mp_lower = fp->mp_upper = 0; + olddata.iov_len = PAGEHDRSZ; + goto prep_subDB; + } + } else { + /* there's only a key anyway, so this is a no-op */ + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + char *ptr; + unsigned ksize = mc->mc_db->md_xsize; + if (key->iov_len != ksize) + return MDBX_BAD_VALSIZE; + ptr = page_leaf2key(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top], ksize); + memcpy(ptr, key->iov_base, ksize); + fix_parent: + /* if overwriting slot 0 of leaf, need to + * update branch key if there is a parent page */ + if (mc->mc_top && !mc->mc_ki[mc->mc_top]) { + unsigned dtop = 1; + mc->mc_top--; + /* slot 0 is always an empty key, find real slot */ + while (mc->mc_top && !mc->mc_ki[mc->mc_top]) { + mc->mc_top--; + dtop++; + } + if (mc->mc_ki[mc->mc_top]) + rc2 = mdbx_update_key(mc, key); + else + rc2 = MDBX_SUCCESS; + mdbx_cassert(mc, mc->mc_top + dtop < UINT16_MAX); + mc->mc_top += (uint16_t)dtop; + if (rc2) + return rc2; + } + + if (mdbx_audit_enabled()) { + int err = mdbx_cursor_check(mc, false); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + return MDBX_SUCCESS; + } + + more:; + if (mdbx_audit_enabled()) { + int err = mdbx_cursor_check(mc, false); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + + /* overflow page overwrites need special handling */ + if (unlikely(F_ISSET(node_flags(node), F_BIGDATA))) { + int level, ovpages, + dpages = (node_size(key, data) > env->me_nodemax) + ? number_of_ovpages(env, data->iov_len) + : 0; + + const pgno_t pg = node_largedata_pgno(node); + MDBX_page *omp; + if (unlikely((rc2 = mdbx_page_get(mc, pg, &omp, &level)) != 0)) + return rc2; + ovpages = omp->mp_pages; + + /* Is the ov page large enough? */ + if (unlikely(mc->mc_flags & C_GCFREEZE) + ? ovpages >= dpages + : ovpages == + /* LY: add configurable threshold to keep reserve space */ + dpages) { + if (!IS_DIRTY(omp) && (level || (env->me_flags & MDBX_WRITEMAP))) { + rc = mdbx_page_unspill(mc->mc_txn, omp, &omp); + if (unlikely(rc)) + return rc; + level = 0; /* dirty in this txn or clean */ + } + /* Is it dirty? */ + if (IS_DIRTY(omp)) { + /* yes, overwrite it. Note in this case we don't + * bother to try shrinking the page if the new data + * is smaller than the overflow threshold. */ + if (unlikely(level > 1)) { + /* It is writable only in a parent txn */ + MDBX_page *np = mdbx_page_malloc(mc->mc_txn, ovpages); + if (unlikely(!np)) + return MDBX_ENOMEM; + /* Note - this page is already counted in parent's dirtyroom */ + rc2 = mdbx_dpl_append(mc->mc_txn->tw.dirtylist, pg, np); + if (unlikely(rc2 != MDBX_SUCCESS)) { + rc = rc2; + mdbx_dpage_free(env, np, ovpages); + goto fail; + } + + /* Currently we make the page look as with put() in the + * parent txn, in case the user peeks at MDBX_RESERVEd + * or unused parts. Some users treat ovpages specially. */ + const size_t whole = pgno2bytes(env, ovpages); + /* Skip the part where MDBX will put *data. + * Copy end of page, adjusting alignment so + * compiler may copy words instead of bytes. */ + const size_t off = + (PAGEHDRSZ + data->iov_len) & -(intptr_t)sizeof(size_t); + memcpy((size_t *)((char *)np + off), (size_t *)((char *)omp + off), + whole - off); + memcpy(np, omp, PAGEHDRSZ); /* Copy header of page */ + omp = np; + } + node_set_ds(node, data->iov_len); + if (F_ISSET(flags, MDBX_RESERVE)) + data->iov_base = page_data(omp); + else + memcpy(page_data(omp), data->iov_base, data->iov_len); + + if (mdbx_audit_enabled()) { + int err = mdbx_cursor_check(mc, false); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + return MDBX_SUCCESS; + } + } + if ((rc2 = mdbx_page_retire(mc, omp)) != MDBX_SUCCESS) + return rc2; + } else { + olddata.iov_len = node_ds(node); + olddata.iov_base = node_data(node); + mdbx_cassert(mc, (char *)olddata.iov_base + olddata.iov_len <= + (char *)(mc->mc_pg[mc->mc_top]) + env->me_psize); + + /* DB has dups? */ + if (F_ISSET(mc->mc_db->md_flags, MDBX_DUPSORT)) { + /* Prepare (sub-)page/sub-DB to accept the new item, if needed. + * fp: old sub-page or a header faking it. + * mp: new (sub-)page. offset: growth in page size. + * xdata: node data with new page or DB. */ + unsigned i; + size_t offset = 0; + MDBX_page *mp = fp = xdata.iov_base = env->me_pbuf; + mp->mp_pgno = mc->mc_pg[mc->mc_top]->mp_pgno; + + /* Was a single item before, must convert now */ + if (!F_ISSET(node_flags(node), F_DUPDATA)) { + + /* Just overwrite the current item */ + if (flags & MDBX_CURRENT) { + mdbx_cassert(mc, node_size(key, data) <= env->me_nodemax); + goto current; + } + + /* does data match? */ + if (!mc->mc_dbx->md_dcmp(data, &olddata)) { + if (unlikely(flags & (MDBX_NODUPDATA | MDBX_APPENDDUP))) + return MDBX_KEYEXIST; + /* overwrite it */ + mdbx_cassert(mc, node_size(key, data) <= env->me_nodemax); + goto current; + } + + /* Back up original data item */ + dupdata_flag = 1; + dkey.iov_len = olddata.iov_len; + dkey.iov_base = memcpy(fp + 1, olddata.iov_base, olddata.iov_len); + + /* Make sub-page header for the dup items, with dummy body */ + fp->mp_flags = P_LEAF | P_DIRTY | P_SUBP; + fp->mp_lower = 0; + xdata.iov_len = PAGEHDRSZ + dkey.iov_len + data->iov_len; + if (mc->mc_db->md_flags & MDBX_DUPFIXED) { + fp->mp_flags |= P_LEAF2; + fp->mp_leaf2_ksize = (uint16_t)data->iov_len; + xdata.iov_len += 2 * data->iov_len; /* leave space for 2 more */ + mdbx_cassert(mc, xdata.iov_len <= env->me_psize); + } else { + xdata.iov_len += 2 * (sizeof(indx_t) + NODESIZE) + + (dkey.iov_len & 1) + (data->iov_len & 1); + mdbx_cassert(mc, xdata.iov_len <= env->me_psize); + } + fp->mp_upper = (uint16_t)(xdata.iov_len - PAGEHDRSZ); + olddata.iov_len = xdata.iov_len; /* pretend olddata is fp */ + } else if (node_flags(node) & F_SUBDATA) { + /* Data is on sub-DB, just store it */ + flags |= F_DUPDATA | F_SUBDATA; + goto put_sub; + } else { + /* Data is on sub-page */ + fp = olddata.iov_base; + switch (flags) { + default: + if (!(mc->mc_db->md_flags & MDBX_DUPFIXED)) { + offset = node_size(data, nullptr) + sizeof(indx_t); + break; + } + offset = fp->mp_leaf2_ksize; + if (page_room(fp) < offset) { + offset *= 4; /* space for 4 more */ + break; + } + /* FALLTHRU: Big enough MDBX_DUPFIXED sub-page */ + __fallthrough; + case MDBX_CURRENT | MDBX_NODUPDATA: + case MDBX_CURRENT: + fp->mp_flags |= P_DIRTY; + fp->mp_pgno = mp->mp_pgno; + mc->mc_xcursor->mx_cursor.mc_pg[0] = fp; + flags |= F_DUPDATA; + goto put_sub; + } + xdata.iov_len = olddata.iov_len + offset; + } + + fp_flags = fp->mp_flags; + if (NODESIZE + node_ks(node) + xdata.iov_len > env->me_nodemax) { + /* Too big for a sub-page, convert to sub-DB */ + fp_flags &= ~P_SUBP; + prep_subDB: + nested_dupdb.md_xsize = 0; + nested_dupdb.md_flags = 0; + if (mc->mc_db->md_flags & MDBX_DUPFIXED) { + fp_flags |= P_LEAF2; + nested_dupdb.md_xsize = fp->mp_leaf2_ksize; + nested_dupdb.md_flags = MDBX_DUPFIXED; + if (mc->mc_db->md_flags & MDBX_INTEGERDUP) + nested_dupdb.md_flags |= MDBX_INTEGERKEY; + } + nested_dupdb.md_depth = 1; + nested_dupdb.md_branch_pages = 0; + nested_dupdb.md_leaf_pages = 1; + nested_dupdb.md_overflow_pages = 0; + nested_dupdb.md_entries = page_numkeys(fp); + xdata.iov_len = sizeof(nested_dupdb); + xdata.iov_base = &nested_dupdb; + if ((rc = mdbx_page_alloc(mc, 1, &mp, MDBX_ALLOC_ALL))) + return rc; + mc->mc_db->md_leaf_pages += 1; + mdbx_cassert(mc, env->me_psize > olddata.iov_len); + offset = env->me_psize - (unsigned)olddata.iov_len; + flags |= F_DUPDATA | F_SUBDATA; + nested_dupdb.md_root = mp->mp_pgno; + nested_dupdb.md_seq = nested_dupdb.md_mod_txnid = 0; + sub_root = mp; + } + if (mp != fp) { + mp->mp_flags = fp_flags | P_DIRTY; + mp->mp_leaf2_ksize = fp->mp_leaf2_ksize; + mp->mp_lower = fp->mp_lower; + mdbx_cassert(mc, fp->mp_upper + offset <= UINT16_MAX); + mp->mp_upper = (indx_t)(fp->mp_upper + offset); + if (unlikely(fp_flags & P_LEAF2)) { + memcpy(page_data(mp), page_data(fp), + page_numkeys(fp) * fp->mp_leaf2_ksize); + } else { + memcpy((char *)mp + mp->mp_upper + PAGEHDRSZ, + (char *)fp + fp->mp_upper + PAGEHDRSZ, + olddata.iov_len - fp->mp_upper - PAGEHDRSZ); + memcpy((char *)(&mp->mp_ptrs), (char *)(&fp->mp_ptrs), + page_numkeys(fp) * sizeof(mp->mp_ptrs[0])); + for (i = 0; i < page_numkeys(fp); i++) { + mdbx_cassert(mc, mp->mp_ptrs[i] + offset <= UINT16_MAX); + mp->mp_ptrs[i] += (indx_t)offset; + } + } + } + + rdata = &xdata; + flags |= F_DUPDATA; + do_sub = true; + if (!insert_key) + mdbx_node_del(mc, 0); + goto new_sub; + } + + /* MDBX passes F_SUBDATA in 'flags' to write a DB record */ + if (unlikely((node_flags(node) ^ flags) & F_SUBDATA)) + return MDBX_INCOMPATIBLE; + + current: + if (data->iov_len == olddata.iov_len) { + mdbx_cassert(mc, EVEN(key->iov_len) == EVEN(node_ks(node))); + /* same size, just replace it. Note that we could + * also reuse this node if the new data is smaller, + * but instead we opt to shrink the node in that case. */ + if (F_ISSET(flags, MDBX_RESERVE)) + data->iov_base = olddata.iov_base; + else if (!(mc->mc_flags & C_SUB)) + memcpy(olddata.iov_base, data->iov_base, data->iov_len); + else { + mdbx_cassert(mc, page_numkeys(mc->mc_pg[mc->mc_top]) == 1); + mdbx_cassert(mc, PAGETYPE(mc->mc_pg[mc->mc_top]) == P_LEAF); + mdbx_cassert(mc, node_ds(node) == 0); + mdbx_cassert(mc, node_flags(node) == 0); + mdbx_cassert(mc, key->iov_len < UINT16_MAX); + node_set_ks(node, key->iov_len); + memcpy(node_key(node), key->iov_base, key->iov_len); + mdbx_cassert(mc, (char *)node_key(node) + node_ds(node) < + (char *)(mc->mc_pg[mc->mc_top]) + env->me_psize); + goto fix_parent; + } + + if (mdbx_audit_enabled()) { + int err = mdbx_cursor_check(mc, false); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + return MDBX_SUCCESS; + } + } + mdbx_node_del(mc, 0); + } + + rdata = data; + +new_sub: + nflags = flags & NODE_ADD_FLAGS; + nsize = IS_LEAF2(mc->mc_pg[mc->mc_top]) ? key->iov_len + : leaf_size(env, key, rdata); + if (page_room(mc->mc_pg[mc->mc_top]) < nsize) { + if ((flags & (F_DUPDATA | F_SUBDATA)) == F_DUPDATA) + nflags &= ~MDBX_APPEND; /* sub-page may need room to grow */ + if (!insert_key) + nflags |= MDBX_SPLIT_REPLACE; + rc = mdbx_page_split(mc, key, rdata, P_INVALID, nflags); + if (rc == MDBX_SUCCESS && mdbx_audit_enabled()) + rc = mdbx_cursor_check(mc, false); + } else { + /* There is room already in this leaf page. */ + if (IS_LEAF2(mc->mc_pg[mc->mc_top])) { + mdbx_cassert(mc, (nflags & (F_BIGDATA | F_SUBDATA | F_DUPDATA)) == 0 && + rdata->iov_len == 0); + rc = mdbx_node_add_leaf2(mc, mc->mc_ki[mc->mc_top], key); + } else + rc = mdbx_node_add_leaf(mc, mc->mc_ki[mc->mc_top], key, rdata, nflags); + if (likely(rc == 0)) { + /* Adjust other cursors pointing to mp */ + MDBX_cursor *m2, *m3; + MDBX_dbi dbi = mc->mc_dbi; + unsigned i = mc->mc_top; + MDBX_page *mp = mc->mc_pg[i]; + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == mc || m3->mc_snum < mc->mc_snum || m3->mc_pg[i] != mp) + continue; + if (m3->mc_ki[i] >= mc->mc_ki[i] && insert_key) { + m3->mc_ki[i]++; + } + if (XCURSOR_INITED(m3)) + XCURSOR_REFRESH(m3, mp, m3->mc_ki[i]); + } + } + } + + if (likely(rc == MDBX_SUCCESS)) { + /* Now store the actual data in the child DB. Note that we're + * storing the user data in the keys field, so there are strict + * size limits on dupdata. The actual data fields of the child + * DB are all zero size. */ + if (do_sub) { + int xflags; + size_t ecount; + put_sub: + xdata.iov_len = 0; + xdata.iov_base = nullptr; + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (flags & MDBX_CURRENT) { + xflags = (flags & MDBX_NODUPDATA) + ? MDBX_CURRENT | MDBX_NOOVERWRITE | MDBX_NOSPILL + : MDBX_CURRENT | MDBX_NOSPILL; + } else { + rc2 = mdbx_xcursor_init1(mc, node); + if (unlikely(rc2 != MDBX_SUCCESS)) + return rc2; + xflags = (flags & MDBX_NODUPDATA) ? MDBX_NOOVERWRITE | MDBX_NOSPILL + : MDBX_NOSPILL; + } + if (sub_root) + mc->mc_xcursor->mx_cursor.mc_pg[0] = sub_root; + /* converted, write the original data first */ + if (dupdata_flag) { + rc = mdbx_cursor_put(&mc->mc_xcursor->mx_cursor, &dkey, &xdata, xflags); + if (unlikely(rc)) + goto bad_sub; + /* we've done our job */ + dkey.iov_len = 0; + } + if (!(node_flags(node) & F_SUBDATA) || sub_root) { + /* Adjust other cursors pointing to mp */ + MDBX_cursor *m2; + MDBX_xcursor *mx = mc->mc_xcursor; + unsigned i = mc->mc_top; + MDBX_page *mp = mc->mc_pg[i]; + const int nkeys = page_numkeys(mp); + + for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2 = m2->mc_next) { + if (m2 == mc || m2->mc_snum < mc->mc_snum) + continue; + if (!(m2->mc_flags & C_INITIALIZED)) + continue; + if (m2->mc_pg[i] == mp) { + if (m2->mc_ki[i] == mc->mc_ki[i]) { + rc2 = mdbx_xcursor_init2(m2, mx, dupdata_flag); + if (unlikely(rc2 != MDBX_SUCCESS)) + return rc2; + } else if (!insert_key && m2->mc_ki[i] < nkeys) { + XCURSOR_REFRESH(m2, mp, m2->mc_ki[i]); + } + } + } + } + mdbx_cassert(mc, mc->mc_xcursor->mx_db.md_entries < PTRDIFF_MAX); + ecount = (size_t)mc->mc_xcursor->mx_db.md_entries; + if (flags & MDBX_APPENDDUP) + xflags |= MDBX_APPEND; + rc = mdbx_cursor_put(&mc->mc_xcursor->mx_cursor, data, &xdata, xflags); + if (flags & F_SUBDATA) { + void *db = node_data(node); + memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDBX_db)); + } + insert_data = (ecount != (size_t)mc->mc_xcursor->mx_db.md_entries); + } + /* Increment count unless we just replaced an existing item. */ + if (insert_data) + mc->mc_db->md_entries++; + if (insert_key) { + /* Invalidate txn if we created an empty sub-DB */ + if (unlikely(rc)) + goto bad_sub; + /* If we succeeded and the key didn't exist before, + * make sure the cursor is marked valid. */ + mc->mc_flags |= C_INITIALIZED; + } + if (flags & MDBX_MULTIPLE) { + if (!rc) { + mcount++; + /* let caller know how many succeeded, if any */ + data[1].iov_len = mcount; + if (mcount < dcount) { + data[0].iov_base = (char *)data[0].iov_base + data[0].iov_len; + insert_key = insert_data = false; + goto more; + } + } + } + if (rc == MDBX_SUCCESS && mdbx_audit_enabled()) + rc = mdbx_cursor_check(mc, false); + return rc; + bad_sub: + if (unlikely(rc == MDBX_KEYEXIST)) + mdbx_error("unexpected %s", "MDBX_KEYEXIST"); + /* should not happen, we deleted that item */ + rc = MDBX_PROBLEM; + } +fail: + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return rc; +} + +int mdbx_cursor_del(MDBX_cursor *mc, unsigned flags) { + if (unlikely(!mc)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = check_txn_rw(mc->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!(mc->mc_flags & C_INITIALIZED))) + return MDBX_EINVAL; + + if (unlikely(mc->mc_ki[mc->mc_top] >= page_numkeys(mc->mc_pg[mc->mc_top]))) + return MDBX_NOTFOUND; + + if (unlikely(!(flags & MDBX_NOSPILL) && + (rc = mdbx_page_spill(mc, NULL, NULL)))) + return rc; + + rc = mdbx_cursor_touch(mc); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + if (IS_LEAF2(mp)) + goto del_key; + + MDBX_node *node = page_node(mp, mc->mc_ki[mc->mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + if (flags & MDBX_NODUPDATA) { + /* mdbx_cursor_del0() will subtract the final entry */ + mc->mc_db->md_entries -= mc->mc_xcursor->mx_db.md_entries - 1; + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + } else { + if (!F_ISSET(node_flags(node), F_SUBDATA)) { + mc->mc_xcursor->mx_cursor.mc_pg[0] = node_data(node); + } + rc = mdbx_cursor_del(&mc->mc_xcursor->mx_cursor, MDBX_NOSPILL); + if (unlikely(rc)) + return rc; + /* If sub-DB still has entries, we're done */ + if (mc->mc_xcursor->mx_db.md_entries) { + if (node_flags(node) & F_SUBDATA) { + /* update subDB info */ + void *db = node_data(node); + memcpy(db, &mc->mc_xcursor->mx_db, sizeof(MDBX_db)); + } else { + MDBX_cursor *m2; + /* shrink fake page */ + mdbx_node_shrink(mp, mc->mc_ki[mc->mc_top]); + node = page_node(mp, mc->mc_ki[mc->mc_top]); + mc->mc_xcursor->mx_cursor.mc_pg[0] = node_data(node); + /* fix other sub-DB cursors pointed at fake pages on this page */ + for (m2 = mc->mc_txn->mt_cursors[mc->mc_dbi]; m2; m2 = m2->mc_next) { + if (m2 == mc || m2->mc_snum < mc->mc_snum) + continue; + if (!(m2->mc_flags & C_INITIALIZED)) + continue; + if (m2->mc_pg[mc->mc_top] == mp) { + MDBX_node *inner = node; + if (m2->mc_ki[mc->mc_top] >= page_numkeys(mp)) + continue; + if (m2->mc_ki[mc->mc_top] != mc->mc_ki[mc->mc_top]) { + inner = page_node(mp, m2->mc_ki[mc->mc_top]); + if (node_flags(inner) & F_SUBDATA) + continue; + } + m2->mc_xcursor->mx_cursor.mc_pg[0] = node_data(inner); + } + } + } + mc->mc_db->md_entries--; + mdbx_cassert(mc, mc->mc_db->md_entries > 0 && mc->mc_db->md_depth > 0 && + mc->mc_db->md_root != P_INVALID); + return rc; + } else { + mc->mc_xcursor->mx_cursor.mc_flags &= ~C_INITIALIZED; + } + /* otherwise fall thru and delete the sub-DB */ + } + + if (node_flags(node) & F_SUBDATA) { + /* add all the child DB's pages to the free list */ + rc = mdbx_drop0(&mc->mc_xcursor->mx_cursor, 0); + if (unlikely(rc)) + goto fail; + } + } + /* MDBX passes F_SUBDATA in 'flags' to delete a DB record */ + else if (unlikely((node_flags(node) ^ flags) & F_SUBDATA)) { + rc = MDBX_INCOMPATIBLE; + goto fail; + } + + /* add overflow pages to free list */ + if (F_ISSET(node_flags(node), F_BIGDATA)) { + MDBX_page *omp; + if (unlikely( + (rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL)) || + (rc = mdbx_page_retire(mc, omp)))) + goto fail; + } + +del_key: + return mdbx_cursor_del0(mc); + +fail: + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return rc; +} + +/* Allocate and initialize new pages for a database. + * Set MDBX_TXN_ERROR on failure. + * + * [in] mc a cursor on the database being added to. + * [in] flags flags defining what type of page is being allocated. + * [in] num the number of pages to allocate. This is usually 1, + * unless allocating overflow pages for a large record. + * [out] mp Address of a page, or NULL on failure. + * + * Returns 0 on success, non-zero on failure. */ +static int mdbx_page_new(MDBX_cursor *mc, unsigned flags, unsigned num, + MDBX_page **mp) { + MDBX_page *np; + int rc; + + if (unlikely((rc = mdbx_page_alloc(mc, num, &np, MDBX_ALLOC_ALL)))) + return rc; + *mp = np; + mdbx_debug("allocated new page #%" PRIaPGNO ", size %u", np->mp_pgno, + mc->mc_txn->mt_env->me_psize); + np->mp_flags = (uint16_t)(flags | P_DIRTY); + np->mp_lower = 0; + np->mp_upper = (indx_t)(mc->mc_txn->mt_env->me_psize - PAGEHDRSZ); + + mc->mc_db->md_branch_pages += IS_BRANCH(np); + mc->mc_db->md_leaf_pages += IS_LEAF(np); + if (unlikely(IS_OVERFLOW(np))) { + mc->mc_db->md_overflow_pages += num; + np->mp_pages = num; + mdbx_cassert(mc, !(mc->mc_flags & C_SUB)); + } else if (unlikely(mc->mc_flags & C_SUB)) { + MDBX_db *outer = mdbx_outer_db(mc); + outer->md_branch_pages += IS_BRANCH(np); + outer->md_leaf_pages += IS_LEAF(np); + } + + return MDBX_SUCCESS; +} + +static int __must_check_result mdbx_node_add_leaf2(MDBX_cursor *mc, + unsigned indx, + const MDBX_val *key) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + DKBUF; + mdbx_debug("add to leaf2-%spage %" PRIaPGNO " index %i, " + " key size %" PRIuPTR " [%s]", + IS_SUBP(mp) ? "sub-" : "", mp->mp_pgno, indx, + key ? key->iov_len : 0, DKEY(key)); + + mdbx_cassert(mc, key); + mdbx_cassert(mc, PAGETYPE(mp) == (P_LEAF | P_LEAF2)); + const unsigned ksize = mc->mc_db->md_xsize; + mdbx_cassert(mc, ksize == key->iov_len); + const unsigned nkeys = page_numkeys(mp); + + /* Just using these for counting */ + const intptr_t lower = mp->mp_lower + sizeof(indx_t); + const intptr_t upper = mp->mp_upper - (ksize - sizeof(indx_t)); + if (unlikely(lower > upper)) { + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PAGE_FULL; + } + mp->mp_lower = (indx_t)lower; + mp->mp_upper = (indx_t)upper; + + char *const ptr = page_leaf2key(mp, indx, ksize); + mdbx_cassert(mc, nkeys >= indx); + const unsigned diff = nkeys - indx; + if (likely(diff > 0)) + /* Move higher keys up one slot. */ + memmove(ptr + ksize, ptr, diff * ksize); + /* insert new key */ + memcpy(ptr, key->iov_base, ksize); + return MDBX_SUCCESS; +} + +static int __must_check_result mdbx_node_add_branch(MDBX_cursor *mc, + unsigned indx, + const MDBX_val *key, + pgno_t pgno) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + DKBUF; + mdbx_debug("add to branch-%spage %" PRIaPGNO " index %i, node-pgno %" PRIaPGNO + " key size %" PRIuPTR " [%s]", + IS_SUBP(mp) ? "sub-" : "", mp->mp_pgno, indx, pgno, + key ? key->iov_len : 0, DKEY(key)); + + mdbx_cassert(mc, PAGETYPE(mp) == P_BRANCH); + STATIC_ASSERT(NODESIZE % 2 == 0); + + /* Move higher pointers up one slot. */ + const unsigned nkeys = page_numkeys(mp); + mdbx_cassert(mc, nkeys >= indx); + for (unsigned i = nkeys; i > indx; --i) + mp->mp_ptrs[i] = mp->mp_ptrs[i - 1]; + + /* Adjust free space offsets. */ + const size_t branch_bytes = branch_size(mc->mc_txn->mt_env, key); + const intptr_t lower = mp->mp_lower + sizeof(indx_t); + const intptr_t upper = mp->mp_upper - (branch_bytes - sizeof(indx_t)); + if (unlikely(lower > upper)) { + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PAGE_FULL; + } + mp->mp_lower = (indx_t)lower; + mp->mp_ptrs[indx] = mp->mp_upper = (indx_t)upper; + + /* Write the node data. */ + MDBX_node *node = page_node(mp, indx); + node_set_pgno(node, pgno); + node_set_flags(node, 0); + UNALIGNED_POKE_8(node, MDBX_node, mn_extra, 0); + node_set_ks(node, 0); + if (likely(key != NULL)) { + node_set_ks(node, key->iov_len); + memcpy(node_key(node), key->iov_base, key->iov_len); + } + return MDBX_SUCCESS; +} + +static int __must_check_result mdbx_node_add_leaf(MDBX_cursor *mc, + unsigned indx, + const MDBX_val *key, + MDBX_val *data, + unsigned flags) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + DKBUF; + mdbx_debug("add to leaf-%spage %" PRIaPGNO " index %i, data size %" PRIuPTR + " key size %" PRIuPTR " [%s]", + IS_SUBP(mp) ? "sub-" : "", mp->mp_pgno, indx, + data ? data->iov_len : 0, key ? key->iov_len : 0, DKEY(key)); + mdbx_cassert(mc, key != NULL && data != NULL); + mdbx_cassert(mc, PAGETYPE(mp) == P_LEAF); + MDBX_page *largepage = NULL; + + size_t leaf_bytes = 0; + if (unlikely(flags & F_BIGDATA)) { + /* Data already on overflow page. */ + STATIC_ASSERT(sizeof(pgno_t) % 2 == 0); + leaf_bytes = node_size(key, nullptr) + sizeof(pgno_t) + sizeof(indx_t); + } else if (unlikely(node_size(key, data) > mc->mc_txn->mt_env->me_nodemax)) { + /* Put data on overflow page. */ + mdbx_cassert(mc, !F_ISSET(mc->mc_db->md_flags, MDBX_DUPSORT)); + const pgno_t ovpages = number_of_ovpages(mc->mc_txn->mt_env, data->iov_len); + int rc = mdbx_page_new(mc, P_OVERFLOW, ovpages, &largepage); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + mdbx_debug("allocated %u overflow page(s) %" PRIaPGNO "for %" PRIuPTR + " data bytes", + largepage->mp_pages, largepage->mp_pgno, data->iov_len); + flags |= F_BIGDATA; + leaf_bytes = node_size(key, nullptr) + sizeof(pgno_t) + sizeof(indx_t); + } else { + leaf_bytes = node_size(key, data) + sizeof(indx_t); + } + mdbx_cassert(mc, leaf_bytes == leaf_size(mc->mc_txn->mt_env, key, data)); + + /* Move higher pointers up one slot. */ + const unsigned nkeys = page_numkeys(mp); + mdbx_cassert(mc, nkeys >= indx); + for (unsigned i = nkeys; i > indx; --i) + mp->mp_ptrs[i] = mp->mp_ptrs[i - 1]; + + /* Adjust free space offsets. */ + const intptr_t lower = mp->mp_lower + sizeof(indx_t); + const intptr_t upper = mp->mp_upper - (leaf_bytes - sizeof(indx_t)); + if (unlikely(lower > upper)) { + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PAGE_FULL; + } + mp->mp_lower = (indx_t)lower; + mp->mp_ptrs[indx] = mp->mp_upper = (indx_t)upper; + + /* Write the node data. */ + MDBX_node *node = page_node(mp, indx); + node_set_ks(node, key->iov_len); + node_set_flags(node, (uint8_t)flags); + UNALIGNED_POKE_8(node, MDBX_node, mn_extra, 0); + node_set_ds(node, data->iov_len); + memcpy(node_key(node), key->iov_base, key->iov_len); + + void *nodedata = node_data(node); + if (likely(largepage == NULL)) { + if (unlikely(flags & F_BIGDATA)) + memcpy(nodedata, data->iov_base, sizeof(pgno_t)); + else if (unlikely(flags & MDBX_RESERVE)) + data->iov_base = nodedata; + else if (likely(nodedata != data->iov_base)) + memcpy(nodedata, data->iov_base, data->iov_len); + } else { + poke_pgno(nodedata, largepage->mp_pgno); + nodedata = page_data(largepage); + if (unlikely(flags & MDBX_RESERVE)) + data->iov_base = nodedata; + else if (likely(nodedata != data->iov_base)) + memcpy(nodedata, data->iov_base, data->iov_len); + } + return MDBX_SUCCESS; +} + +/* Delete the specified node from a page. + * [in] mc Cursor pointing to the node to delete. + * [in] ksize The size of a node. Only used if the page is + * part of a MDBX_DUPFIXED database. */ +static void mdbx_node_del(MDBX_cursor *mc, size_t ksize) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + int indx = mc->mc_ki[mc->mc_top]; + int i, j, nkeys, ptr; + MDBX_node *node; + char *base; + + mdbx_debug("delete node %u on %s page %" PRIaPGNO, indx, + IS_LEAF(mp) ? "leaf" : "branch", mp->mp_pgno); + nkeys = page_numkeys(mp); + mdbx_cassert(mc, indx < nkeys); + + if (IS_LEAF2(mp)) { + mdbx_cassert(mc, ksize >= sizeof(indx_t)); + unsigned diff = nkeys - 1 - indx; + base = page_leaf2key(mp, indx, ksize); + if (diff) + memmove(base, base + ksize, diff * ksize); + mdbx_cassert(mc, mp->mp_lower >= sizeof(indx_t)); + mp->mp_lower -= sizeof(indx_t); + mdbx_cassert(mc, + (size_t)UINT16_MAX - mp->mp_upper >= ksize - sizeof(indx_t)); + mp->mp_upper += (indx_t)(ksize - sizeof(indx_t)); + return; + } + + node = page_node(mp, indx); + size_t sz = NODESIZE + node_ks(node); + if (IS_LEAF(mp)) { + if (F_ISSET(node_flags(node), F_BIGDATA)) + sz += sizeof(pgno_t); + else + sz += node_ds(node); + } + sz = EVEN(sz); + + ptr = mp->mp_ptrs[indx]; + for (i = j = 0; i < nkeys; i++) { + if (i != indx) { + mp->mp_ptrs[j] = mp->mp_ptrs[i]; + if (mp->mp_ptrs[i] < ptr) { + mdbx_cassert(mc, (size_t)UINT16_MAX - mp->mp_ptrs[j] >= sz); + mp->mp_ptrs[j] += (indx_t)sz; + } + j++; + } + } + + base = (char *)mp + mp->mp_upper + PAGEHDRSZ; + memmove(base + sz, base, ptr - mp->mp_upper); + + mdbx_cassert(mc, mp->mp_lower >= sizeof(indx_t)); + mp->mp_lower -= sizeof(indx_t); + mdbx_cassert(mc, (size_t)UINT16_MAX - mp->mp_upper >= sz); + mp->mp_upper += (indx_t)sz; +} + +/* Compact the main page after deleting a node on a subpage. + * [in] mp The main page to operate on. + * [in] indx The index of the subpage on the main page. */ +static void mdbx_node_shrink(MDBX_page *mp, unsigned indx) { + MDBX_node *node; + MDBX_page *sp, *xp; + char *base; + size_t nsize, delta, len, ptr; + int i; + + node = page_node(mp, indx); + sp = (MDBX_page *)node_data(node); + delta = page_room(sp); + assert(delta > 0); + + /* Prepare to shift upward, set len = length(subpage part to shift) */ + if (IS_LEAF2(sp)) { + delta &= /* do not make the node uneven-sized */ ~1u; + if (unlikely(delta) == 0) + return; + nsize = node_ds(node) - delta; + assert(nsize % 1 == 0); + len = nsize; + } else { + xp = (MDBX_page *)((char *)sp + delta); /* destination subpage */ + for (i = page_numkeys(sp); --i >= 0;) { + assert(sp->mp_ptrs[i] >= delta); + xp->mp_ptrs[i] = (indx_t)(sp->mp_ptrs[i] - delta); + } + nsize = node_ds(node) - delta; + len = PAGEHDRSZ; + } + sp->mp_upper = sp->mp_lower; + sp->mp_pgno = mp->mp_pgno; + node_set_ds(node, nsize); + + /* Shift upward */ + base = (char *)mp + mp->mp_upper + PAGEHDRSZ; + memmove(base + delta, base, (char *)sp + len - base); + + ptr = mp->mp_ptrs[indx]; + for (i = page_numkeys(mp); --i >= 0;) { + if (mp->mp_ptrs[i] <= ptr) { + assert((size_t)UINT16_MAX - mp->mp_ptrs[i] >= delta); + mp->mp_ptrs[i] += (indx_t)delta; + } + } + assert((size_t)UINT16_MAX - mp->mp_upper >= delta); + mp->mp_upper += (indx_t)delta; +} + +/* Initial setup of a sorted-dups cursor. + * + * Sorted duplicates are implemented as a sub-database for the given key. + * The duplicate data items are actually keys of the sub-database. + * Operations on the duplicate data items are performed using a sub-cursor + * initialized when the sub-database is first accessed. This function does + * the preliminary setup of the sub-cursor, filling in the fields that + * depend only on the parent DB. + * + * [in] mc The main cursor whose sorted-dups cursor is to be initialized. */ +static int mdbx_xcursor_init0(MDBX_cursor *mc) { + MDBX_xcursor *mx = mc->mc_xcursor; + if (unlikely(mx == nullptr)) + return MDBX_CORRUPTED; + + mx->mx_cursor.mc_xcursor = NULL; + mx->mx_cursor.mc_txn = mc->mc_txn; + mx->mx_cursor.mc_db = &mx->mx_db; + mx->mx_cursor.mc_dbx = &mx->mx_dbx; + mx->mx_cursor.mc_dbi = mc->mc_dbi; + mx->mx_cursor.mc_dbflag = &mx->mx_dbflag; + mx->mx_cursor.mc_snum = 0; + mx->mx_cursor.mc_top = 0; + mx->mx_cursor.mc_flags = C_SUB; + mx->mx_dbx.md_name.iov_len = 0; + mx->mx_dbx.md_name.iov_base = NULL; + mx->mx_dbx.md_cmp = mc->mc_dbx->md_dcmp; + mx->mx_dbx.md_dcmp = NULL; + return MDBX_SUCCESS; +} + +/* Final setup of a sorted-dups cursor. + * Sets up the fields that depend on the data from the main cursor. + * [in] mc The main cursor whose sorted-dups cursor is to be initialized. + * [in] node The data containing the MDBX_db record for the sorted-dup database. + */ +static int mdbx_xcursor_init1(MDBX_cursor *mc, MDBX_node *node) { + MDBX_xcursor *mx = mc->mc_xcursor; + if (unlikely(mx == nullptr)) + return MDBX_CORRUPTED; + + if (node_flags(node) & F_SUBDATA) { + if (unlikely(node_ds(node) != sizeof(MDBX_db))) + return MDBX_CORRUPTED; + memcpy(&mx->mx_db, node_data(node), sizeof(MDBX_db)); + mx->mx_cursor.mc_pg[0] = 0; + mx->mx_cursor.mc_snum = 0; + mx->mx_cursor.mc_top = 0; + mx->mx_cursor.mc_flags = C_SUB; + } else { + if (unlikely(node_ds(node) <= PAGEHDRSZ)) + return MDBX_CORRUPTED; + MDBX_page *fp = node_data(node); + mx->mx_db.md_xsize = 0; + mx->mx_db.md_flags = 0; + mx->mx_db.md_depth = 1; + mx->mx_db.md_branch_pages = 0; + mx->mx_db.md_leaf_pages = 1; + mx->mx_db.md_overflow_pages = 0; + mx->mx_db.md_entries = page_numkeys(fp); + mx->mx_db.md_root = fp->mp_pgno; + mx->mx_cursor.mc_snum = 1; + mx->mx_cursor.mc_top = 0; + mx->mx_cursor.mc_flags = C_INITIALIZED | C_SUB; + mx->mx_cursor.mc_pg[0] = fp; + mx->mx_cursor.mc_ki[0] = 0; + if (mc->mc_db->md_flags & MDBX_DUPFIXED) { + mx->mx_db.md_flags = MDBX_DUPFIXED; + mx->mx_db.md_xsize = fp->mp_leaf2_ksize; + if (mc->mc_db->md_flags & MDBX_INTEGERDUP) + mx->mx_db.md_flags |= MDBX_INTEGERKEY; + } + } + mdbx_debug("Sub-db -%u root page %" PRIaPGNO, mx->mx_cursor.mc_dbi, + mx->mx_db.md_root); + mx->mx_dbflag = DB_VALID | DB_USRVALID | DB_DUPDATA; + return MDBX_SUCCESS; +} + +/* Fixup a sorted-dups cursor due to underlying update. + * Sets up some fields that depend on the data from the main cursor. + * Almost the same as init1, but skips initialization steps if the + * xcursor had already been used. + * [in] mc The main cursor whose sorted-dups cursor is to be fixed up. + * [in] src_mx The xcursor of an up-to-date cursor. + * [in] new_dupdata True if converting from a non-F_DUPDATA item. */ +static int mdbx_xcursor_init2(MDBX_cursor *mc, MDBX_xcursor *src_mx, + int new_dupdata) { + MDBX_xcursor *mx = mc->mc_xcursor; + if (unlikely(mx == nullptr)) + return MDBX_CORRUPTED; + + if (new_dupdata) { + mx->mx_cursor.mc_snum = 1; + mx->mx_cursor.mc_top = 0; + mx->mx_cursor.mc_flags |= C_INITIALIZED; + mx->mx_cursor.mc_ki[0] = 0; + mx->mx_dbflag = DB_VALID | DB_USRVALID | DB_DUPDATA; + mx->mx_dbx.md_cmp = src_mx->mx_dbx.md_cmp; + } else if (!(mx->mx_cursor.mc_flags & C_INITIALIZED)) { + return MDBX_SUCCESS; + } + mx->mx_db = src_mx->mx_db; + mx->mx_cursor.mc_pg[0] = src_mx->mx_cursor.mc_pg[0]; + mdbx_debug("Sub-db -%u root page %" PRIaPGNO, mx->mx_cursor.mc_dbi, + mx->mx_db.md_root); + return MDBX_SUCCESS; +} + +/* Initialize a cursor for a given transaction and database. */ +static int mdbx_cursor_init(MDBX_cursor *mc, MDBX_txn *txn, MDBX_dbi dbi) { + mc->mc_signature = MDBX_MC_SIGNATURE; + mc->mc_next = NULL; + mc->mc_backup = NULL; + mc->mc_dbi = dbi; + mc->mc_txn = txn; + mc->mc_db = &txn->mt_dbs[dbi]; + mc->mc_dbx = &txn->mt_dbxs[dbi]; + mc->mc_dbflag = &txn->mt_dbflags[dbi]; + mc->mc_snum = 0; + mc->mc_top = 0; + mc->mc_pg[0] = 0; + mc->mc_flags = 0; + mc->mc_ki[0] = 0; + mc->mc_xcursor = NULL; + + if (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) { + STATIC_ASSERT(offsetof(MDBX_cursor_couple, outer) == 0); + MDBX_xcursor *mx = &container_of(mc, MDBX_cursor_couple, outer)->inner; + mdbx_tassert(txn, mx != NULL); + mx->mx_cursor.mc_signature = MDBX_MC_SIGNATURE; + mc->mc_xcursor = mx; + int rc = mdbx_xcursor_init0(mc); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + int rc = MDBX_SUCCESS; + if (unlikely(*mc->mc_dbflag & DB_STALE)) { + rc = mdbx_page_search(mc, NULL, MDBX_PS_ROOTONLY); + rc = (rc != MDBX_NOTFOUND) ? rc : MDBX_SUCCESS; + } + return rc; +} + +int mdbx_cursor_open(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cursor **ret) { + if (unlikely(!ret)) + return MDBX_EINVAL; + *ret = NULL; + + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_VALID))) + return MDBX_EINVAL; + + if (unlikely(dbi == FREE_DBI && !F_ISSET(txn->mt_flags, MDBX_RDONLY))) + return MDBX_EACCESS; + + const size_t size = (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) + ? sizeof(MDBX_cursor_couple) + : sizeof(MDBX_cursor); + + MDBX_cursor *mc; + if (likely((mc = mdbx_malloc(size)) != NULL)) { + rc = mdbx_cursor_init(mc, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_free(mc); + return rc; + } + if (txn->mt_cursors) { + mc->mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = mc; + mc->mc_flags |= C_UNTRACK; + } + } else { + return MDBX_ENOMEM; + } + + *ret = mc; + return MDBX_SUCCESS; +} + +int mdbx_cursor_renew(MDBX_txn *txn, MDBX_cursor *mc) { + if (unlikely(!mc)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE && + mc->mc_signature != MDBX_MC_READY4CLOSE)) + return MDBX_EINVAL; + + int rc = check_txn(mc->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!TXN_DBI_EXIST(txn, mc->mc_dbi, DB_VALID))) + return MDBX_EINVAL; + + if (unlikely(mc->mc_backup)) + return MDBX_EINVAL; + + if (unlikely((mc->mc_flags & C_UNTRACK) || txn->mt_cursors)) { + MDBX_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; + while (*prev && *prev != mc) + prev = &(*prev)->mc_next; + if (*prev == mc) + *prev = mc->mc_next; + mc->mc_signature = MDBX_MC_READY4CLOSE; + } + + if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED)) + return MDBX_BAD_TXN; + + return mdbx_cursor_init(mc, txn, mc->mc_dbi); +} + +/* Return the count of duplicate data items for the current key */ +int mdbx_cursor_count(MDBX_cursor *mc, size_t *countp) { + if (unlikely(mc == NULL)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = check_txn(mc->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(countp == NULL || !(mc->mc_flags & C_INITIALIZED))) + return MDBX_EINVAL; + + if (!mc->mc_snum) { + *countp = 0; + return MDBX_NOTFOUND; + } + + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + if ((mc->mc_flags & C_EOF) && mc->mc_ki[mc->mc_top] >= page_numkeys(mp)) { + *countp = 0; + return MDBX_NOTFOUND; + } + + *countp = 1; + if (mc->mc_xcursor != NULL) { + MDBX_node *node = page_node(mp, mc->mc_ki[mc->mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + mdbx_cassert(mc, mc->mc_xcursor && (mc->mc_xcursor->mx_cursor.mc_flags & + C_INITIALIZED)); + *countp = unlikely(mc->mc_xcursor->mx_db.md_entries > PTRDIFF_MAX) + ? PTRDIFF_MAX + : (size_t)mc->mc_xcursor->mx_db.md_entries; + } + } + return MDBX_SUCCESS; +} + +void mdbx_cursor_close(MDBX_cursor *mc) { + if (mc) { + mdbx_ensure(NULL, mc->mc_signature == MDBX_MC_SIGNATURE || + mc->mc_signature == MDBX_MC_READY4CLOSE); + if (!mc->mc_backup) { + /* Remove from txn, if tracked. + * A read-only txn (!C_UNTRACK) may have been freed already, + * so do not peek inside it. Only write txns track cursors. */ + if ((mc->mc_flags & C_UNTRACK) && mc->mc_txn->mt_cursors) { + MDBX_cursor **prev = &mc->mc_txn->mt_cursors[mc->mc_dbi]; + while (*prev && *prev != mc) + prev = &(*prev)->mc_next; + if (*prev == mc) + *prev = mc->mc_next; + } + mc->mc_signature = 0; + mdbx_free(mc); + } else { + /* cursor closed before nested txn ends */ + mdbx_cassert(mc, mc->mc_signature == MDBX_MC_SIGNATURE); + mc->mc_signature = MDBX_MC_WAIT4EOT; + } + } +} + +MDBX_txn *mdbx_cursor_txn(MDBX_cursor *mc) { + if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE)) + return NULL; + MDBX_txn *txn = mc->mc_txn; + if (unlikely(!txn || txn->mt_signature != MDBX_MT_SIGNATURE)) + return NULL; + if (unlikely(txn->mt_flags & MDBX_TXN_FINISHED)) + return NULL; + return txn; +} + +MDBX_dbi mdbx_cursor_dbi(MDBX_cursor *mc) { + if (unlikely(!mc || mc->mc_signature != MDBX_MC_SIGNATURE)) + return UINT_MAX; + return mc->mc_dbi; +} + +/* Replace the key for a branch node with a new key. + * Set MDBX_TXN_ERROR on failure. + * [in] mc Cursor pointing to the node to operate on. + * [in] key The new key to use. + * Returns 0 on success, non-zero on failure. */ +static int mdbx_update_key(MDBX_cursor *mc, const MDBX_val *key) { + MDBX_page *mp; + MDBX_node *node; + char *base; + size_t len; + int delta, ksize, oksize; + int ptr, i, nkeys, indx; + DKBUF; + + indx = mc->mc_ki[mc->mc_top]; + mp = mc->mc_pg[mc->mc_top]; + node = page_node(mp, indx); + ptr = mp->mp_ptrs[indx]; + if (MDBX_DEBUG) { + MDBX_val k2; + char kbuf2[DKBUF_MAXKEYSIZE * 2 + 1]; + k2.iov_base = node_key(node); + k2.iov_len = node_ks(node); + mdbx_debug("update key %u (ofs %u) [%s] to [%s] on page %" PRIaPGNO, indx, + ptr, mdbx_dump_val(&k2, kbuf2, sizeof(kbuf2)), DKEY(key), + mp->mp_pgno); + } + + /* Sizes must be 2-byte aligned. */ + ksize = EVEN(key->iov_len); + oksize = EVEN(node_ks(node)); + delta = ksize - oksize; + + /* Shift node contents if EVEN(key length) changed. */ + if (delta) { + if (delta > (int)page_room(mp)) { + /* not enough space left, do a delete and split */ + mdbx_debug("Not enough room, delta = %d, splitting...", delta); + pgno_t pgno = node_pgno(node); + mdbx_node_del(mc, 0); + int rc = mdbx_page_split(mc, key, NULL, pgno, MDBX_SPLIT_REPLACE); + if (rc == MDBX_SUCCESS && mdbx_audit_enabled()) + rc = mdbx_cursor_check(mc, true); + return rc; + } + + nkeys = page_numkeys(mp); + for (i = 0; i < nkeys; i++) { + if (mp->mp_ptrs[i] <= ptr) { + mdbx_cassert(mc, mp->mp_ptrs[i] >= delta); + mp->mp_ptrs[i] -= (indx_t)delta; + } + } + + base = (char *)mp + mp->mp_upper + PAGEHDRSZ; + len = ptr - mp->mp_upper + NODESIZE; + memmove(base - delta, base, len); + mdbx_cassert(mc, mp->mp_upper >= delta); + mp->mp_upper -= (indx_t)delta; + + node = page_node(mp, indx); + } + + /* But even if no shift was needed, update ksize */ + node_set_ks(node, key->iov_len); + + if (key->iov_len) + memcpy(node_key(node), key->iov_base, key->iov_len); + return MDBX_SUCCESS; +} + +/* Move a node from csrc to cdst. */ +static int mdbx_node_move(MDBX_cursor *csrc, MDBX_cursor *cdst, int fromleft) { + int rc; + DKBUF; + + MDBX_page *psrc = csrc->mc_pg[csrc->mc_top]; + MDBX_page *pdst = cdst->mc_pg[cdst->mc_top]; + mdbx_cassert(csrc, PAGETYPE(psrc) == PAGETYPE(pdst)); + mdbx_cassert(csrc, csrc->mc_dbi == cdst->mc_dbi); + mdbx_cassert(csrc, csrc->mc_top == cdst->mc_top); + if (unlikely(PAGETYPE(psrc) != PAGETYPE(pdst))) { + bailout: + csrc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return MDBX_PROBLEM; + } + + MDBX_val key4move; + switch (PAGETYPE(psrc)) { + case P_BRANCH: { + const MDBX_node *srcnode = page_node(psrc, csrc->mc_ki[csrc->mc_top]); + mdbx_cassert(csrc, node_flags(srcnode) == 0); + const pgno_t srcpg = node_pgno(srcnode); + key4move.iov_len = node_ks(srcnode); + key4move.iov_base = node_key(srcnode); + + if (csrc->mc_ki[csrc->mc_top] == 0) { + const uint16_t snum = csrc->mc_snum; + mdbx_cassert(csrc, snum > 0); + /* must find the lowest key below src */ + rc = mdbx_page_search_lowest(csrc); + MDBX_page *lowest_page = csrc->mc_pg[csrc->mc_top]; + if (unlikely(rc)) + return rc; + mdbx_cassert(csrc, IS_LEAF(lowest_page)); + if (unlikely(!IS_LEAF(lowest_page))) + goto bailout; + if (IS_LEAF2(lowest_page)) { + key4move.iov_len = csrc->mc_db->md_xsize; + key4move.iov_base = page_leaf2key(lowest_page, 0, key4move.iov_len); + } else { + const MDBX_node *lowest_node = page_node(lowest_page, 0); + key4move.iov_len = node_ks(lowest_node); + key4move.iov_base = node_key(lowest_node); + } + + /* restore cursor after mdbx_page_search_lowest() */ + csrc->mc_snum = snum; + csrc->mc_top = snum - 1; + csrc->mc_ki[csrc->mc_top] = 0; + + /* paranoia */ + mdbx_cassert(csrc, psrc == csrc->mc_pg[csrc->mc_top]); + mdbx_cassert(csrc, IS_BRANCH(psrc)); + if (unlikely(!IS_BRANCH(psrc))) + goto bailout; + } + + if (cdst->mc_ki[cdst->mc_top] == 0) { + const uint16_t snum = cdst->mc_snum; + mdbx_cassert(csrc, snum > 0); + MDBX_cursor mn; + mdbx_cursor_copy(cdst, &mn); + mn.mc_xcursor = NULL; + /* must find the lowest key below dst */ + rc = mdbx_page_search_lowest(&mn); + if (unlikely(rc)) + return rc; + MDBX_page *const lowest_page = mn.mc_pg[mn.mc_top]; + mdbx_cassert(cdst, IS_LEAF(lowest_page)); + if (unlikely(!IS_LEAF(lowest_page))) + goto bailout; + MDBX_val key; + if (IS_LEAF2(lowest_page)) { + key.iov_len = mn.mc_db->md_xsize; + key.iov_base = page_leaf2key(lowest_page, 0, key.iov_len); + } else { + MDBX_node *lowest_node = page_node(lowest_page, 0); + key.iov_len = node_ks(lowest_node); + key.iov_base = node_key(lowest_node); + } + + /* restore cursor after mdbx_page_search_lowest() */ + mn.mc_snum = snum; + mn.mc_top = snum - 1; + mn.mc_ki[mn.mc_top] = 0; + + const intptr_t delta = + EVEN(key.iov_len) - EVEN(node_ks(page_node(mn.mc_pg[mn.mc_top], 0))); + const intptr_t needed = + branch_size(cdst->mc_txn->mt_env, &key4move) + delta; + const intptr_t have = page_room(pdst); + if (unlikely(needed > have)) + return MDBX_RESULT_TRUE; + + if (unlikely((rc = mdbx_page_touch(csrc)) || + (rc = mdbx_page_touch(cdst)))) + return rc; + psrc = csrc->mc_pg[csrc->mc_top]; + pdst = cdst->mc_pg[cdst->mc_top]; + + rc = mdbx_update_key(&mn, &key); + if (unlikely(rc)) + return rc; + } else { + const size_t needed = branch_size(cdst->mc_txn->mt_env, &key4move); + const size_t have = page_room(pdst); + if (unlikely(needed > have)) + return MDBX_RESULT_TRUE; + + if (unlikely((rc = mdbx_page_touch(csrc)) || + (rc = mdbx_page_touch(cdst)))) + return rc; + psrc = csrc->mc_pg[csrc->mc_top]; + pdst = cdst->mc_pg[cdst->mc_top]; + } + + mdbx_debug("moving %s-node %u [%s] on page %" PRIaPGNO + " to node %u on page %" PRIaPGNO, + "branch", csrc->mc_ki[csrc->mc_top], DKEY(&key4move), + psrc->mp_pgno, cdst->mc_ki[cdst->mc_top], pdst->mp_pgno); + /* Add the node to the destination page. */ + rc = + mdbx_node_add_branch(cdst, cdst->mc_ki[cdst->mc_top], &key4move, srcpg); + } break; + + case P_LEAF: { + /* Mark src and dst as dirty. */ + if (unlikely((rc = mdbx_page_touch(csrc)) || (rc = mdbx_page_touch(cdst)))) + return rc; + psrc = csrc->mc_pg[csrc->mc_top]; + pdst = cdst->mc_pg[cdst->mc_top]; + const MDBX_node *srcnode = page_node(psrc, csrc->mc_ki[csrc->mc_top]); + MDBX_val data; + data.iov_len = node_ds(srcnode); + data.iov_base = node_data(srcnode); + key4move.iov_len = node_ks(srcnode); + key4move.iov_base = node_key(srcnode); + mdbx_debug("moving %s-node %u [%s] on page %" PRIaPGNO + " to node %u on page %" PRIaPGNO, + "leaf", csrc->mc_ki[csrc->mc_top], DKEY(&key4move), + psrc->mp_pgno, cdst->mc_ki[cdst->mc_top], pdst->mp_pgno); + /* Add the node to the destination page. */ + rc = mdbx_node_add_leaf(cdst, cdst->mc_ki[cdst->mc_top], &key4move, &data, + node_flags(srcnode)); + } break; + + case P_LEAF | P_LEAF2: { + /* Mark src and dst as dirty. */ + if (unlikely((rc = mdbx_page_touch(csrc)) || (rc = mdbx_page_touch(cdst)))) + return rc; + psrc = csrc->mc_pg[csrc->mc_top]; + pdst = cdst->mc_pg[cdst->mc_top]; + key4move.iov_len = csrc->mc_db->md_xsize; + key4move.iov_base = + page_leaf2key(psrc, csrc->mc_ki[csrc->mc_top], key4move.iov_len); + mdbx_debug("moving %s-node %u [%s] on page %" PRIaPGNO + " to node %u on page %" PRIaPGNO, + "leaf2", csrc->mc_ki[csrc->mc_top], DKEY(&key4move), + psrc->mp_pgno, cdst->mc_ki[cdst->mc_top], pdst->mp_pgno); + /* Add the node to the destination page. */ + rc = mdbx_node_add_leaf2(cdst, cdst->mc_ki[cdst->mc_top], &key4move); + } break; + + default: + goto bailout; + } + + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + /* Delete the node from the source page. */ + mdbx_node_del(csrc, key4move.iov_len); + + mdbx_cassert(csrc, psrc == csrc->mc_pg[csrc->mc_top]); + mdbx_cassert(cdst, pdst == cdst->mc_pg[cdst->mc_top]); + mdbx_cassert(csrc, PAGETYPE(psrc) == PAGETYPE(pdst)); + + { + /* Adjust other cursors pointing to mp */ + MDBX_cursor *m2, *m3; + const MDBX_dbi dbi = csrc->mc_dbi; + mdbx_cassert(csrc, csrc->mc_top == cdst->mc_top); + if (fromleft) { + /* If we're adding on the left, bump others up */ + for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (csrc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (!(m3->mc_flags & C_INITIALIZED) || m3->mc_top < csrc->mc_top) + continue; + if (m3 != cdst && m3->mc_pg[csrc->mc_top] == pdst && + m3->mc_ki[csrc->mc_top] >= cdst->mc_ki[csrc->mc_top]) { + m3->mc_ki[csrc->mc_top]++; + } + if (m3 != csrc && m3->mc_pg[csrc->mc_top] == psrc && + m3->mc_ki[csrc->mc_top] == csrc->mc_ki[csrc->mc_top]) { + m3->mc_pg[csrc->mc_top] = pdst; + m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top]; + mdbx_cassert(csrc, csrc->mc_top > 0); + m3->mc_ki[csrc->mc_top - 1]++; + } + if (XCURSOR_INITED(m3) && IS_LEAF(psrc)) + XCURSOR_REFRESH(m3, m3->mc_pg[csrc->mc_top], m3->mc_ki[csrc->mc_top]); + } + } else { + /* Adding on the right, bump others down */ + for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (csrc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == csrc) + continue; + if (!(m3->mc_flags & C_INITIALIZED) || m3->mc_top < csrc->mc_top) + continue; + if (m3->mc_pg[csrc->mc_top] == psrc) { + if (!m3->mc_ki[csrc->mc_top]) { + m3->mc_pg[csrc->mc_top] = pdst; + m3->mc_ki[csrc->mc_top] = cdst->mc_ki[cdst->mc_top]; + mdbx_cassert(csrc, csrc->mc_top > 0); + m3->mc_ki[csrc->mc_top - 1]--; + } else { + m3->mc_ki[csrc->mc_top]--; + } + if (XCURSOR_INITED(m3) && IS_LEAF(psrc)) + XCURSOR_REFRESH(m3, m3->mc_pg[csrc->mc_top], + m3->mc_ki[csrc->mc_top]); + } + } + } + } + + /* Update the parent separators. */ + if (csrc->mc_ki[csrc->mc_top] == 0) { + mdbx_cassert(csrc, csrc->mc_top > 0); + if (csrc->mc_ki[csrc->mc_top - 1] != 0) { + MDBX_val key; + if (IS_LEAF2(psrc)) { + key.iov_len = psrc->mp_leaf2_ksize; + key.iov_base = page_leaf2key(psrc, 0, key.iov_len); + } else { + MDBX_node *srcnode = page_node(psrc, 0); + key.iov_len = node_ks(srcnode); + key.iov_base = node_key(srcnode); + } + mdbx_debug("update separator for source page %" PRIaPGNO " to [%s]", + psrc->mp_pgno, DKEY(&key)); + MDBX_cursor mn; + mdbx_cursor_copy(csrc, &mn); + mn.mc_xcursor = NULL; + mdbx_cassert(csrc, mn.mc_snum > 0); + mn.mc_snum--; + mn.mc_top--; + /* We want mdbx_rebalance to find mn when doing fixups */ + WITH_CURSOR_TRACKING(mn, rc = mdbx_update_key(&mn, &key)); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + if (IS_BRANCH(psrc)) { + const MDBX_val nullkey = {0, 0}; + const indx_t ix = csrc->mc_ki[csrc->mc_top]; + csrc->mc_ki[csrc->mc_top] = 0; + rc = mdbx_update_key(csrc, &nullkey); + csrc->mc_ki[csrc->mc_top] = ix; + mdbx_cassert(csrc, rc == MDBX_SUCCESS); + } + } + + if (cdst->mc_ki[cdst->mc_top] == 0) { + mdbx_cassert(cdst, cdst->mc_top > 0); + if (cdst->mc_ki[cdst->mc_top - 1] != 0) { + MDBX_val key; + if (IS_LEAF2(pdst)) { + key.iov_len = pdst->mp_leaf2_ksize; + key.iov_base = page_leaf2key(pdst, 0, key.iov_len); + } else { + MDBX_node *srcnode = page_node(pdst, 0); + key.iov_len = node_ks(srcnode); + key.iov_base = node_key(srcnode); + } + mdbx_debug("update separator for destination page %" PRIaPGNO " to [%s]", + pdst->mp_pgno, DKEY(&key)); + MDBX_cursor mn; + mdbx_cursor_copy(cdst, &mn); + mn.mc_xcursor = NULL; + mdbx_cassert(cdst, mn.mc_snum > 0); + mn.mc_snum--; + mn.mc_top--; + /* We want mdbx_rebalance to find mn when doing fixups */ + WITH_CURSOR_TRACKING(mn, rc = mdbx_update_key(&mn, &key)); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + if (IS_BRANCH(pdst)) { + const MDBX_val nullkey = {0, 0}; + const indx_t ix = cdst->mc_ki[cdst->mc_top]; + cdst->mc_ki[cdst->mc_top] = 0; + rc = mdbx_update_key(cdst, &nullkey); + cdst->mc_ki[cdst->mc_top] = ix; + mdbx_cassert(cdst, rc == MDBX_SUCCESS); + } + } + + return MDBX_SUCCESS; +} + +/* Merge one page into another. + * + * The nodes from the page pointed to by csrc will be copied to the page + * pointed to by cdst and then the csrc page will be freed. + * + * [in] csrc Cursor pointing to the source page. + * [in] cdst Cursor pointing to the destination page. + * + * Returns 0 on success, non-zero on failure. */ +static int mdbx_page_merge(MDBX_cursor *csrc, MDBX_cursor *cdst) { + MDBX_val key; + int rc; + + mdbx_cassert(csrc, csrc != cdst); + const MDBX_page *const psrc = csrc->mc_pg[csrc->mc_top]; + MDBX_page *pdst = cdst->mc_pg[cdst->mc_top]; + mdbx_debug("merging page %" PRIaPGNO " into %" PRIaPGNO, psrc->mp_pgno, + pdst->mp_pgno); + + mdbx_cassert(csrc, PAGETYPE(psrc) == PAGETYPE(pdst)); + mdbx_cassert(csrc, + csrc->mc_dbi == cdst->mc_dbi && csrc->mc_db == cdst->mc_db); + mdbx_cassert(csrc, csrc->mc_snum > 1); /* can't merge root page */ + mdbx_cassert(cdst, cdst->mc_snum > 1); + mdbx_cassert(cdst, cdst->mc_snum < cdst->mc_db->md_depth || + IS_LEAF(cdst->mc_pg[cdst->mc_db->md_depth - 1])); + mdbx_cassert(csrc, csrc->mc_snum < csrc->mc_db->md_depth || + IS_LEAF(csrc->mc_pg[csrc->mc_db->md_depth - 1])); + mdbx_cassert(cdst, page_room(pdst) >= page_used(cdst->mc_txn->mt_env, psrc)); + const int pagetype = PAGETYPE(psrc); + + /* Move all nodes from src to dst */ + const unsigned dst_nkeys = page_numkeys(pdst); + const unsigned src_nkeys = page_numkeys(psrc); + if (likely(src_nkeys)) { + unsigned j = dst_nkeys; + if (unlikely(pagetype & P_LEAF2)) { + /* Mark dst as dirty. */ + if (unlikely(rc = mdbx_page_touch(cdst))) + return rc; + + key.iov_len = csrc->mc_db->md_xsize; + key.iov_base = page_data(psrc); + unsigned i = 0; + do { + rc = mdbx_node_add_leaf2(cdst, j++, &key); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + key.iov_base = (char *)key.iov_base + key.iov_len; + } while (++i != src_nkeys); + } else { + MDBX_node *srcnode = page_node(psrc, 0); + key.iov_len = node_ks(srcnode); + key.iov_base = node_key(srcnode); + if (pagetype & P_BRANCH) { + MDBX_cursor mn; + mdbx_cursor_copy(csrc, &mn); + mn.mc_xcursor = NULL; + /* must find the lowest key below src */ + rc = mdbx_page_search_lowest(&mn); + if (unlikely(rc)) + return rc; + if (IS_LEAF2(mn.mc_pg[mn.mc_top])) { + key.iov_len = mn.mc_db->md_xsize; + key.iov_base = page_leaf2key(mn.mc_pg[mn.mc_top], 0, key.iov_len); + } else { + MDBX_node *lowest = page_node(mn.mc_pg[mn.mc_top], 0); + key.iov_len = node_ks(lowest); + key.iov_base = node_key(lowest); + + const size_t dst_room = page_room(pdst); + const size_t src_used = page_used(cdst->mc_txn->mt_env, psrc); + const size_t space_needed = src_used - node_ks(srcnode) + key.iov_len; + if (unlikely(space_needed > dst_room)) + return MDBX_RESULT_TRUE; + } + } + + /* Mark dst as dirty. */ + if (unlikely(rc = mdbx_page_touch(cdst))) + return rc; + + unsigned i = 0; + while (true) { + if (pagetype & P_LEAF) { + MDBX_val data; + data.iov_len = node_ds(srcnode); + data.iov_base = node_data(srcnode); + rc = mdbx_node_add_leaf(cdst, j++, &key, &data, node_flags(srcnode)); + } else { + mdbx_cassert(csrc, node_flags(srcnode) == 0); + rc = mdbx_node_add_branch(cdst, j++, &key, node_pgno(srcnode)); + } + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (++i == src_nkeys) + break; + srcnode = page_node(psrc, i); + key.iov_len = node_ks(srcnode); + key.iov_base = node_key(srcnode); + } + } + + pdst = cdst->mc_pg[cdst->mc_top]; + mdbx_debug("dst page %" PRIaPGNO " now has %u keys (%.1f%% filled)", + pdst->mp_pgno, page_numkeys(pdst), + page_fill(cdst->mc_txn->mt_env, pdst)); + + mdbx_cassert(csrc, psrc == csrc->mc_pg[csrc->mc_top]); + mdbx_cassert(cdst, pdst == cdst->mc_pg[cdst->mc_top]); + } + + /* Unlink the src page from parent and add to free list. */ + csrc->mc_top--; + mdbx_node_del(csrc, 0); + if (csrc->mc_ki[csrc->mc_top] == 0) { + const MDBX_val nullkey = {0, 0}; + rc = mdbx_update_key(csrc, &nullkey); + if (unlikely(rc)) { + csrc->mc_top++; + return rc; + } + } + csrc->mc_top++; + + mdbx_cassert(csrc, psrc == csrc->mc_pg[csrc->mc_top]); + mdbx_cassert(cdst, pdst == cdst->mc_pg[cdst->mc_top]); + + { + /* Adjust other cursors pointing to mp */ + MDBX_cursor *m2, *m3; + const MDBX_dbi dbi = csrc->mc_dbi; + const unsigned top = csrc->mc_top; + + for (m2 = csrc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (csrc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == csrc || top >= m3->mc_snum) + continue; + if (m3->mc_pg[top] == psrc) { + m3->mc_pg[top] = pdst; + mdbx_cassert(m3, dst_nkeys + m3->mc_ki[top] <= UINT16_MAX); + m3->mc_ki[top] += (indx_t)dst_nkeys; + m3->mc_ki[top - 1] = cdst->mc_ki[top - 1]; + } else if (m3->mc_pg[top - 1] == csrc->mc_pg[top - 1] && + m3->mc_ki[top - 1] > csrc->mc_ki[top - 1]) { + m3->mc_ki[top - 1]--; + } + if (XCURSOR_INITED(m3) && IS_LEAF(psrc)) + XCURSOR_REFRESH(m3, m3->mc_pg[top], m3->mc_ki[top]); + } + } + + /* If not operating on GC, allow this page to be reused + * in this txn. Otherwise just add to free list. */ + rc = mdbx_page_retire(csrc, (MDBX_page *)psrc); + if (unlikely(rc)) + return rc; + + mdbx_cassert(cdst, cdst->mc_db->md_entries > 0); + mdbx_cassert(cdst, cdst->mc_snum <= cdst->mc_db->md_depth); + mdbx_cassert(cdst, cdst->mc_top > 0); + mdbx_cassert(cdst, cdst->mc_snum == cdst->mc_top + 1); + MDBX_page *const top_page = cdst->mc_pg[cdst->mc_top]; + const indx_t top_indx = cdst->mc_ki[cdst->mc_top]; + const uint16_t save_snum = cdst->mc_snum; + const uint16_t save_depth = cdst->mc_db->md_depth; + mdbx_cursor_pop(cdst); + rc = mdbx_rebalance(cdst); + if (unlikely(rc)) + return rc; + + mdbx_cassert(cdst, cdst->mc_db->md_entries > 0); + mdbx_cassert(cdst, cdst->mc_snum <= cdst->mc_db->md_depth); + mdbx_cassert(cdst, cdst->mc_snum == cdst->mc_top + 1); + + if (IS_LEAF(cdst->mc_pg[cdst->mc_top])) { + /* LY: don't touch cursor if top-page is a LEAF */ + mdbx_cassert(cdst, IS_LEAF(cdst->mc_pg[cdst->mc_top]) || + PAGETYPE(cdst->mc_pg[cdst->mc_top]) == pagetype); + return MDBX_SUCCESS; + } + + if (pagetype != PAGETYPE(top_page)) { + /* LY: LEAF-page becomes BRANCH, unable restore cursor's stack */ + goto bailout; + } + + if (top_page == cdst->mc_pg[cdst->mc_top]) { + /* LY: don't touch cursor if prev top-page already on the top */ + mdbx_cassert(cdst, cdst->mc_ki[cdst->mc_top] == top_indx); + mdbx_cassert(cdst, IS_LEAF(cdst->mc_pg[cdst->mc_top]) || + PAGETYPE(cdst->mc_pg[cdst->mc_top]) == pagetype); + return MDBX_SUCCESS; + } + + const int new_snum = save_snum - save_depth + cdst->mc_db->md_depth; + if (unlikely(new_snum < 1 || new_snum > cdst->mc_db->md_depth)) { + /* LY: out of range, unable restore cursor's stack */ + goto bailout; + } + + if (top_page == cdst->mc_pg[new_snum - 1]) { + mdbx_cassert(cdst, cdst->mc_ki[new_snum - 1] == top_indx); + /* LY: restore cursor stack */ + cdst->mc_snum = (uint16_t)new_snum; + cdst->mc_top = (uint16_t)new_snum - 1; + mdbx_cassert(cdst, cdst->mc_snum < cdst->mc_db->md_depth || + IS_LEAF(cdst->mc_pg[cdst->mc_db->md_depth - 1])); + mdbx_cassert(cdst, IS_LEAF(cdst->mc_pg[cdst->mc_top]) || + PAGETYPE(cdst->mc_pg[cdst->mc_top]) == pagetype); + return MDBX_SUCCESS; + } + + MDBX_page *const stub_page = (MDBX_page *)(~(uintptr_t)top_page); + const indx_t stub_indx = top_indx; + if (save_depth > cdst->mc_db->md_depth && + ((cdst->mc_pg[save_snum - 1] == top_page && + cdst->mc_ki[save_snum - 1] == top_indx) || + (cdst->mc_pg[save_snum - 1] == stub_page && + cdst->mc_ki[save_snum - 1] == stub_indx))) { + /* LY: restore cursor stack */ + cdst->mc_pg[new_snum - 1] = top_page; + cdst->mc_ki[new_snum - 1] = top_indx; + cdst->mc_pg[new_snum] = (MDBX_page *)(~(uintptr_t)cdst->mc_pg[new_snum]); + cdst->mc_ki[new_snum] = ~cdst->mc_ki[new_snum]; + cdst->mc_snum = (uint16_t)new_snum; + cdst->mc_top = (uint16_t)new_snum - 1; + mdbx_cassert(cdst, cdst->mc_snum < cdst->mc_db->md_depth || + IS_LEAF(cdst->mc_pg[cdst->mc_db->md_depth - 1])); + mdbx_cassert(cdst, IS_LEAF(cdst->mc_pg[cdst->mc_top]) || + PAGETYPE(cdst->mc_pg[cdst->mc_top]) == pagetype); + return MDBX_SUCCESS; + } + +bailout: + /* LY: unable restore cursor's stack */ + cdst->mc_flags &= ~C_INITIALIZED; + return MDBX_CURSOR_FULL; +} + +/* Copy the contents of a cursor. + * [in] csrc The cursor to copy from. + * [out] cdst The cursor to copy to. */ +static void mdbx_cursor_copy(const MDBX_cursor *csrc, MDBX_cursor *cdst) { + unsigned i; + + mdbx_cassert(csrc, + csrc->mc_txn->mt_txnid >= *csrc->mc_txn->mt_env->me_oldest); + cdst->mc_txn = csrc->mc_txn; + cdst->mc_dbi = csrc->mc_dbi; + cdst->mc_db = csrc->mc_db; + cdst->mc_dbx = csrc->mc_dbx; + cdst->mc_snum = csrc->mc_snum; + cdst->mc_top = csrc->mc_top; + cdst->mc_flags = csrc->mc_flags; + + for (i = 0; i < csrc->mc_snum; i++) { + cdst->mc_pg[i] = csrc->mc_pg[i]; + cdst->mc_ki[i] = csrc->mc_ki[i]; + } +} + +/* Rebalance the tree after a delete operation. + * [in] mc Cursor pointing to the page where rebalancing should begin. + * Returns 0 on success, non-zero on failure. */ +static int mdbx_rebalance(MDBX_cursor *mc) { + int rc; + + mdbx_cassert(mc, mc->mc_snum > 0); + mdbx_cassert(mc, mc->mc_snum < mc->mc_db->md_depth || + IS_LEAF(mc->mc_pg[mc->mc_db->md_depth - 1])); + const int pagetype = PAGETYPE(mc->mc_pg[mc->mc_top]); + + const unsigned minkeys = (P_BRANCH == 1) ? (pagetype & P_BRANCH) + 1 + : (pagetype & P_BRANCH) ? 2 : 1; + + /* The threshold of minimum page fill factor, in form of a negative binary + * exponent, i.e. 2 means 1/(2**3) == 1/4 == 25%. Pages emptier than this + * are candidates for merging. */ + const unsigned threshold_fill_exp2 = 2; + + /* The threshold of minimum page fill factor, as a number of free bytes on a + * page. Pages emptier than this are candidates for merging. */ + const unsigned spaceleft_threshold = + page_space(mc->mc_txn->mt_env) - + (page_space(mc->mc_txn->mt_env) >> threshold_fill_exp2); + + mdbx_debug("rebalancing %s page %" PRIaPGNO " (has %u keys, %.1f%% full)", + (pagetype & P_LEAF) ? "leaf" : "branch", + mc->mc_pg[mc->mc_top]->mp_pgno, + page_numkeys(mc->mc_pg[mc->mc_top]), + page_fill(mc->mc_txn->mt_env, mc->mc_pg[mc->mc_top])); + + if (page_fill_enough(mc->mc_pg[mc->mc_top], spaceleft_threshold, minkeys)) { + mdbx_debug("no need to rebalance page %" PRIaPGNO ", above fill threshold", + mc->mc_pg[mc->mc_top]->mp_pgno); + mdbx_cassert(mc, mc->mc_db->md_entries > 0); + return MDBX_SUCCESS; + } + + if (mc->mc_snum < 2) { + MDBX_page *const mp = mc->mc_pg[0]; + const unsigned nkeys = page_numkeys(mp); + mdbx_cassert(mc, (mc->mc_db->md_entries == 0) == (nkeys == 0)); + if (IS_SUBP(mp)) { + mdbx_debug("%s", "Can't rebalance a subpage, ignoring"); + mdbx_cassert(mc, pagetype & P_LEAF); + return MDBX_SUCCESS; + } + if (nkeys == 0) { + mdbx_cassert(mc, IS_LEAF(mp)); + mdbx_debug("%s", "tree is completely empty"); + mc->mc_db->md_root = P_INVALID; + mc->mc_db->md_depth = 0; + mdbx_cassert(mc, mc->mc_db->md_branch_pages == 0 && + mc->mc_db->md_overflow_pages == 0 && + mc->mc_db->md_leaf_pages == 1); + /* Adjust cursors pointing to mp */ + const MDBX_dbi dbi = mc->mc_dbi; + for (MDBX_cursor *m2 = mc->mc_txn->mt_cursors[dbi]; m2; + m2 = m2->mc_next) { + MDBX_cursor *m3 = + (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == mc || !(m3->mc_flags & C_INITIALIZED)) + continue; + if (m3->mc_pg[0] == mp) { + m3->mc_snum = 0; + m3->mc_top = 0; + m3->mc_flags &= ~C_INITIALIZED; + } + } + mc->mc_snum = 0; + mc->mc_top = 0; + mc->mc_flags &= ~C_INITIALIZED; + + rc = mdbx_page_retire(mc, mp); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } else if (IS_BRANCH(mp) && nkeys == 1) { + mdbx_debug("%s", "collapsing root page!"); + mc->mc_db->md_root = node_pgno(page_node(mp, 0)); + rc = mdbx_page_get(mc, mc->mc_db->md_root, &mc->mc_pg[0], NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + mc->mc_db->md_depth--; + mc->mc_ki[0] = mc->mc_ki[1]; + for (int i = 1; i < mc->mc_db->md_depth; i++) { + mc->mc_pg[i] = mc->mc_pg[i + 1]; + mc->mc_ki[i] = mc->mc_ki[i + 1]; + } + + /* Adjust other cursors pointing to mp */ + MDBX_cursor *m2, *m3; + MDBX_dbi dbi = mc->mc_dbi; + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == mc || !(m3->mc_flags & C_INITIALIZED)) + continue; + if (m3->mc_pg[0] == mp) { + for (int i = 0; i < mc->mc_db->md_depth; i++) { + m3->mc_pg[i] = m3->mc_pg[i + 1]; + m3->mc_ki[i] = m3->mc_ki[i + 1]; + } + m3->mc_snum--; + m3->mc_top--; + } + } + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]) || + PAGETYPE(mc->mc_pg[mc->mc_top]) == pagetype); + mdbx_cassert(mc, mc->mc_snum < mc->mc_db->md_depth || + IS_LEAF(mc->mc_pg[mc->mc_db->md_depth - 1])); + + rc = mdbx_page_retire(mc, mp); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } else { + mdbx_debug("root page %" PRIaPGNO + " doesn't need rebalancing (flags 0x%x)", + mp->mp_pgno, mp->mp_flags); + } + return MDBX_SUCCESS; + } + + /* The parent (branch page) must have at least 2 pointers, + * otherwise the tree is invalid. */ + const unsigned pre_top = mc->mc_top - 1; + mdbx_cassert(mc, IS_BRANCH(mc->mc_pg[pre_top])); + mdbx_cassert(mc, !IS_SUBP(mc->mc_pg[0])); + mdbx_cassert(mc, page_numkeys(mc->mc_pg[pre_top]) > 1); + + /* Leaf page fill factor is below the threshold. + * Try to move keys from left or right neighbor, or + * merge with a neighbor page. */ + + /* Find neighbors. */ + MDBX_cursor mn; + mdbx_cursor_copy(mc, &mn); + mn.mc_xcursor = NULL; + + MDBX_page *left = nullptr, *right = nullptr; + if (mn.mc_ki[pre_top] > 0) { + rc = mdbx_page_get( + &mn, node_pgno(page_node(mn.mc_pg[pre_top], mn.mc_ki[pre_top] - 1)), + &left, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + mdbx_cassert(mc, PAGETYPE(left) == PAGETYPE(mc->mc_pg[mc->mc_top])); + } + if (mn.mc_ki[pre_top] + 1u < page_numkeys(mn.mc_pg[pre_top])) { + rc = mdbx_page_get( + &mn, node_pgno(page_node(mn.mc_pg[pre_top], mn.mc_ki[pre_top] + 1)), + &right, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + mdbx_cassert(mc, PAGETYPE(right) == PAGETYPE(mc->mc_pg[mc->mc_top])); + } + + int ki = mc->mc_ki[mc->mc_top]; + bool fromleft; + if (!left || (right && page_room(left) < page_room(right))) { + mdbx_debug("merging %s neighbor", "right"); + mn.mc_pg[mn.mc_top] = right; + mn.mc_ki[pre_top] += 1; + mn.mc_ki[mn.mc_top] = 0; + mc->mc_ki[mc->mc_top] = (indx_t)page_numkeys(mc->mc_pg[mc->mc_top]); + fromleft = false; + } else { + mdbx_debug("merging %s neighbor", "left"); + mn.mc_pg[mn.mc_top] = left; + mn.mc_ki[pre_top] -= 1; + mn.mc_ki[mn.mc_top] = (indx_t)page_numkeys(mn.mc_pg[mn.mc_top]) - 1; + mc->mc_ki[mc->mc_top] = 0; + fromleft = true; + } + + mdbx_debug("found neighbor page %" PRIaPGNO " (%u keys, %.1f%% full)", + mn.mc_pg[mn.mc_top]->mp_pgno, page_numkeys(mn.mc_pg[mn.mc_top]), + page_fill(mc->mc_txn->mt_env, mn.mc_pg[mn.mc_top])); + + /* If the neighbor page is above threshold and has enough keys, + * move one key from it. Otherwise we should try to merge them. + * (A branch page must never have less than 2 keys.) */ + if (page_fill_enough(mn.mc_pg[mn.mc_top], spaceleft_threshold, minkeys + 1)) { + rc = mdbx_node_move(&mn, mc, fromleft); + if (likely(rc == MDBX_SUCCESS)) + ki += fromleft /* if we inserted on left, bump position up */; + else if (unlikely(rc != MDBX_RESULT_TRUE)) + return rc; + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]) || + PAGETYPE(mc->mc_pg[mc->mc_top]) == pagetype); + mdbx_cassert(mc, mc->mc_snum < mc->mc_db->md_depth || + IS_LEAF(mc->mc_pg[mc->mc_db->md_depth - 1])); + } else { + if (!fromleft) { + rc = mdbx_page_merge(&mn, mc); + if (unlikely(MDBX_IS_ERROR(rc))) + return rc; + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]) || + PAGETYPE(mc->mc_pg[mc->mc_top]) == pagetype); + mdbx_cassert(mc, mc->mc_snum < mc->mc_db->md_depth || + IS_LEAF(mc->mc_pg[mc->mc_db->md_depth - 1])); + } else { + int new_ki = ki + page_numkeys(mn.mc_pg[mn.mc_top]); + mn.mc_ki[mn.mc_top] += mc->mc_ki[mn.mc_top] + 1; + /* We want mdbx_rebalance to find mn when doing fixups */ + WITH_CURSOR_TRACKING(mn, rc = mdbx_page_merge(mc, &mn)); + if (likely(rc == MDBX_SUCCESS)) { + ki = new_ki; + mdbx_cursor_copy(&mn, mc); + } else if (unlikely(rc != MDBX_RESULT_TRUE)) + return rc; + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top]) || + PAGETYPE(mc->mc_pg[mc->mc_top]) == pagetype); + mdbx_cassert(mc, mc->mc_snum < mc->mc_db->md_depth || + IS_LEAF(mc->mc_pg[mc->mc_db->md_depth - 1])); + } + } + mc->mc_ki[mc->mc_top] = (indx_t)ki; + return MDBX_SUCCESS; +} + +static __cold int mdbx_page_check(MDBX_env *env, const MDBX_page *const mp, + bool maybe_unfinished) { + const unsigned nkeys = page_numkeys(mp); + char *const end_of_page = (char *)mp + env->me_psize; + mdbx_assert(env, mp->mp_pgno >= MIN_PAGENO && mp->mp_pgno <= MAX_PAGENO); + if (unlikely(mp->mp_pgno < MIN_PAGENO || mp->mp_pgno > MAX_PAGENO)) + return MDBX_CORRUPTED; + if (IS_OVERFLOW(mp)) { + mdbx_assert(env, mp->mp_pages >= 1 && mp->mp_pages < MAX_PAGENO / 2); + if (unlikely(mp->mp_pages < 1 && mp->mp_pages >= MAX_PAGENO / 2)) + return MDBX_CORRUPTED; + mdbx_assert(env, mp->mp_pgno <= MAX_PAGENO - mp->mp_pages); + if (unlikely(mp->mp_pgno > MAX_PAGENO - mp->mp_pages)) + return MDBX_CORRUPTED; + return MDBX_SUCCESS; + } + if (!(IS_DIRTY(mp) && maybe_unfinished)) { + mdbx_assert(env, nkeys >= 2 || !IS_BRANCH(mp)); + if (unlikely(nkeys < 2 && IS_BRANCH(mp))) + return MDBX_CORRUPTED; + } + + for (unsigned i = IS_LEAF(mp) ? 0 : 1; i < nkeys; ++i) { + if (IS_LEAF2(mp)) { + const size_t ksize = mp->mp_leaf2_ksize; + const char *const key = page_leaf2key(mp, i, ksize); + mdbx_assert(env, key + ksize <= end_of_page); + if (unlikely(end_of_page < key + ksize)) + return MDBX_CORRUPTED; + } else { + const MDBX_node *const node = page_node(mp, i); + const char *node_end = (char *)node + NODESIZE; + mdbx_assert(env, node_end <= end_of_page); + if (unlikely(node_end > end_of_page)) + return MDBX_CORRUPTED; + if (IS_LEAF(mp) || i > 0) { + size_t ksize = node_ks(node); + char *key = node_key(node); + mdbx_assert(env, key + ksize <= end_of_page); + if (unlikely(end_of_page < key + ksize)) + return MDBX_CORRUPTED; + } + if (IS_BRANCH(mp)) + continue; + if (node_flags(node) == F_BIGDATA /* data on large-page */) { + continue; + } + const size_t dsize = node_ds(node); + const char *const data = node_data(node); + mdbx_assert(env, data + dsize <= end_of_page); + if (unlikely(end_of_page < data + dsize)) + return MDBX_CORRUPTED; + + switch (node_flags(node)) { + default: + mdbx_assert(env, false); + return MDBX_CORRUPTED; + case 0 /* usual */: + break; + case F_SUBDATA /* sub-db */: + mdbx_assert(env, dsize >= sizeof(MDBX_db)); + if (unlikely(dsize < sizeof(MDBX_db))) + return MDBX_CORRUPTED; + break; + case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: + mdbx_assert(env, dsize == sizeof(MDBX_db)); + if (unlikely(dsize != sizeof(MDBX_db))) + return MDBX_CORRUPTED; + break; + case F_DUPDATA /* short sub-page */: + mdbx_assert(env, dsize > PAGEHDRSZ); + if (unlikely(dsize <= PAGEHDRSZ)) + return MDBX_CORRUPTED; + else { + const MDBX_page *const sp = (MDBX_page *)data; + const char *const end_of_subpage = data + dsize; + const int nsubkeys = page_numkeys(sp); + switch (sp->mp_flags & ~P_DIRTY /* ignore for sub-pages */) { + case P_LEAF | P_SUBP: + case P_LEAF | P_LEAF2 | P_SUBP: + break; + default: + mdbx_assert(env, false); + return MDBX_CORRUPTED; + } + + for (int j = 0; j < nsubkeys; j++) { + if (IS_LEAF2(sp)) { + /* LEAF2 pages have no mp_ptrs[] or node headers */ + size_t sub_ksize = sp->mp_leaf2_ksize; + char *sub_key = page_leaf2key(sp, j, sub_ksize); + mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); + if (unlikely(end_of_subpage < sub_key + sub_ksize)) + return MDBX_CORRUPTED; + } else { + mdbx_assert(env, IS_LEAF(sp)); + if (unlikely(!IS_LEAF(sp))) + return MDBX_CORRUPTED; + const MDBX_node *const sub_node = page_node(sp, j); + const char *sub_node_end = (char *)sub_node + NODESIZE; + mdbx_assert(env, sub_node_end <= end_of_subpage); + if (unlikely(sub_node_end > end_of_subpage)) + return MDBX_CORRUPTED; + mdbx_assert(env, node_flags(sub_node) == 0); + if (unlikely(node_flags(sub_node) != 0)) + return MDBX_CORRUPTED; + + size_t sub_ksize = node_ks(sub_node); + char *sub_key = node_key(sub_node); + size_t sub_dsize = node_ds(sub_node); + char *sub_data = node_data(sub_node); + mdbx_assert(env, sub_key + sub_ksize <= end_of_subpage); + if (unlikely(end_of_subpage < sub_key + sub_ksize)) + return MDBX_CORRUPTED; + mdbx_assert(env, sub_data + sub_dsize <= end_of_subpage); + if (unlikely(end_of_subpage < sub_data + sub_dsize)) + return MDBX_CORRUPTED; + } + } + } + break; + } + } + } + return MDBX_SUCCESS; +} + +static __cold int mdbx_cursor_check(MDBX_cursor *mc, bool pending) { + mdbx_tassert(mc->mc_txn, mc->mc_txn->mt_parent || + mc->mc_txn->tw.dirtyroom + + mc->mc_txn->tw.dirtylist->length == + MDBX_DPL_TXNFULL); + mdbx_cassert(mc, mc->mc_top == mc->mc_snum - 1); + if (unlikely(mc->mc_top != mc->mc_snum - 1)) + return MDBX_CURSOR_FULL; + mdbx_cassert(mc, pending ? mc->mc_snum <= mc->mc_db->md_depth + : mc->mc_snum == mc->mc_db->md_depth); + if (unlikely(pending ? mc->mc_snum > mc->mc_db->md_depth + : mc->mc_snum != mc->mc_db->md_depth)) + return MDBX_CURSOR_FULL; + + for (int n = 0; n < mc->mc_snum; ++n) { + MDBX_page *mp = mc->mc_pg[n]; + const unsigned nkeys = page_numkeys(mp); + const bool expect_branch = (n < mc->mc_db->md_depth - 1) ? true : false; + const bool expect_nested_leaf = + (n + 1 == mc->mc_db->md_depth - 1) ? true : false; + const bool branch = IS_BRANCH(mp) ? true : false; + mdbx_cassert(mc, branch == expect_branch); + if (unlikely(branch != expect_branch)) + return MDBX_CURSOR_FULL; + if (!pending) { + mdbx_cassert(mc, + nkeys > mc->mc_ki[n] || (!branch && nkeys == mc->mc_ki[n] && + (mc->mc_flags & C_EOF) != 0)); + if (unlikely(nkeys <= mc->mc_ki[n] && + !(!branch && nkeys == mc->mc_ki[n] && + (mc->mc_flags & C_EOF) != 0))) + return MDBX_CURSOR_FULL; + } else { + mdbx_cassert(mc, nkeys + 1 >= mc->mc_ki[n]); + if (unlikely(nkeys + 1 < mc->mc_ki[n])) + return MDBX_CURSOR_FULL; + } + + int err = mdbx_page_check(mc->mc_txn->mt_env, mp, pending); + if (unlikely(err != MDBX_SUCCESS)) + return err; + + for (unsigned i = 0; i < nkeys; ++i) { + if (branch) { + MDBX_node *node = page_node(mp, i); + mdbx_cassert(mc, node_flags(node) == 0); + if (unlikely(node_flags(node) != 0)) + return MDBX_CURSOR_FULL; + pgno_t pgno = node_pgno(node); + MDBX_page *np; + int rc = mdbx_page_get(mc, pgno, &np, NULL); + mdbx_cassert(mc, rc == MDBX_SUCCESS); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + const bool nested_leaf = IS_LEAF(np) ? true : false; + mdbx_cassert(mc, nested_leaf == expect_nested_leaf); + if (unlikely(nested_leaf != expect_nested_leaf)) + return MDBX_CURSOR_FULL; + err = mdbx_page_check(mc->mc_txn->mt_env, np, pending); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + } + } + return MDBX_SUCCESS; +} + +/* Complete a delete operation started by mdbx_cursor_del(). */ +static int mdbx_cursor_del0(MDBX_cursor *mc) { + int rc; + MDBX_page *mp; + indx_t ki; + unsigned nkeys; + MDBX_cursor *m2, *m3; + MDBX_dbi dbi = mc->mc_dbi; + + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); + ki = mc->mc_ki[mc->mc_top]; + mp = mc->mc_pg[mc->mc_top]; + mdbx_node_del(mc, mc->mc_db->md_xsize); + mc->mc_db->md_entries--; + { + /* Adjust other cursors pointing to mp */ + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == mc || !(m2->mc_flags & m3->mc_flags & C_INITIALIZED)) + continue; + if (m3->mc_snum < mc->mc_snum) + continue; + if (m3->mc_pg[mc->mc_top] == mp) { + if (m3->mc_ki[mc->mc_top] == ki) { + m3->mc_flags |= C_DEL; + if (mc->mc_db->md_flags & MDBX_DUPSORT) { + /* Sub-cursor referred into dataset which is gone */ + m3->mc_xcursor->mx_cursor.mc_flags &= ~(C_INITIALIZED | C_EOF); + } + continue; + } else if (m3->mc_ki[mc->mc_top] > ki) { + m3->mc_ki[mc->mc_top]--; + } + if (XCURSOR_INITED(m3)) + XCURSOR_REFRESH(m3, m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]); + } + } + } + rc = mdbx_rebalance(mc); + + if (likely(rc == MDBX_SUCCESS)) { + /* DB is totally empty now, just bail out. + * Other cursors adjustments were already done + * by mdbx_rebalance and aren't needed here. */ + if (!mc->mc_snum) { + mdbx_cassert(mc, mc->mc_db->md_entries == 0 && mc->mc_db->md_depth == 0 && + mc->mc_db->md_root == P_INVALID); + mc->mc_flags |= C_DEL | C_EOF; + return rc; + } + + ki = mc->mc_ki[mc->mc_top]; + mp = mc->mc_pg[mc->mc_top]; + mdbx_cassert(mc, IS_LEAF(mc->mc_pg[mc->mc_top])); + nkeys = page_numkeys(mp); + mdbx_cassert(mc, (mc->mc_db->md_entries > 0 && nkeys > 0) || + ((mc->mc_flags & C_SUB) && + mc->mc_db->md_entries == 0 && nkeys == 0)); + + /* Adjust THIS and other cursors pointing to mp */ + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == mc || !(m2->mc_flags & m3->mc_flags & C_INITIALIZED)) + continue; + if (m3->mc_snum < mc->mc_snum) + continue; + if (m3->mc_pg[mc->mc_top] == mp) { + /* if m3 points past last node in page, find next sibling */ + if (m3->mc_ki[mc->mc_top] >= nkeys) { + rc = mdbx_cursor_sibling(m3, true); + if (rc == MDBX_NOTFOUND) { + m3->mc_flags |= C_EOF; + rc = MDBX_SUCCESS; + continue; + } else if (unlikely(rc != MDBX_SUCCESS)) + break; + } + if (m3->mc_ki[mc->mc_top] >= ki || m3->mc_pg[mc->mc_top] != mp) { + if ((mc->mc_db->md_flags & MDBX_DUPSORT) != 0 && + (m3->mc_flags & C_EOF) == 0) { + MDBX_node *node = + page_node(m3->mc_pg[m3->mc_top], m3->mc_ki[m3->mc_top]); + /* If this node has dupdata, it may need to be reinited + * because its data has moved. + * If the xcursor was not initd it must be reinited. + * Else if node points to a subDB, nothing is needed. */ + if (node_flags(node) & F_DUPDATA) { + if (m3->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) { + if (!(node_flags(node) & F_SUBDATA)) + m3->mc_xcursor->mx_cursor.mc_pg[0] = node_data(node); + } else { + rc = mdbx_xcursor_init1(m3, node); + if (unlikely(rc != MDBX_SUCCESS)) + break; + m3->mc_xcursor->mx_cursor.mc_flags |= C_DEL; + } + } + } + } + } + } + + if (mc->mc_ki[mc->mc_top] >= nkeys) { + rc = mdbx_cursor_sibling(mc, true); + if (rc == MDBX_NOTFOUND) { + mc->mc_flags |= C_EOF; + rc = MDBX_SUCCESS; + } + } + if ((mc->mc_db->md_flags & MDBX_DUPSORT) != 0 && + (mc->mc_flags & C_EOF) == 0) { + MDBX_node *node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + /* If this node has dupdata, it may need to be reinited + * because its data has moved. + * If the xcursor was not initd it must be reinited. + * Else if node points to a subDB, nothing is needed. */ + if (node_flags(node) & F_DUPDATA) { + if (mc->mc_xcursor->mx_cursor.mc_flags & C_INITIALIZED) { + if (!(node_flags(node) & F_SUBDATA)) + mc->mc_xcursor->mx_cursor.mc_pg[0] = node_data(node); + } else { + rc = mdbx_xcursor_init1(mc, node); + if (likely(rc != MDBX_SUCCESS)) + mc->mc_xcursor->mx_cursor.mc_flags |= C_DEL; + } + } + } + mc->mc_flags |= C_DEL; + } + + if (unlikely(rc)) + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + else if (mdbx_audit_enabled()) + rc = mdbx_cursor_check(mc, false); + + return rc; +} + +int mdbx_del(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data) { + int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!key)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(txn->mt_flags & (MDBX_RDONLY | MDBX_TXN_BLOCKED))) + return (txn->mt_flags & MDBX_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; + + return mdbx_del0(txn, dbi, key, data, 0); +} + +static int mdbx_del0(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, + unsigned flags) { + MDBX_cursor_couple cx; + MDBX_cursor_op op; + MDBX_val rdata; + int rc, exact = 0; + DKBUF; + + mdbx_debug("====> delete db %u key [%s], data [%s]", dbi, DKEY(key), + DVAL(data)); + + rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (data) { + op = MDBX_GET_BOTH; + rdata = *data; + data = &rdata; + } else { + op = MDBX_SET; + flags |= MDBX_NODUPDATA; + } + rc = mdbx_cursor_set(&cx.outer, key, data, op, &exact); + if (likely(rc == MDBX_SUCCESS)) { + /* let mdbx_page_split know about this cursor if needed: + * delete will trigger a rebalance; if it needs to move + * a node from one page to another, it will have to + * update the parent's separator key(s). If the new sepkey + * is larger than the current one, the parent page may + * run out of space, triggering a split. We need this + * cursor to be consistent until the end of the rebalance. */ + cx.outer.mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = &cx.outer; + rc = mdbx_cursor_del(&cx.outer, flags); + txn->mt_cursors[dbi] = cx.outer.mc_next; + } + return rc; +} + +/* Split a page and insert a new node. + * Set MDBX_TXN_ERROR on failure. + * [in,out] mc Cursor pointing to the page and desired insertion index. + * The cursor will be updated to point to the actual page and index where + * the node got inserted after the split. + * [in] newkey The key for the newly inserted node. + * [in] newdata The data for the newly inserted node. + * [in] newpgno The page number, if the new node is a branch node. + * [in] nflags The NODE_ADD_FLAGS for the new node. + * Returns 0 on success, non-zero on failure. */ +static int mdbx_page_split(MDBX_cursor *mc, const MDBX_val *newkey, + MDBX_val *newdata, pgno_t newpgno, unsigned nflags) { + unsigned flags; + int rc = MDBX_SUCCESS, foliage = 0, did_split = 0; + pgno_t pgno = 0; + unsigned i, ptop; + MDBX_env *env = mc->mc_txn->mt_env; + MDBX_node *node; + MDBX_val sepkey, rkey, xdata; + MDBX_page *copy = NULL; + MDBX_page *rp, *pp; + MDBX_cursor mn; + DKBUF; + + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + unsigned newindx = mc->mc_ki[mc->mc_top]; + unsigned nkeys = page_numkeys(mp); + if (mdbx_audit_enabled()) { + rc = mdbx_cursor_check(mc, true); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + mdbx_debug("-----> splitting %s page %" PRIaPGNO + " and adding [%s] at index %i/%i", + IS_LEAF(mp) ? "leaf" : "branch", mp->mp_pgno, DKEY(newkey), + mc->mc_ki[mc->mc_top], nkeys); + + /* Create a right sibling. */ + if ((rc = mdbx_page_new(mc, mp->mp_flags, 1, &rp))) + return rc; + rp->mp_leaf2_ksize = mp->mp_leaf2_ksize; + mdbx_debug("new right sibling: page %" PRIaPGNO, rp->mp_pgno); + + /* Usually when splitting the root page, the cursor + * height is 1. But when called from mdbx_update_key, + * the cursor height may be greater because it walks + * up the stack while finding the branch slot to update. */ + if (mc->mc_top < 1) { + if ((rc = mdbx_page_new(mc, P_BRANCH, 1, &pp))) + goto done; + /* shift current top to make room for new parent */ + mdbx_cassert(mc, mc->mc_snum < 2 && mc->mc_db->md_depth > 0); + mc->mc_pg[2] = mc->mc_pg[1]; + mc->mc_ki[2] = mc->mc_ki[1]; + mc->mc_pg[1] = mc->mc_pg[0]; + mc->mc_ki[1] = mc->mc_ki[0]; + mc->mc_pg[0] = pp; + mc->mc_ki[0] = 0; + mc->mc_db->md_root = pp->mp_pgno; + mdbx_debug("root split! new root = %" PRIaPGNO, pp->mp_pgno); + foliage = mc->mc_db->md_depth++; + + /* Add left (implicit) pointer. */ + if (unlikely((rc = mdbx_node_add_branch(mc, 0, NULL, mp->mp_pgno)) != + MDBX_SUCCESS)) { + /* undo the pre-push */ + mc->mc_pg[0] = mc->mc_pg[1]; + mc->mc_ki[0] = mc->mc_ki[1]; + mc->mc_db->md_root = mp->mp_pgno; + mc->mc_db->md_depth--; + goto done; + } + mc->mc_snum++; + mc->mc_top++; + ptop = 0; + } else { + ptop = mc->mc_top - 1; + mdbx_debug("parent branch page is %" PRIaPGNO, mc->mc_pg[ptop]->mp_pgno); + } + + mdbx_cursor_copy(mc, &mn); + mn.mc_xcursor = NULL; + mn.mc_pg[mn.mc_top] = rp; + mn.mc_ki[mn.mc_top] = 0; + mn.mc_ki[ptop] = mc->mc_ki[ptop] + 1; + + unsigned split_indx; + if (nflags & MDBX_APPEND) { + mn.mc_ki[mn.mc_top] = 0; + sepkey = *newkey; + split_indx = newindx; + nkeys = 0; + } else { + split_indx = (nkeys + 1) / 2; + if (IS_LEAF2(rp)) { + char *split, *ins; + int x; + unsigned lsize, rsize, ksize; + /* Move half of the keys to the right sibling */ + x = mc->mc_ki[mc->mc_top] - split_indx; + ksize = mc->mc_db->md_xsize; + split = page_leaf2key(mp, split_indx, ksize); + rsize = (nkeys - split_indx) * ksize; + lsize = (nkeys - split_indx) * sizeof(indx_t); + mdbx_cassert(mc, mp->mp_lower >= lsize); + mp->mp_lower -= (indx_t)lsize; + mdbx_cassert(mc, rp->mp_lower + lsize <= UINT16_MAX); + rp->mp_lower += (indx_t)lsize; + mdbx_cassert(mc, mp->mp_upper + rsize - lsize <= UINT16_MAX); + mp->mp_upper += (indx_t)(rsize - lsize); + mdbx_cassert(mc, rp->mp_upper >= rsize - lsize); + rp->mp_upper -= (indx_t)(rsize - lsize); + sepkey.iov_len = ksize; + if (newindx == split_indx) { + sepkey.iov_base = newkey->iov_base; + } else { + sepkey.iov_base = split; + } + if (x < 0) { + mdbx_cassert(mc, ksize >= sizeof(indx_t)); + ins = page_leaf2key(mp, mc->mc_ki[mc->mc_top], ksize); + memcpy(rp->mp_ptrs, split, rsize); + sepkey.iov_base = rp->mp_ptrs; + memmove(ins + ksize, ins, (split_indx - mc->mc_ki[mc->mc_top]) * ksize); + memcpy(ins, newkey->iov_base, ksize); + mdbx_cassert(mc, UINT16_MAX - mp->mp_lower >= (int)sizeof(indx_t)); + mp->mp_lower += sizeof(indx_t); + mdbx_cassert(mc, mp->mp_upper >= ksize - sizeof(indx_t)); + mp->mp_upper -= (indx_t)(ksize - sizeof(indx_t)); + } else { + if (x) + memcpy(rp->mp_ptrs, split, x * ksize); + ins = page_leaf2key(rp, x, ksize); + memcpy(ins, newkey->iov_base, ksize); + memcpy(ins + ksize, split + x * ksize, rsize - x * ksize); + mdbx_cassert(mc, UINT16_MAX - rp->mp_lower >= (int)sizeof(indx_t)); + rp->mp_lower += sizeof(indx_t); + mdbx_cassert(mc, rp->mp_upper >= ksize - sizeof(indx_t)); + rp->mp_upper -= (indx_t)(ksize - sizeof(indx_t)); + mdbx_cassert(mc, x <= UINT16_MAX); + mc->mc_ki[mc->mc_top] = (indx_t)x; + } + } else { + size_t psize, nsize, k; + /* Maximum free space in an empty page */ + const unsigned pmax = page_space(env); + nsize = IS_LEAF(mp) ? leaf_size(env, newkey, newdata) + : branch_size(env, newkey); + + /* grab a page to hold a temporary copy */ + copy = mdbx_page_malloc(mc->mc_txn, 1); + if (unlikely(copy == NULL)) { + rc = MDBX_ENOMEM; + goto done; + } + copy->mp_pgno = mp->mp_pgno; + copy->mp_flags = mp->mp_flags; + copy->mp_lower = 0; + copy->mp_upper = (indx_t)page_space(env); + + /* prepare to insert */ + for (unsigned j = i = 0; i < nkeys; i++) { + if (i == newindx) + copy->mp_ptrs[j++] = 0; + copy->mp_ptrs[j++] = mp->mp_ptrs[i]; + } + + /* When items are relatively large the split point needs + * to be checked, because being off-by-one will make the + * difference between success or failure in mdbx_node_add. + * + * It's also relevant if a page happens to be laid out + * such that one half of its nodes are all "small" and + * the other half of its nodes are "large." If the new + * item is also "large" and falls on the half with + * "large" nodes, it also may not fit. + * + * As a final tweak, if the new item goes on the last + * spot on the page (and thus, onto the new page), bias + * the split so the new page is emptier than the old page. + * This yields better packing during sequential inserts. + */ + int dir; + if (nkeys < 32 || nsize > pmax / 16 || newindx >= nkeys) { + /* Find split point */ + psize = 0; + if (newindx <= split_indx || newindx >= nkeys) { + i = 0; + dir = 1; + k = (newindx >= nkeys) ? nkeys : split_indx + 1 + IS_LEAF(mp); + } else { + i = nkeys; + dir = -1; + k = split_indx - 1; + } + for (; i != k; i += dir) { + if (i == newindx) { + psize += nsize; + node = NULL; + } else { + node = (MDBX_node *)((char *)mp + copy->mp_ptrs[i] + PAGEHDRSZ); + psize += NODESIZE + node_ks(node) + sizeof(indx_t); + if (IS_LEAF(mp)) { + if (F_ISSET(node_flags(node), F_BIGDATA)) + psize += sizeof(pgno_t); + else + psize += node_ds(node); + } + psize = EVEN(psize); + } + if (psize > pmax || i == k - dir) { + split_indx = i + (dir < 0); + break; + } + } + } + if (split_indx == newindx) { + sepkey.iov_len = newkey->iov_len; + sepkey.iov_base = newkey->iov_base; + } else { + node = + (MDBX_node *)((char *)mp + copy->mp_ptrs[split_indx] + PAGEHDRSZ); + sepkey.iov_len = node_ks(node); + sepkey.iov_base = node_key(node); + } + } + } + + mdbx_debug("separator is %d [%s]", split_indx, DKEY(&sepkey)); + if (mdbx_audit_enabled()) { + rc = mdbx_cursor_check(mc, true); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + rc = mdbx_cursor_check(&mn, true); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + } + + /* Copy separator key to the parent. */ + if (page_room(mn.mc_pg[ptop]) < branch_size(env, &sepkey)) { + const int snum = mc->mc_snum; + const int depth = mc->mc_db->md_depth; + mn.mc_snum--; + mn.mc_top--; + did_split = 1; + /* We want other splits to find mn when doing fixups */ + WITH_CURSOR_TRACKING( + mn, rc = mdbx_page_split(&mn, &sepkey, NULL, rp->mp_pgno, 0)); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + mdbx_cassert(mc, mc->mc_snum - snum == mc->mc_db->md_depth - depth); + if (mdbx_audit_enabled()) { + rc = mdbx_cursor_check(mc, true); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + } + + /* root split? */ + ptop += mc->mc_snum - snum; + + /* Right page might now have changed parent. + * Check if left page also changed parent. */ + if (mn.mc_pg[ptop] != mc->mc_pg[ptop] && + mc->mc_ki[ptop] >= page_numkeys(mc->mc_pg[ptop])) { + for (i = 0; i < ptop; i++) { + mc->mc_pg[i] = mn.mc_pg[i]; + mc->mc_ki[i] = mn.mc_ki[i]; + } + mc->mc_pg[ptop] = mn.mc_pg[ptop]; + if (mn.mc_ki[ptop]) { + mc->mc_ki[ptop] = mn.mc_ki[ptop] - 1; + } else { + /* find right page's left sibling */ + mc->mc_ki[ptop] = mn.mc_ki[ptop]; + rc = mdbx_cursor_sibling(mc, false); + } + } + } else { + mn.mc_top--; + rc = mdbx_node_add_branch(&mn, mn.mc_ki[ptop], &sepkey, rp->mp_pgno); + mn.mc_top++; + } + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_NOTFOUND) /* improper mdbx_cursor_sibling() result */ { + mdbx_error("unexpected %s", "MDBX_NOTFOUND"); + rc = MDBX_PROBLEM; + } + goto done; + } + + if (nflags & MDBX_APPEND) { + mc->mc_pg[mc->mc_top] = rp; + mc->mc_ki[mc->mc_top] = 0; + switch (PAGETYPE(rp)) { + case P_BRANCH: { + mdbx_cassert(mc, (nflags & (F_BIGDATA | F_SUBDATA | F_DUPDATA)) == 0); + mdbx_cassert(mc, newpgno != 0 && newpgno != P_INVALID); + rc = mdbx_node_add_branch(mc, 0, newkey, newpgno); + } break; + case P_LEAF: { + mdbx_cassert(mc, newpgno == 0 || newpgno == P_INVALID); + rc = mdbx_node_add_leaf(mc, 0, newkey, newdata, nflags); + } break; + case P_LEAF | P_LEAF2: { + mdbx_cassert(mc, (nflags & (F_BIGDATA | F_SUBDATA | F_DUPDATA)) == 0); + mdbx_cassert(mc, newpgno == 0 || newpgno == P_INVALID); + rc = mdbx_node_add_leaf2(mc, 0, newkey); + } break; + default: + rc = MDBX_CORRUPTED; + } + if (rc) + goto done; + for (i = 0; i < mc->mc_top; i++) + mc->mc_ki[i] = mn.mc_ki[i]; + } else if (!IS_LEAF2(mp)) { + /* Move nodes */ + mc->mc_pg[mc->mc_top] = rp; + i = split_indx; + indx_t n = 0; + do { + MDBX_val *rdata = NULL; + if (i == newindx) { + rkey.iov_base = newkey->iov_base; + rkey.iov_len = newkey->iov_len; + if (IS_LEAF(mp)) { + rdata = newdata; + } else + pgno = newpgno; + flags = nflags; + /* Update index for the new key. */ + mc->mc_ki[mc->mc_top] = n; + } else { + node = (MDBX_node *)((char *)mp + copy->mp_ptrs[i] + PAGEHDRSZ); + rkey.iov_base = node_key(node); + rkey.iov_len = node_ks(node); + if (IS_LEAF(mp)) { + xdata.iov_base = node_data(node); + xdata.iov_len = node_ds(node); + rdata = &xdata; + } else + pgno = node_pgno(node); + flags = node_flags(node); + } + + switch (PAGETYPE(rp)) { + case P_BRANCH: { + mdbx_cassert(mc, 0 == (uint16_t)flags); + if (n == 0) { + /* First branch index doesn't need key data. */ + rkey.iov_len = 0; + } + rc = mdbx_node_add_branch(mc, n, &rkey, pgno); + } break; + case P_LEAF: { + mdbx_cassert(mc, pgno == 0); + mdbx_cassert(mc, rdata != NULL); + rc = mdbx_node_add_leaf(mc, n, &rkey, rdata, flags); + } break; + /* case P_LEAF | P_LEAF2: { + mdbx_cassert(mc, (nflags & (F_BIGDATA | F_SUBDATA | F_DUPDATA)) == 0); + mdbx_cassert(mc, gno == 0); + rc = mdbx_node_add_leaf2(mc, n, &rkey); + } break; */ + default: + rc = MDBX_CORRUPTED; + } + if (rc) + goto done; + + if (i == nkeys) { + i = 0; + n = 0; + mc->mc_pg[mc->mc_top] = copy; + } else { + i++; + n++; + } + } while (i != split_indx); + + nkeys = page_numkeys(copy); + for (i = 0; i < nkeys; i++) + mp->mp_ptrs[i] = copy->mp_ptrs[i]; + mp->mp_lower = copy->mp_lower; + mp->mp_upper = copy->mp_upper; + memcpy(page_node(mp, nkeys - 1), page_node(copy, nkeys - 1), + env->me_psize - copy->mp_upper - PAGEHDRSZ); + + /* reset back to original page */ + if (newindx < split_indx) { + mc->mc_pg[mc->mc_top] = mp; + } else { + mc->mc_pg[mc->mc_top] = rp; + mc->mc_ki[ptop]++; + /* Make sure mc_ki is still valid. */ + if (mn.mc_pg[ptop] != mc->mc_pg[ptop] && + mc->mc_ki[ptop] >= page_numkeys(mc->mc_pg[ptop])) { + for (i = 0; i <= ptop; i++) { + mc->mc_pg[i] = mn.mc_pg[i]; + mc->mc_ki[i] = mn.mc_ki[i]; + } + } + } + if (nflags & MDBX_RESERVE) { + node = page_node(mc->mc_pg[mc->mc_top], mc->mc_ki[mc->mc_top]); + if (!(node_flags(node) & F_BIGDATA)) + newdata->iov_base = node_data(node); + } + } else { + if (newindx >= split_indx) { + mc->mc_pg[mc->mc_top] = rp; + mc->mc_ki[ptop]++; + /* Make sure mc_ki is still valid. */ + if (mn.mc_pg[ptop] != mc->mc_pg[ptop] && + mc->mc_ki[ptop] >= page_numkeys(mc->mc_pg[ptop])) { + for (i = 0; i <= ptop; i++) { + mc->mc_pg[i] = mn.mc_pg[i]; + mc->mc_ki[i] = mn.mc_ki[i]; + } + } + } + } + + { + /* Adjust other cursors pointing to mp */ + MDBX_cursor *m2, *m3; + MDBX_dbi dbi = mc->mc_dbi; + nkeys = page_numkeys(mp); + + for (m2 = mc->mc_txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) { + m3 = (mc->mc_flags & C_SUB) ? &m2->mc_xcursor->mx_cursor : m2; + if (m3 == mc) + continue; + if (!(m2->mc_flags & m3->mc_flags & C_INITIALIZED)) + continue; + if (foliage) { + int k; + /* sub cursors may be on different DB */ + if (m3->mc_pg[0] != mp) + continue; + /* root split */ + for (k = foliage; k >= 0; k--) { + m3->mc_ki[k + 1] = m3->mc_ki[k]; + m3->mc_pg[k + 1] = m3->mc_pg[k]; + } + m3->mc_ki[0] = (m3->mc_ki[0] >= nkeys) ? 1 : 0; + m3->mc_pg[0] = mc->mc_pg[0]; + m3->mc_snum++; + m3->mc_top++; + } + if (m3->mc_top >= mc->mc_top && m3->mc_pg[mc->mc_top] == mp) { + if (m3->mc_ki[mc->mc_top] >= newindx && !(nflags & MDBX_SPLIT_REPLACE)) + m3->mc_ki[mc->mc_top]++; + if (m3->mc_ki[mc->mc_top] >= nkeys) { + m3->mc_pg[mc->mc_top] = rp; + mdbx_cassert(mc, m3->mc_ki[mc->mc_top] >= nkeys); + m3->mc_ki[mc->mc_top] -= (indx_t)nkeys; + for (i = 0; i < mc->mc_top; i++) { + m3->mc_ki[i] = mn.mc_ki[i]; + m3->mc_pg[i] = mn.mc_pg[i]; + } + } + } else if (!did_split && m3->mc_top >= ptop && + m3->mc_pg[ptop] == mc->mc_pg[ptop] && + m3->mc_ki[ptop] >= mc->mc_ki[ptop]) { + m3->mc_ki[ptop]++; + } + if (XCURSOR_INITED(m3) && IS_LEAF(mp)) + XCURSOR_REFRESH(m3, m3->mc_pg[mc->mc_top], m3->mc_ki[mc->mc_top]); + } + } + mdbx_debug("mp left: %d, rp left: %d", page_room(mp), page_room(rp)); + +done: + if (copy) /* tmp page */ + mdbx_dpage_free(env, copy, 1); + if (unlikely(rc)) + mc->mc_txn->mt_flags |= MDBX_TXN_ERROR; + return rc; +} + +int mdbx_put(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, + unsigned flags) { + int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!key || !data)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_RESERVE | + MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) + return MDBX_EINVAL; + + if (unlikely(txn->mt_flags & (MDBX_RDONLY | MDBX_TXN_BLOCKED))) + return (txn->mt_flags & MDBX_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; + + MDBX_cursor_couple cx; + rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + cx.outer.mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = &cx.outer; + + /* LY: support for update (explicit overwrite) */ + if (flags & MDBX_CURRENT) { + rc = mdbx_cursor_get(&cx.outer, key, NULL, MDBX_SET); + if (likely(rc == MDBX_SUCCESS) && + (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT)) { + /* LY: allows update (explicit overwrite) only for unique keys */ + MDBX_node *node = page_node(cx.outer.mc_pg[cx.outer.mc_top], + cx.outer.mc_ki[cx.outer.mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + mdbx_tassert(txn, XCURSOR_INITED(&cx.outer) && + cx.outer.mc_xcursor->mx_db.md_entries > 1); + rc = MDBX_EMULTIVAL; + } + } + } + + if (likely(rc == MDBX_SUCCESS)) + rc = mdbx_cursor_put(&cx.outer, key, data, flags); + txn->mt_cursors[dbi] = cx.outer.mc_next; + + return rc; +} + +/**** COPYING *****************************************************************/ + +#ifndef MDBX_WBUF +#define MDBX_WBUF ((size_t)1024 * 1024) +#endif +#define MDBX_EOF 0x10 /* mdbx_env_copyfd1() is done reading */ + +/* State needed for a double-buffering compacting copy. */ +typedef struct mdbx_copy { + MDBX_env *mc_env; + MDBX_txn *mc_txn; + mdbx_condmutex_t mc_condmutex; + uint8_t *mc_wbuf[2]; + uint8_t *mc_over[2]; + size_t mc_wlen[2]; + size_t mc_olen[2]; + mdbx_filehandle_t mc_fd; + volatile int mc_error; + pgno_t mc_next_pgno; + short mc_toggle; /* Buffer number in provider */ + short mc_new; /* (0-2 buffers to write) | (MDBX_EOF at end) */ + /* Error code. Never cleared if set. Both threads can set nonzero + * to fail the copy. Not mutex-protected, MDBX expects atomic int. */ +} mdbx_copy; + +/* Dedicated writer thread for compacting copy. */ +static THREAD_RESULT __cold THREAD_CALL mdbx_env_copythr(void *arg) { + mdbx_copy *my = arg; + uint8_t *ptr; + int toggle = 0; + int rc; + + mdbx_condmutex_lock(&my->mc_condmutex); + while (!my->mc_error) { + while (!my->mc_new) + mdbx_condmutex_wait(&my->mc_condmutex); + if (my->mc_new == 0 + MDBX_EOF) /* 0 buffers, just EOF */ + break; + size_t wsize = my->mc_wlen[toggle]; + ptr = my->mc_wbuf[toggle]; + again: + if (wsize > 0 && !my->mc_error) { + rc = mdbx_write(my->mc_fd, ptr, wsize); + if (rc != MDBX_SUCCESS) { + my->mc_error = rc; + break; + } + } + + /* If there's an overflow page tail, write it too */ + if (my->mc_olen[toggle]) { + wsize = my->mc_olen[toggle]; + ptr = my->mc_over[toggle]; + my->mc_olen[toggle] = 0; + goto again; + } + my->mc_wlen[toggle] = 0; + toggle ^= 1; + /* Return the empty buffer to provider */ + my->mc_new--; + mdbx_condmutex_signal(&my->mc_condmutex); + } + mdbx_condmutex_unlock(&my->mc_condmutex); + return (THREAD_RESULT)0; +} + +/* Give buffer and/or MDBX_EOF to writer thread, await unused buffer. + * + * [in] my control structure. + * [in] adjust (1 to hand off 1 buffer) | (MDBX_EOF when ending). */ +static int __cold mdbx_env_cthr_toggle(mdbx_copy *my, int adjust) { + mdbx_condmutex_lock(&my->mc_condmutex); + my->mc_new += (short)adjust; + mdbx_condmutex_signal(&my->mc_condmutex); + while (my->mc_new & 2) /* both buffers in use */ + mdbx_condmutex_wait(&my->mc_condmutex); + mdbx_condmutex_unlock(&my->mc_condmutex); + + my->mc_toggle ^= (adjust & 1); + /* Both threads reset mc_wlen, to be safe from threading errors */ + my->mc_wlen[my->mc_toggle] = 0; + return my->mc_error; +} + +/* Depth-first tree traversal for compacting copy. + * [in] my control structure. + * [in,out] pg database root. + * [in] flags includes F_DUPDATA if it is a sorted-duplicate sub-DB. */ +static int __cold mdbx_env_cwalk(mdbx_copy *my, pgno_t *pg, int flags) { + MDBX_cursor mc; + MDBX_page *mo, *mp, *leaf; + char *buf, *ptr; + int rc, toggle; + unsigned i; + + /* Empty DB, nothing to do */ + if (*pg == P_INVALID) + return MDBX_SUCCESS; + + memset(&mc, 0, sizeof(mc)); + mc.mc_snum = 1; + mc.mc_txn = my->mc_txn; + + rc = mdbx_page_get(&mc, *pg, &mc.mc_pg[0], NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_page_search_root(&mc, NULL, MDBX_PS_FIRST); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + /* Make cursor pages writable */ + buf = ptr = mdbx_malloc(pgno2bytes(my->mc_env, mc.mc_snum)); + if (buf == NULL) + return MDBX_ENOMEM; + + for (i = 0; i < mc.mc_top; i++) { + mdbx_page_copy((MDBX_page *)ptr, mc.mc_pg[i], my->mc_env->me_psize); + mc.mc_pg[i] = (MDBX_page *)ptr; + ptr += my->mc_env->me_psize; + } + + /* This is writable space for a leaf page. Usually not needed. */ + leaf = (MDBX_page *)ptr; + + toggle = my->mc_toggle; + while (mc.mc_snum > 0) { + unsigned n; + mp = mc.mc_pg[mc.mc_top]; + n = page_numkeys(mp); + + if (IS_LEAF(mp)) { + if (!IS_LEAF2(mp) && !(flags & F_DUPDATA)) { + for (i = 0; i < n; i++) { + MDBX_node *node = page_node(mp, i); + if (node_flags(node) & F_BIGDATA) { + MDBX_page *omp; + + /* Need writable leaf */ + if (mp != leaf) { + mc.mc_pg[mc.mc_top] = leaf; + mdbx_page_copy(leaf, mp, my->mc_env->me_psize); + mp = leaf; + node = page_node(mp, i); + } + + const pgno_t pgno = node_largedata_pgno(node); + poke_pgno(node_data(node), my->mc_next_pgno); + rc = mdbx_page_get(&mc, pgno, &omp, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + if (my->mc_wlen[toggle] >= MDBX_WBUF) { + rc = mdbx_env_cthr_toggle(my, 1); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + toggle = my->mc_toggle; + } + mo = (MDBX_page *)(my->mc_wbuf[toggle] + my->mc_wlen[toggle]); + memcpy(mo, omp, my->mc_env->me_psize); + mo->mp_pgno = my->mc_next_pgno; + my->mc_next_pgno += omp->mp_pages; + my->mc_wlen[toggle] += my->mc_env->me_psize; + if (omp->mp_pages > 1) { + my->mc_olen[toggle] = pgno2bytes(my->mc_env, omp->mp_pages - 1); + my->mc_over[toggle] = (uint8_t *)omp + my->mc_env->me_psize; + rc = mdbx_env_cthr_toggle(my, 1); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + toggle = my->mc_toggle; + } + } else if (node_flags(node) & F_SUBDATA) { + if (node_ds(node) < sizeof(MDBX_db)) { + rc = MDBX_CORRUPTED; + goto done; + } + + /* Need writable leaf */ + if (mp != leaf) { + mc.mc_pg[mc.mc_top] = leaf; + mdbx_page_copy(leaf, mp, my->mc_env->me_psize); + mp = leaf; + node = page_node(mp, i); + } + + MDBX_db db; + memcpy(&db, node_data(node), sizeof(MDBX_db)); + my->mc_toggle = (short)toggle; + rc = mdbx_env_cwalk(my, &db.md_root, node_flags(node) & F_DUPDATA); + if (rc) + goto done; + toggle = my->mc_toggle; + memcpy(node_data(node), &db, sizeof(MDBX_db)); + } + } + } + } else { + mc.mc_ki[mc.mc_top]++; + if (mc.mc_ki[mc.mc_top] < n) { + again: + rc = mdbx_page_get(&mc, node_pgno(page_node(mp, mc.mc_ki[mc.mc_top])), + &mp, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + mc.mc_top++; + mc.mc_snum++; + mc.mc_ki[mc.mc_top] = 0; + if (IS_BRANCH(mp)) { + /* Whenever we advance to a sibling branch page, + * we must proceed all the way down to its first leaf. */ + mdbx_page_copy(mc.mc_pg[mc.mc_top], mp, my->mc_env->me_psize); + goto again; + } else + mc.mc_pg[mc.mc_top] = mp; + continue; + } + } + if (my->mc_wlen[toggle] >= MDBX_WBUF) { + rc = mdbx_env_cthr_toggle(my, 1); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + toggle = my->mc_toggle; + } + mo = (MDBX_page *)(my->mc_wbuf[toggle] + my->mc_wlen[toggle]); + mdbx_page_copy(mo, mp, my->mc_env->me_psize); + mo->mp_pgno = my->mc_next_pgno++; + my->mc_wlen[toggle] += my->mc_env->me_psize; + if (mc.mc_top) { + /* Update parent if there is one */ + node_set_pgno(page_node(mc.mc_pg[mc.mc_top - 1], mc.mc_ki[mc.mc_top - 1]), + mo->mp_pgno); + mdbx_cursor_pop(&mc); + } else { + /* Otherwise we're done */ + *pg = mo->mp_pgno; + break; + } + } +done: + mdbx_free(buf); + return rc; +} + +static __cold void compact_fixup_meta(MDBX_env *env, MDBX_meta *meta) { + /* Calculate filesize taking in account shrink/growing thresholds */ + if (meta->mm_geo.next > meta->mm_geo.now) { + const pgno_t aligned = pgno_align2os_pgno( + env, + pgno_add(meta->mm_geo.next, + meta->mm_geo.grow - meta->mm_geo.next % meta->mm_geo.grow)); + meta->mm_geo.now = aligned; + } else if (meta->mm_geo.next < meta->mm_geo.now) { + meta->mm_geo.now = meta->mm_geo.next; + const pgno_t aligner = + meta->mm_geo.grow ? meta->mm_geo.grow : meta->mm_geo.shrink; + const pgno_t aligned = pgno_align2os_pgno( + env, meta->mm_geo.next + aligner - meta->mm_geo.next % aligner); + meta->mm_geo.now = aligned; + } + + if (meta->mm_geo.now < meta->mm_geo.lower) + meta->mm_geo.now = meta->mm_geo.lower; + if (meta->mm_geo.now > meta->mm_geo.upper) + meta->mm_geo.now = meta->mm_geo.upper; + + /* Update signature */ + assert(meta->mm_geo.now >= meta->mm_geo.next); + meta->mm_datasync_sign = mdbx_meta_sign(meta); +} + +/* Copy environment with compaction. */ +static int __cold mdbx_env_compact(MDBX_env *env, MDBX_txn *read_txn, + mdbx_filehandle_t fd, uint8_t *buffer, + const bool dest_is_pipe) { + const size_t meta_bytes = pgno2bytes(env, NUM_METAS); + uint8_t *const data_buffer = + buffer + roundup_powerof2(meta_bytes, env->me_os_psize); + MDBX_meta *const meta = mdbx_init_metas(env, buffer); + /* copy canary sequenses if present */ + if (read_txn->mt_canary.v) { + meta->mm_canary = read_txn->mt_canary; + meta->mm_canary.v = mdbx_meta_txnid_stable(env, meta); + } + + /* Set metapage 1 with current main DB */ + pgno_t new_root, root = read_txn->mt_dbs[MAIN_DBI].md_root; + if ((new_root = root) == P_INVALID) { + /* When the DB is empty, handle it specially to + * fix any breakage like page leaks from ITS#8174. */ + meta->mm_dbs[MAIN_DBI].md_flags = read_txn->mt_dbs[MAIN_DBI].md_flags; + compact_fixup_meta(env, meta); + if (dest_is_pipe) { + int rc = mdbx_write(fd, buffer, meta_bytes); + if (rc != MDBX_SUCCESS) + return rc; + } + } else { + /* Count free pages + GC pages. Subtract from last_pg + * to find the new last_pg, which also becomes the new root. */ + pgno_t freecount = 0; + MDBX_cursor mc; + MDBX_val key, data; + + int rc = mdbx_cursor_init(&mc, read_txn, FREE_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + while ((rc = mdbx_cursor_get(&mc, &key, &data, MDBX_NEXT)) == 0) + freecount += *(pgno_t *)data.iov_base; + if (unlikely(rc != MDBX_NOTFOUND)) + return rc; + + freecount += read_txn->mt_dbs[FREE_DBI].md_branch_pages + + read_txn->mt_dbs[FREE_DBI].md_leaf_pages + + read_txn->mt_dbs[FREE_DBI].md_overflow_pages; + + new_root = read_txn->mt_next_pgno - 1 - freecount; + meta->mm_geo.next = new_root + 1; + meta->mm_dbs[MAIN_DBI] = read_txn->mt_dbs[MAIN_DBI]; + meta->mm_dbs[MAIN_DBI].md_root = new_root; + + mdbx_copy ctx; + memset(&ctx, 0, sizeof(ctx)); + rc = mdbx_condmutex_init(&ctx.mc_condmutex); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + memset(data_buffer, 0, MDBX_WBUF * 2); + ctx.mc_wbuf[0] = data_buffer; + ctx.mc_wbuf[1] = data_buffer + MDBX_WBUF; + ctx.mc_next_pgno = NUM_METAS; + ctx.mc_env = env; + ctx.mc_fd = fd; + ctx.mc_txn = read_txn; + + mdbx_thread_t thread; + int thread_err = mdbx_thread_create(&thread, mdbx_env_copythr, &ctx); + if (likely(thread_err == MDBX_SUCCESS)) { + if (dest_is_pipe) { + compact_fixup_meta(env, meta); + rc = mdbx_write(fd, buffer, meta_bytes); + } + if (rc == MDBX_SUCCESS) + rc = mdbx_env_cwalk(&ctx, &root, 0); + mdbx_env_cthr_toggle(&ctx, 1 | MDBX_EOF); + thread_err = mdbx_thread_join(thread); + mdbx_condmutex_destroy(&ctx.mc_condmutex); + } + if (unlikely(thread_err != MDBX_SUCCESS)) + return thread_err; + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + if (unlikely(ctx.mc_error != MDBX_SUCCESS)) + return ctx.mc_error; + + if (dest_is_pipe) { + if (root != new_root) { + mdbx_error("post-compactification root %" PRIaPGNO + " NE expected %" PRIaPGNO + " (source DB corrupted or has a page leak(s))", + root, new_root); + return MDBX_CORRUPTED; /* page leak or corrupt DB */ + } + } else { + if (root > new_root) { + mdbx_error("post-compactification root %" PRIaPGNO + " GT expected %" PRIaPGNO " (source DB corrupted)", + root, new_root); + return MDBX_CORRUPTED; /* page leak or corrupt DB */ + } + if (root < new_root) { + mdbx_notice("post-compactification root %" PRIaPGNO + " LT expected %" PRIaPGNO " (page leak(s) in source DB)", + root, new_root); + /* fixup meta */ + meta->mm_dbs[MAIN_DBI].md_root = root; + meta->mm_geo.next = root + 1; + } + compact_fixup_meta(env, meta); + } + } + + /* Extend file if required */ + if (meta->mm_geo.now != meta->mm_geo.next) { + const size_t whole_size = pgno2bytes(env, meta->mm_geo.now); + if (!dest_is_pipe) + return mdbx_ftruncate(fd, whole_size); + + const size_t used_size = pgno2bytes(env, meta->mm_geo.next); + memset(data_buffer, 0, MDBX_WBUF); + for (size_t offset = used_size; offset < whole_size;) { + const size_t chunk = + (MDBX_WBUF < whole_size - offset) ? MDBX_WBUF : whole_size - offset; + /* copy to avoit EFAULT in case swapped-out */ + int rc = mdbx_write(fd, data_buffer, chunk); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + offset += chunk; + } + } + return MDBX_SUCCESS; +} + +/* Copy environment as-is. */ +static int __cold mdbx_env_copy_asis(MDBX_env *env, MDBX_txn *read_txn, + mdbx_filehandle_t fd, uint8_t *buffer, + const bool dest_is_pipe) { + /* We must start the actual read txn after blocking writers */ + int rc = mdbx_txn_end(read_txn, MDBX_END_RESET_TMP); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + /* Temporarily block writers until we snapshot the meta pages */ + rc = mdbx_txn_lock(env, false); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + rc = mdbx_txn_renew0(read_txn, MDBX_RDONLY); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_txn_unlock(env); + return rc; + } + + mdbx_jitter4testing(false); + const size_t meta_bytes = pgno2bytes(env, NUM_METAS); + /* Make a snapshot of meta-pages, + * but writing ones after the data was flushed */ + memcpy(buffer, env->me_map, meta_bytes); + MDBX_meta *const headcopy = /* LY: get pointer to the spanshot copy */ + (MDBX_meta *)(buffer + ((uint8_t *)mdbx_meta_head(env) - env->me_map)); + /* Update signature to steady */ + headcopy->mm_datasync_sign = mdbx_meta_sign(headcopy); + mdbx_txn_unlock(env); + + /* Copy the data */ + const size_t whole_size = pgno_align2os_bytes(env, read_txn->mt_end_pgno); + const size_t used_size = pgno2bytes(env, read_txn->mt_next_pgno); + mdbx_jitter4testing(false); + + if (dest_is_pipe) + rc = mdbx_write(fd, buffer, meta_bytes); + + uint8_t *const data_buffer = + buffer + roundup_powerof2(meta_bytes, env->me_os_psize); + for (size_t offset = meta_bytes; rc == MDBX_SUCCESS && offset < used_size;) { + if (dest_is_pipe) { +#if defined(__linux__) || defined(__gnu_linux__) + off_t in_offset = offset; + const intptr_t written = + sendfile(fd, env->me_fd, &in_offset, used_size - offset); + if (unlikely(written <= 0)) { + rc = written ? errno : MDBX_ENODATA; + break; + } + offset = in_offset; + continue; +#endif + } else { +#if __GLIBC_PREREQ(2, 27) && defined(_GNU_SOURCE) + off_t in_offset = offset, out_offset = offset; + ssize_t bytes_copied = copy_file_range( + env->me_fd, &in_offset, fd, &out_offset, used_size - offset, 0); + if (unlikely(bytes_copied <= 0)) { + rc = bytes_copied ? errno : MDBX_ENODATA; + break; + } + offset = in_offset; + continue; +#endif + } + + /* fallback to portable */ + const size_t chunk = + (MDBX_WBUF < used_size - offset) ? MDBX_WBUF : used_size - offset; + /* copy to avoit EFAULT in case swapped-out */ + memcpy(data_buffer, env->me_map + offset, chunk); + rc = mdbx_write(fd, data_buffer, chunk); + offset += chunk; + } + + /* Extend file if required */ + if (likely(rc == MDBX_SUCCESS) && whole_size != used_size) { + if (!dest_is_pipe) + rc = mdbx_ftruncate(fd, whole_size); + else { + memset(data_buffer, 0, MDBX_WBUF); + for (size_t offset = used_size; + rc == MDBX_SUCCESS && offset < whole_size;) { + const size_t chunk = + (MDBX_WBUF < whole_size - offset) ? MDBX_WBUF : whole_size - offset; + /* copy to avoit EFAULT in case swapped-out */ + rc = mdbx_write(fd, data_buffer, chunk); + offset += chunk; + } + } + } + + return rc; +} + +int __cold mdbx_env_copy2fd(MDBX_env *env, mdbx_filehandle_t fd, + unsigned flags) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + const int dest_is_pipe = mdbx_is_pipe(fd); + if (MDBX_IS_ERROR(dest_is_pipe)) + return dest_is_pipe; + + if (!dest_is_pipe) { + int rc = mdbx_fseek(fd, 0); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + const size_t buffer_size = + pgno_align2os_bytes(env, NUM_METAS) + + roundup_powerof2(((flags & MDBX_CP_COMPACT) ? MDBX_WBUF * 2 : MDBX_WBUF), + env->me_os_psize); + + uint8_t *buffer = NULL; + int rc = mdbx_memalign_alloc(env->me_os_psize, buffer_size, (void **)&buffer); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + MDBX_txn *read_txn = NULL; + /* Do the lock/unlock of the reader mutex before starting the + * write txn. Otherwise other read txns could block writers. */ + rc = mdbx_txn_begin(env, NULL, MDBX_RDONLY, &read_txn); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_memalign_free(buffer); + return rc; + } + + if (!dest_is_pipe) { + /* Firstly write a stub to meta-pages. + * Now we sure to incomplete copy will not be used. */ + memset(buffer, -1, pgno2bytes(env, NUM_METAS)); + rc = mdbx_write(fd, buffer, pgno2bytes(env, NUM_METAS)); + } + + if (likely(rc == MDBX_SUCCESS)) { + memset(buffer, 0, pgno2bytes(env, NUM_METAS)); + rc = (flags & MDBX_CP_COMPACT) + ? mdbx_env_compact(env, read_txn, fd, buffer, dest_is_pipe) + : mdbx_env_copy_asis(env, read_txn, fd, buffer, dest_is_pipe); + } + mdbx_txn_abort(read_txn); + + if (!dest_is_pipe) { + if (likely(rc == MDBX_SUCCESS)) + rc = mdbx_filesync(fd, MDBX_SYNC_DATA | MDBX_SYNC_SIZE | MDBX_SYNC_IODQ); + + /* Write actual meta */ + if (likely(rc == MDBX_SUCCESS)) + rc = mdbx_pwrite(fd, buffer, pgno2bytes(env, NUM_METAS), 0); + + if (likely(rc == MDBX_SUCCESS)) + rc = mdbx_filesync(fd, MDBX_SYNC_DATA | MDBX_SYNC_IODQ); + } + + mdbx_memalign_free(buffer); + return rc; +} + +int __cold mdbx_env_copy(MDBX_env *env, const char *dest_path, unsigned flags) { + if (unlikely(!env || !dest_path)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + char *dxb_pathname; + mdbx_filehandle_t newfd = INVALID_HANDLE_VALUE; + + if (env->me_flags & MDBX_NOSUBDIR) { + dxb_pathname = (char *)dest_path; + } else { + size_t len = strlen(dest_path); + len += sizeof(MDBX_DATANAME); + dxb_pathname = mdbx_malloc(len); + if (!dxb_pathname) + return MDBX_ENOMEM; + sprintf(dxb_pathname, "%s" MDBX_DATANAME, dest_path); + } + + /* The destination path must exist, but the destination file must not. + * We don't want the OS to cache the writes, since the source data is + * already in the OS cache. */ + int rc = mdbx_openfile(dxb_pathname, O_WRONLY | O_CREAT | O_EXCL, 0640, + &newfd, true); + if (rc == MDBX_SUCCESS) { + if (env->me_psize >= env->me_os_psize) { +#ifdef F_NOCACHE /* __APPLE__ */ + (void)fcntl(newfd, F_NOCACHE, 1); +#elif defined(O_DIRECT) && defined(F_GETFL) + /* Set O_DIRECT if the file system supports it */ + if ((rc = fcntl(newfd, F_GETFL)) != -1) + (void)fcntl(newfd, F_SETFL, rc | O_DIRECT); +#endif + } + rc = mdbx_env_copy2fd(env, newfd, flags); + } + + if (newfd != INVALID_HANDLE_VALUE) { + int err = mdbx_closefile(newfd); + if (rc == MDBX_SUCCESS && err != rc) + rc = err; + if (rc != MDBX_SUCCESS) + (void)mdbx_removefile(dxb_pathname); + } + + if (dxb_pathname != dest_path) + mdbx_free(dxb_pathname); + + return rc; +} + +/******************************************************************************/ + +int __cold mdbx_env_set_flags(MDBX_env *env, unsigned flags, int onoff) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(flags & ~CHANGEABLE)) + return MDBX_EPERM; + + if (unlikely(env->me_flags & MDBX_RDONLY)) + return MDBX_EACCESS; + + if (unlikely(env->me_txn0->mt_owner == mdbx_thread_self())) + return MDBX_BUSY; + + int rc = mdbx_txn_lock(env, false); + if (unlikely(rc)) + return rc; + + if (onoff) + env->me_flags |= flags; + else + env->me_flags &= ~flags; + + mdbx_txn_unlock(env); + return MDBX_SUCCESS; +} + +int __cold mdbx_env_get_flags(MDBX_env *env, unsigned *arg) { + if (unlikely(!env || !arg)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + *arg = env->me_flags & (CHANGEABLE | CHANGELESS); + return MDBX_SUCCESS; +} + +int __cold mdbx_env_set_userctx(MDBX_env *env, void *ctx) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + env->me_userctx = ctx; + return MDBX_SUCCESS; +} + +void *__cold mdbx_env_get_userctx(MDBX_env *env) { + return env ? env->me_userctx : NULL; +} + +int __cold mdbx_env_set_assert(MDBX_env *env, MDBX_assert_func *func) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + +#if MDBX_DEBUG + env->me_assert_func = func; + return MDBX_SUCCESS; +#else + (void)func; + return MDBX_ENOSYS; +#endif +} + +int __cold mdbx_env_get_path(MDBX_env *env, const char **arg) { + if (unlikely(!env || !arg)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + *arg = env->me_path; + return MDBX_SUCCESS; +} + +int __cold mdbx_env_get_fd(MDBX_env *env, mdbx_filehandle_t *arg) { + if (unlikely(!env || !arg)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + *arg = env->me_fd; + return MDBX_SUCCESS; +} + +/* Common code for mdbx_dbi_stat() and mdbx_env_stat(). + * [in] env the environment to operate in. + * [in] db the MDBX_db record containing the stats to return. + * [out] arg the address of an MDBX_stat structure to receive the stats. + * Returns 0, this function always succeeds. */ +static void mdbx_stat0(const MDBX_env *env, const MDBX_db *db, MDBX_stat *dest, + size_t bytes) { + dest->ms_psize = env->me_psize; + dest->ms_depth = db->md_depth; + dest->ms_branch_pages = db->md_branch_pages; + dest->ms_leaf_pages = db->md_leaf_pages; + dest->ms_overflow_pages = db->md_overflow_pages; + dest->ms_entries = db->md_entries; + if (likely(bytes >= + offsetof(MDBX_stat, ms_mod_txnid) + sizeof(dest->ms_mod_txnid))) + dest->ms_mod_txnid = db->md_mod_txnid; +} + +int __cold mdbx_env_stat(MDBX_env *env, MDBX_stat *dest, size_t bytes) { + return mdbx_env_stat_ex(env, NULL, dest, bytes); +} + +int __cold mdbx_env_stat_ex(const MDBX_env *env, const MDBX_txn *txn, + MDBX_stat *dest, size_t bytes) { + if (unlikely((env == NULL && txn == NULL) || dest == NULL)) + return MDBX_EINVAL; + + if (txn) { + int err = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + if (env) { + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + if (txn && unlikely(txn->mt_env != env)) + return MDBX_EINVAL; + } + + const size_t size_before_modtxnid = offsetof(MDBX_stat, ms_mod_txnid); + if (unlikely(bytes != sizeof(MDBX_stat)) && bytes != size_before_modtxnid) + return MDBX_EINVAL; + + if (txn) { + mdbx_stat0(txn->mt_env, &txn->mt_dbs[MAIN_DBI], dest, bytes); + return MDBX_SUCCESS; + } + + while (1) { + const MDBX_meta *const recent_meta = mdbx_meta_head(env); + const txnid_t txnid = mdbx_meta_txnid_fluid(env, recent_meta); + mdbx_stat0(env, &recent_meta->mm_dbs[MAIN_DBI], dest, bytes); + mdbx_compiler_barrier(); + if (likely(txnid == mdbx_meta_txnid_fluid(env, recent_meta) && + recent_meta == mdbx_meta_head(env))) + return MDBX_SUCCESS; + } +} + +int __cold mdbx_env_info(MDBX_env *env, MDBX_envinfo *arg, size_t bytes) { + return mdbx_env_info_ex(env, NULL, arg, bytes); +} + +int __cold mdbx_env_info_ex(const MDBX_env *env, const MDBX_txn *txn, + MDBX_envinfo *arg, size_t bytes) { + if (unlikely((env == NULL && txn == NULL) || arg == NULL)) + return MDBX_EINVAL; + + if (txn) { + int err = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(err != MDBX_SUCCESS)) + return err; + } + if (env) { + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + if (txn && unlikely(txn->mt_env != env)) + return MDBX_EINVAL; + } else { + env = txn->mt_env; + } + + const size_t size_before_bootid = offsetof(MDBX_envinfo, mi_bootid); + if (unlikely(bytes != sizeof(MDBX_envinfo)) && bytes != size_before_bootid) + return MDBX_EINVAL; + + const MDBX_meta *const meta0 = METAPAGE(env, 0); + const MDBX_meta *const meta1 = METAPAGE(env, 1); + const MDBX_meta *const meta2 = METAPAGE(env, 2); + pgno_t unsynced_pages; + while (1) { + if (unlikely(env->me_flags & MDBX_FATAL_ERROR)) + return MDBX_PANIC; + + const MDBX_meta *const recent_meta = mdbx_meta_head(env); + arg->mi_recent_txnid = mdbx_meta_txnid_fluid(env, recent_meta); + arg->mi_meta0_txnid = mdbx_meta_txnid_fluid(env, meta0); + arg->mi_meta0_sign = meta0->mm_datasync_sign; + arg->mi_meta1_txnid = mdbx_meta_txnid_fluid(env, meta1); + arg->mi_meta1_sign = meta1->mm_datasync_sign; + arg->mi_meta2_txnid = mdbx_meta_txnid_fluid(env, meta2); + arg->mi_meta2_sign = meta2->mm_datasync_sign; + + const MDBX_meta *txn_meta = recent_meta; + arg->mi_last_pgno = txn_meta->mm_geo.next - 1; + arg->mi_geo.current = pgno2bytes(env, txn_meta->mm_geo.now); + if (txn) { + arg->mi_last_pgno = txn->mt_next_pgno - 1; + arg->mi_geo.current = pgno2bytes(env, txn->mt_end_pgno); + + const txnid_t wanna_meta_txnid = (txn->mt_flags & MDBX_RDONLY) + ? txn->mt_txnid + : txn->mt_txnid - MDBX_TXNID_STEP; + txn_meta = (arg->mi_meta0_txnid == wanna_meta_txnid) ? meta0 : txn_meta; + txn_meta = (arg->mi_meta1_txnid == wanna_meta_txnid) ? meta1 : txn_meta; + txn_meta = (arg->mi_meta2_txnid == wanna_meta_txnid) ? meta2 : txn_meta; + } + arg->mi_geo.lower = pgno2bytes(env, txn_meta->mm_geo.lower); + arg->mi_geo.upper = pgno2bytes(env, txn_meta->mm_geo.upper); + arg->mi_geo.shrink = pgno2bytes(env, txn_meta->mm_geo.shrink); + arg->mi_geo.grow = pgno2bytes(env, txn_meta->mm_geo.grow); + unsynced_pages = *env->me_unsynced_pages + + (*env->me_meta_sync_txnid != (uint32_t)arg->mi_last_pgno); + + arg->mi_mapsize = env->me_dxb_mmap.limit; + mdbx_compiler_barrier(); + if (likely(arg->mi_meta0_txnid == mdbx_meta_txnid_fluid(env, meta0) && + arg->mi_meta0_sign == meta0->mm_datasync_sign && + arg->mi_meta1_txnid == mdbx_meta_txnid_fluid(env, meta1) && + arg->mi_meta1_sign == meta1->mm_datasync_sign && + arg->mi_meta2_txnid == mdbx_meta_txnid_fluid(env, meta2) && + arg->mi_meta2_sign == meta2->mm_datasync_sign && + recent_meta == mdbx_meta_head(env) && + arg->mi_recent_txnid == mdbx_meta_txnid_fluid(env, recent_meta))) + break; + } + + arg->mi_maxreaders = env->me_maxreaders; + arg->mi_numreaders = env->me_lck ? env->me_lck->mti_numreaders : INT32_MAX; + arg->mi_dxb_pagesize = env->me_psize; + arg->mi_sys_pagesize = env->me_os_psize; + + const MDBX_lockinfo *const lck = env->me_lck; + if (likely(bytes > size_before_bootid)) { + arg->mi_unsync_volume = pgno2bytes(env, unsynced_pages); + const uint64_t monotime_now = mdbx_osal_monotime(); + arg->mi_since_sync_seconds16dot16 = + mdbx_osal_monotime_to_16dot16(monotime_now - *env->me_sync_timestamp); + arg->mi_since_reader_check_seconds16dot16 = + lck ? mdbx_osal_monotime_to_16dot16(monotime_now - + lck->mti_reader_check_timestamp) + : 0; + arg->mi_autosync_threshold = pgno2bytes(env, *env->me_autosync_threshold); + arg->mi_autosync_period_seconds16dot16 = + mdbx_osal_monotime_to_16dot16(*env->me_autosync_period); + arg->mi_bootid[0] = lck ? lck->mti_bootid.x : 0; + arg->mi_bootid[1] = lck ? lck->mti_bootid.y : 0; + arg->mi_mode = lck ? lck->mti_envmode : env->me_flags; + } + + arg->mi_self_latter_reader_txnid = arg->mi_latter_reader_txnid = 0; + if (lck) { + arg->mi_self_latter_reader_txnid = arg->mi_latter_reader_txnid = + arg->mi_recent_txnid; + for (unsigned i = 0; i < arg->mi_numreaders; ++i) { + const uint32_t pid = lck->mti_readers[i].mr_pid; + if (pid) { + const txnid_t txnid = safe64_read(&lck->mti_readers[i].mr_txnid); + if (arg->mi_latter_reader_txnid > txnid) + arg->mi_latter_reader_txnid = txnid; + if (pid == env->me_pid && arg->mi_self_latter_reader_txnid > txnid) + arg->mi_self_latter_reader_txnid = txnid; + } + } + } + + return MDBX_SUCCESS; +} + +static MDBX_cmp_func *mdbx_default_keycmp(unsigned flags) { + return (flags & MDBX_REVERSEKEY) + ? mdbx_cmp_memnr + : (flags & MDBX_INTEGERKEY) ? mdbx_cmp_int_align2 : mdbx_cmp_memn; +} + +static MDBX_cmp_func *mdbx_default_datacmp(unsigned flags) { + return !(flags & MDBX_DUPSORT) + ? mdbx_cmp_memn + : ((flags & MDBX_INTEGERDUP) + ? mdbx_cmp_int_unaligned + : ((flags & MDBX_REVERSEDUP) ? mdbx_cmp_memnr + : mdbx_cmp_memn)); +} + +static int mdbx_dbi_bind(MDBX_txn *txn, const MDBX_dbi dbi, unsigned user_flags, + MDBX_cmp_func *keycmp, MDBX_cmp_func *datacmp) { + /* LY: so, accepting only three cases for the table's flags: + * 1) user_flags and both comparators are zero + * = assume that a by-default mode/flags is requested for reading; + * 2) user_flags exactly the same + * = assume that the target mode/flags are requested properly; + * 3) user_flags differs, but table is empty and MDBX_CREATE is provided + * = assume that a properly create request with custom flags; + */ + if ((user_flags ^ txn->mt_dbs[dbi].md_flags) & PERSISTENT_FLAGS) { + /* flags ara differs, check other conditions */ + if (!user_flags && (!keycmp || keycmp == txn->mt_dbxs[dbi].md_cmp) && + (!datacmp || datacmp == txn->mt_dbxs[dbi].md_dcmp)) { + /* no comparators were provided and flags are zero, + * seems that is case #1 above */ + user_flags = txn->mt_dbs[dbi].md_flags; + } else if ((user_flags & MDBX_CREATE) && txn->mt_dbs[dbi].md_entries == 0) { + if (txn->mt_flags & MDBX_RDONLY) + return /* FIXME: return extended info */ MDBX_EACCESS; + /* make sure flags changes get committed */ + txn->mt_dbs[dbi].md_flags = user_flags & PERSISTENT_FLAGS; + txn->mt_flags |= MDBX_TXN_DIRTY; + } else { + return /* FIXME: return extended info */ MDBX_INCOMPATIBLE; + } + } + + if (!txn->mt_dbxs[dbi].md_cmp || MDBX_DEBUG) { + if (!keycmp) + keycmp = mdbx_default_keycmp(user_flags); + mdbx_tassert(txn, !txn->mt_dbxs[dbi].md_cmp || + txn->mt_dbxs[dbi].md_cmp == keycmp); + txn->mt_dbxs[dbi].md_cmp = keycmp; + } + + if (!txn->mt_dbxs[dbi].md_dcmp || MDBX_DEBUG) { + if (!datacmp) + datacmp = mdbx_default_datacmp(user_flags); + mdbx_tassert(txn, !txn->mt_dbxs[dbi].md_dcmp || + txn->mt_dbxs[dbi].md_dcmp == datacmp); + txn->mt_dbxs[dbi].md_dcmp = datacmp; + } + + return MDBX_SUCCESS; +} + +int mdbx_dbi_open_ex(MDBX_txn *txn, const char *table_name, unsigned user_flags, + MDBX_dbi *dbi, MDBX_cmp_func *keycmp, + MDBX_cmp_func *datacmp) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!dbi || (user_flags & ~VALID_FLAGS) != 0)) + return MDBX_EINVAL; + + switch (user_flags & + (MDBX_INTEGERDUP | MDBX_DUPFIXED | MDBX_DUPSORT | MDBX_REVERSEDUP)) { + default: + return MDBX_EINVAL; + case MDBX_DUPSORT: + case MDBX_DUPSORT | MDBX_REVERSEDUP: + case MDBX_DUPSORT | MDBX_DUPFIXED: + case MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_REVERSEDUP: + case MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP: + case MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP: + case 0: + break; + } + + /* main table? */ + if (!table_name) { + *dbi = MAIN_DBI; + return mdbx_dbi_bind(txn, MAIN_DBI, user_flags, keycmp, datacmp); + } + + if (txn->mt_dbxs[MAIN_DBI].md_cmp == NULL) { + txn->mt_dbxs[MAIN_DBI].md_cmp = + mdbx_default_keycmp(txn->mt_dbs[MAIN_DBI].md_flags); + txn->mt_dbxs[MAIN_DBI].md_dcmp = + mdbx_default_datacmp(txn->mt_dbs[MAIN_DBI].md_flags); + } + + /* Is the DB already open? */ + size_t len = strlen(table_name); + MDBX_dbi scan, slot; + for (slot = scan = txn->mt_numdbs; --scan >= CORE_DBS;) { + if (!txn->mt_dbxs[scan].md_name.iov_len) { + /* Remember this free slot */ + slot = scan; + continue; + } + if (len == txn->mt_dbxs[scan].md_name.iov_len && + !strncmp(table_name, txn->mt_dbxs[scan].md_name.iov_base, len)) { + *dbi = scan; + return mdbx_dbi_bind(txn, scan, user_flags, keycmp, datacmp); + } + } + + /* Fail, if no free slot and max hit */ + MDBX_env *env = txn->mt_env; + if (unlikely(slot >= env->me_maxdbs)) + return MDBX_DBS_FULL; + + /* Cannot mix named table with some main-table flags */ + if (unlikely(txn->mt_dbs[MAIN_DBI].md_flags & + (MDBX_DUPSORT | MDBX_INTEGERKEY))) + return (user_flags & MDBX_CREATE) ? MDBX_INCOMPATIBLE : MDBX_NOTFOUND; + + /* Find the DB info */ + int exact = 0; + MDBX_val key, data; + key.iov_len = len; + key.iov_base = (void *)table_name; + MDBX_cursor mc; + rc = mdbx_cursor_init(&mc, txn, MAIN_DBI); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_cursor_set(&mc, &key, &data, MDBX_SET, &exact); + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc != MDBX_NOTFOUND || !(user_flags & MDBX_CREATE)) + return rc; + } else { + /* make sure this is actually a table */ + MDBX_node *node = page_node(mc.mc_pg[mc.mc_top], mc.mc_ki[mc.mc_top]); + if (unlikely((node_flags(node) & (F_DUPDATA | F_SUBDATA)) != F_SUBDATA)) + return MDBX_INCOMPATIBLE; + if (unlikely(data.iov_len < sizeof(MDBX_db))) + return MDBX_CORRUPTED; + } + + if (rc != MDBX_SUCCESS && unlikely(txn->mt_flags & MDBX_RDONLY)) + return MDBX_EACCESS; + + /* Done here so we cannot fail after creating a new DB */ + char *namedup = mdbx_strdup(table_name); + if (unlikely(!namedup)) + return MDBX_ENOMEM; + + int err = mdbx_fastmutex_acquire(&env->me_dbi_lock); + if (unlikely(err != MDBX_SUCCESS)) { + mdbx_free(namedup); + return err; + } + + if (txn->mt_numdbs < env->me_numdbs) { + for (unsigned i = txn->mt_numdbs; i < env->me_numdbs; ++i) { + txn->mt_dbflags[i] = 0; + if (env->me_dbflags[i] & MDBX_VALID) { + txn->mt_dbs[i].md_flags = env->me_dbflags[i] & PERSISTENT_FLAGS; + txn->mt_dbflags[i] = DB_VALID | DB_USRVALID | DB_STALE; + mdbx_tassert(txn, txn->mt_dbxs[i].md_cmp != NULL); + } + } + txn->mt_numdbs = env->me_numdbs; + } + + for (slot = scan = txn->mt_numdbs; --scan >= CORE_DBS;) { + if (!txn->mt_dbxs[scan].md_name.iov_len) { + /* Remember this free slot */ + slot = scan; + continue; + } + if (len == txn->mt_dbxs[scan].md_name.iov_len && + !strncmp(table_name, txn->mt_dbxs[scan].md_name.iov_base, len)) { + *dbi = scan; + rc = mdbx_dbi_bind(txn, scan, user_flags, keycmp, datacmp); + goto bailout; + } + } + + if (unlikely(slot >= env->me_maxdbs)) { + rc = MDBX_DBS_FULL; + goto bailout; + } + + unsigned dbflag = DB_FRESH | DB_VALID | DB_USRVALID; + MDBX_db db_dummy; + if (unlikely(rc)) { + /* MDBX_NOTFOUND and MDBX_CREATE: Create new DB */ + mdbx_tassert(txn, rc == MDBX_NOTFOUND); + memset(&db_dummy, 0, sizeof(db_dummy)); + db_dummy.md_root = P_INVALID; + db_dummy.md_flags = user_flags & PERSISTENT_FLAGS; + data.iov_len = sizeof(db_dummy); + data.iov_base = &db_dummy; + WITH_CURSOR_TRACKING( + mc, + rc = mdbx_cursor_put(&mc, &key, &data, F_SUBDATA | MDBX_NOOVERWRITE)); + + if (unlikely(rc != MDBX_SUCCESS)) + goto bailout; + + dbflag |= DB_DIRTY | DB_CREAT; + } + + /* Got info, register DBI in this txn */ + txn->mt_dbxs[slot].md_cmp = nullptr; + txn->mt_dbxs[slot].md_dcmp = nullptr; + txn->mt_dbs[slot] = *(MDBX_db *)data.iov_base; + env->me_dbflags[slot] = 0; + rc = mdbx_dbi_bind(txn, slot, user_flags, keycmp, datacmp); + if (unlikely(rc != MDBX_SUCCESS)) { + mdbx_tassert(txn, (dbflag & DB_CREAT) == 0); + bailout: + mdbx_free(namedup); + } else { + txn->mt_dbflags[slot] = (uint8_t)dbflag; + txn->mt_dbxs[slot].md_name.iov_base = namedup; + txn->mt_dbxs[slot].md_name.iov_len = len; + txn->mt_numdbs += (slot == txn->mt_numdbs); + if ((dbflag & DB_CREAT) == 0) { + env->me_dbflags[slot] = txn->mt_dbs[slot].md_flags | MDBX_VALID; + mdbx_compiler_barrier(); + if (env->me_numdbs <= slot) + env->me_numdbs = slot + 1; + } else { + env->me_dbiseqs[slot] += 1; + } + txn->mt_dbiseqs[slot] = env->me_dbiseqs[slot]; + *dbi = slot; + } + + mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); + return rc; +} + +int mdbx_dbi_open(MDBX_txn *txn, const char *table_name, unsigned table_flags, + MDBX_dbi *dbi) { + return mdbx_dbi_open_ex(txn, table_name, table_flags, dbi, nullptr, nullptr); +} + +int __cold mdbx_dbi_stat(MDBX_txn *txn, MDBX_dbi dbi, MDBX_stat *dest, + size_t bytes) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!dest)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_VALID))) + return MDBX_EINVAL; + + const size_t size_before_modtxnid = offsetof(MDBX_stat, ms_mod_txnid); + if (unlikely(bytes != sizeof(MDBX_stat)) && bytes != size_before_modtxnid) + return MDBX_EINVAL; + + if (unlikely(txn->mt_flags & MDBX_TXN_BLOCKED)) + return MDBX_BAD_TXN; + + if (unlikely(txn->mt_dbflags[dbi] & DB_STALE)) { + rc = mdbx_fetch_sdb(txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + mdbx_stat0(txn->mt_env, &txn->mt_dbs[dbi], dest, bytes); + return MDBX_SUCCESS; +} + +static int mdbx_dbi_close_locked(MDBX_env *env, MDBX_dbi dbi) { + if (unlikely(dbi < CORE_DBS || dbi >= env->me_maxdbs)) + return MDBX_EINVAL; + + char *ptr = env->me_dbxs[dbi].md_name.iov_base; + /* If there was no name, this was already closed */ + if (unlikely(!ptr)) + return MDBX_BAD_DBI; + + env->me_dbflags[dbi] = 0; + env->me_dbxs[dbi].md_name.iov_len = 0; + mdbx_compiler_barrier(); + env->me_dbiseqs[dbi]++; + env->me_dbxs[dbi].md_name.iov_base = NULL; + mdbx_free(ptr); + return MDBX_SUCCESS; +} + +int mdbx_dbi_close(MDBX_env *env, MDBX_dbi dbi) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(dbi < CORE_DBS || dbi >= env->me_maxdbs)) + return MDBX_EINVAL; + + int rc = mdbx_fastmutex_acquire(&env->me_dbi_lock); + if (likely(rc == MDBX_SUCCESS)) { + rc = mdbx_dbi_close_locked(env, dbi); + mdbx_ensure(env, mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); + } + return rc; +} + +int mdbx_dbi_flags_ex(MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags, + unsigned *state) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!flags || !state)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_VALID))) + return MDBX_EINVAL; + + *flags = txn->mt_dbs[dbi].md_flags & PERSISTENT_FLAGS; + *state = txn->mt_dbflags[dbi] & (DB_FRESH | DB_CREAT | DB_DIRTY | DB_STALE); + + return MDBX_SUCCESS; +} + +int mdbx_dbi_flags(MDBX_txn *txn, MDBX_dbi dbi, unsigned *flags) { + unsigned state; + return mdbx_dbi_flags_ex(txn, dbi, flags, &state); +} + +/* Add all the DB's pages to the free list. + * [in] mc Cursor on the DB to free. + * [in] subs non-Zero to check for sub-DBs in this DB. + * Returns 0 on success, non-zero on failure. */ +static int mdbx_drop0(MDBX_cursor *mc, int subs) { + int rc = mdbx_page_search(mc, NULL, MDBX_PS_FIRST); + if (likely(rc == MDBX_SUCCESS)) { + MDBX_txn *txn = mc->mc_txn; + MDBX_cursor mx; + unsigned i; + + /* DUPSORT sub-DBs have no ovpages/DBs. Omit scanning leaves. + * This also avoids any P_LEAF2 pages, which have no nodes. + * Also if the DB doesn't have sub-DBs and has no overflow + * pages, omit scanning leaves. */ + if ((mc->mc_flags & C_SUB) || (subs | mc->mc_db->md_overflow_pages) == 0) + mdbx_cursor_pop(mc); + + rc = mdbx_pnl_need(&txn->tw.retired_pages, + mc->mc_db->md_branch_pages + mc->mc_db->md_leaf_pages + + mc->mc_db->md_overflow_pages); + if (unlikely(rc)) + goto done; + + mdbx_cursor_copy(mc, &mx); + while (mc->mc_snum > 0) { + MDBX_page *mp = mc->mc_pg[mc->mc_top]; + unsigned n = page_numkeys(mp); + if (IS_LEAF(mp)) { + for (i = 0; i < n; i++) { + MDBX_node *node = page_node(mp, i); + if (node_flags(node) & F_BIGDATA) { + MDBX_page *omp; + rc = mdbx_page_get(mc, node_largedata_pgno(node), &omp, NULL); + if (unlikely(rc)) + goto done; + mdbx_cassert(mc, IS_OVERFLOW(omp)); + rc = mdbx_page_retire(mc, omp); + if (unlikely(rc)) + goto done; + if (!mc->mc_db->md_overflow_pages && !subs) + break; + } else if (subs && (node_flags(node) & F_SUBDATA)) { + rc = mdbx_xcursor_init1(mc, node); + if (unlikely(rc != MDBX_SUCCESS)) + goto done; + rc = mdbx_drop0(&mc->mc_xcursor->mx_cursor, 0); + if (unlikely(rc)) + goto done; + } + } + if (!subs && !mc->mc_db->md_overflow_pages) + goto pop; + } else { + for (i = 0; i < n; i++) { + /* free it */ + rc = mdbx_retire_pgno(mc, node_pgno(page_node(mp, i))); + if (unlikely(rc)) + goto done; + } + } + if (!mc->mc_top) + break; + mdbx_cassert(mc, i <= UINT16_MAX); + mc->mc_ki[mc->mc_top] = (indx_t)i; + rc = mdbx_cursor_sibling(mc, 1); + if (rc) { + if (unlikely(rc != MDBX_NOTFOUND)) + goto done; + /* no more siblings, go back to beginning + * of previous level. */ + pop: + mdbx_cursor_pop(mc); + mc->mc_ki[0] = 0; + for (i = 1; i < mc->mc_snum; i++) { + mc->mc_ki[i] = 0; + mc->mc_pg[i] = mx.mc_pg[i]; + } + } + } + /* free it */ + rc = mdbx_retire_pgno(mc, mc->mc_db->md_root); + done: + if (unlikely(rc)) + txn->mt_flags |= MDBX_TXN_ERROR; + } else if (rc == MDBX_NOTFOUND) { + rc = MDBX_SUCCESS; + } + mc->mc_flags &= ~C_INITIALIZED; + return rc; +} + +int mdbx_drop(MDBX_txn *txn, MDBX_dbi dbi, int del) { + int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(1 < (unsigned)del)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(TXN_DBI_CHANGED(txn, dbi))) + return MDBX_BAD_DBI; + + MDBX_cursor *mc; + rc = mdbx_cursor_open(txn, dbi, &mc); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) { + rc = MDBX_EINVAL; + goto bailout; + } + + if (unlikely(TXN_DBI_CHANGED(txn, dbi))) { + rc = MDBX_BAD_DBI; + goto bailout; + } + + rc = mdbx_drop0(mc, mc->mc_db->md_flags & MDBX_DUPSORT); + /* Invalidate the dropped DB's cursors */ + for (MDBX_cursor *m2 = txn->mt_cursors[dbi]; m2; m2 = m2->mc_next) + m2->mc_flags &= ~(C_INITIALIZED | C_EOF); + if (unlikely(rc)) + goto bailout; + + /* Can't delete the main DB */ + if (del && dbi >= CORE_DBS) { + rc = mdbx_del0(txn, MAIN_DBI, &mc->mc_dbx->md_name, NULL, F_SUBDATA); + if (likely(rc == MDBX_SUCCESS)) { + txn->mt_dbflags[dbi] = DB_STALE; + MDBX_env *env = txn->mt_env; + rc = mdbx_fastmutex_acquire(&env->me_dbi_lock); + if (unlikely(rc != MDBX_SUCCESS)) { + txn->mt_flags |= MDBX_TXN_ERROR; + goto bailout; + } + mdbx_dbi_close_locked(env, dbi); + mdbx_ensure(env, + mdbx_fastmutex_release(&env->me_dbi_lock) == MDBX_SUCCESS); + } else { + txn->mt_flags |= MDBX_TXN_ERROR; + } + } else { + /* reset the DB record, mark it dirty */ + txn->mt_dbflags[dbi] |= DB_DIRTY; + txn->mt_dbs[dbi].md_depth = 0; + txn->mt_dbs[dbi].md_branch_pages = 0; + txn->mt_dbs[dbi].md_leaf_pages = 0; + txn->mt_dbs[dbi].md_overflow_pages = 0; + txn->mt_dbs[dbi].md_entries = 0; + txn->mt_dbs[dbi].md_root = P_INVALID; + txn->mt_dbs[dbi].md_seq = 0; + txn->mt_flags |= MDBX_TXN_DIRTY; + } + +bailout: + mdbx_cursor_close(mc); + return rc; +} + +int mdbx_set_compare(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cmp_func *cmp) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + txn->mt_dbxs[dbi].md_cmp = cmp; + return MDBX_SUCCESS; +} + +int mdbx_set_dupsort(MDBX_txn *txn, MDBX_dbi dbi, MDBX_cmp_func *cmp) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + txn->mt_dbxs[dbi].md_dcmp = cmp; + return MDBX_SUCCESS; +} + +int __cold mdbx_reader_list(MDBX_env *env, MDBX_reader_list_func *func, + void *ctx) { + if (unlikely(!env || !func)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = MDBX_RESULT_TRUE; + int serial = 0; + if (likely(env->me_lck)) { + const unsigned snap_nreaders = env->me_lck->mti_numreaders; + for (unsigned i = 0; i < snap_nreaders; i++) { + const MDBX_reader *r = env->me_lck->mti_readers + i; + retry_reader:; + const uint32_t pid = r->mr_pid; + if (!pid) + continue; + txnid_t txnid = safe64_read(&r->mr_txnid); + const size_t tid = r->mr_tid; + const pgno_t pages_used = r->mr_snapshot_pages_used; + const uint64_t reader_pages_retired = r->mr_snapshot_pages_retired; + mdbx_compiler_barrier(); + if (unlikely(tid != r->mr_tid || + pages_used != r->mr_snapshot_pages_used || + reader_pages_retired != r->mr_snapshot_pages_retired || + txnid != safe64_read(&r->mr_txnid) || pid != r->mr_pid)) + goto retry_reader; + + mdbx_assert(env, txnid > 0); + if (txnid >= SAFE64_INVALID_THRESHOLD) + txnid = 0; + + size_t bytes_used = 0; + size_t bytes_retained = 0; + uint64_t lag = 0; + if (txnid) { + retry_header:; + const MDBX_meta *const recent_meta = mdbx_meta_head(env); + const uint64_t head_pages_retired = recent_meta->mm_pages_retired; + const txnid_t head_txnid = mdbx_meta_txnid_fluid(env, recent_meta); + mdbx_compiler_barrier(); + if (unlikely(recent_meta != mdbx_meta_head(env) || + head_pages_retired != recent_meta->mm_pages_retired) || + head_txnid != mdbx_meta_txnid_fluid(env, recent_meta)) + goto retry_header; + + lag = (head_txnid - txnid) / MDBX_TXNID_STEP; + bytes_used = pgno2bytes(env, pages_used); + bytes_retained = (head_pages_retired > reader_pages_retired) + ? pgno2bytes(env, (pgno_t)(head_pages_retired - + reader_pages_retired)) + : 0; + } + rc = func(ctx, ++serial, i, pid, (mdbx_tid_t)tid, txnid, lag, bytes_used, + bytes_retained); + if (unlikely(rc != MDBX_SUCCESS)) + break; + } + } + + return rc; +} + +/* Insert pid into list if not already present. + * return -1 if already present. */ +static int __cold mdbx_pid_insert(uint32_t *ids, uint32_t pid) { + /* binary search of pid in list */ + unsigned base = 0; + unsigned cursor = 1; + int val = 0; + unsigned n = ids[0]; + + while (n > 0) { + unsigned pivot = n >> 1; + cursor = base + pivot + 1; + val = pid - ids[cursor]; + + if (val < 0) { + n = pivot; + } else if (val > 0) { + base = cursor; + n -= pivot + 1; + } else { + /* found, so it's a duplicate */ + return -1; + } + } + + if (val > 0) + ++cursor; + + ids[0]++; + for (n = ids[0]; n > cursor; n--) + ids[n] = ids[n - 1]; + ids[n] = pid; + return 0; +} + +int __cold mdbx_reader_check(MDBX_env *env, int *dead) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (dead) + *dead = 0; + return mdbx_reader_check0(env, false, dead); +} + +/* Return: + * MDBX_RESULT_TRUE - done and mutex recovered + * MDBX_SUCCESS - done + * Otherwise errcode. */ +int __cold mdbx_reader_check0(MDBX_env *env, int rdt_locked, int *dead) { + mdbx_assert(env, rdt_locked >= 0); + +#if MDBX_TXN_CHECKPID + if (unlikely(env->me_pid != mdbx_getpid())) { + env->me_flags |= MDBX_FATAL_ERROR; + return MDBX_PANIC; + } +#endif /* MDBX_TXN_CHECKPID */ + + MDBX_lockinfo *const lck = env->me_lck; + if (unlikely(lck == NULL)) { + /* exclusive mode */ + if (dead) + *dead = 0; + return MDBX_SUCCESS; + } + + lck->mti_reader_check_timestamp = mdbx_osal_monotime(); + const unsigned snap_nreaders = lck->mti_numreaders; + uint32_t pidsbuf_onstask[142]; + uint32_t *const pids = + (snap_nreaders < ARRAY_LENGTH(pidsbuf_onstask)) + ? pidsbuf_onstask + : mdbx_malloc((snap_nreaders + 1) * sizeof(uint32_t)); + if (unlikely(!pids)) + return MDBX_ENOMEM; + + pids[0] = 0; + + int rc = MDBX_SUCCESS, count = 0; + for (unsigned i = 0; i < snap_nreaders; i++) { + const uint32_t pid = lck->mti_readers[i].mr_pid; + if (pid == 0) + continue /* skip empty */; + if (pid == env->me_pid) + continue /* skip self */; + if (mdbx_pid_insert(pids, pid) != 0) + continue /* such pid already processed */; + + int err = mdbx_rpid_check(env, pid); + if (err == MDBX_RESULT_TRUE) + continue /* reader is live */; + + if (err != MDBX_SUCCESS) { + rc = err; + break /* mdbx_rpid_check() failed */; + } + + /* stale reader found */ + if (!rdt_locked) { + err = mdbx_rdt_lock(env); + if (MDBX_IS_ERROR(err)) { + rc = err; + break; + } + + rdt_locked = -1; + if (err == MDBX_RESULT_TRUE) { + /* mutex recovered, the mdbx_mutex_failed() checked all readers */ + rc = MDBX_RESULT_TRUE; + break; + } + + /* a other process may have clean and reused slot, recheck */ + if (lck->mti_readers[i].mr_pid != pid) + continue; + + err = mdbx_rpid_check(env, pid); + if (MDBX_IS_ERROR(err)) { + rc = err; + break; + } + + if (err != MDBX_SUCCESS) + continue /* the race with other process, slot reused */; + } + + /* clean it */ + for (unsigned j = i; j < snap_nreaders; j++) { + if (lck->mti_readers[j].mr_pid == pid) { + mdbx_debug("clear stale reader pid %" PRIuPTR " txn %" PRIaTXN, + (size_t)pid, lck->mti_readers[j].mr_txnid.inconsistent); + lck->mti_readers[j].mr_pid = 0; + lck->mti_readers_refresh_flag = true; + count++; + } + } + } + + if (rdt_locked < 0) + mdbx_rdt_unlock(env); + + if (pids != pidsbuf_onstask) + mdbx_free(pids); + + if (dead) + *dead = count; + return rc; +} + +int __cold mdbx_setup_debug(int loglevel, int flags, MDBX_debug_func *logger) { + const int rc = mdbx_runtime_flags | (mdbx_loglevel << 16); + +#if !MDBX_DEBUG + (void)loglevel; +#else + if (loglevel != -1) + mdbx_loglevel = (uint8_t)loglevel; +#endif + + if (flags != -1) { +#if !MDBX_DEBUG + flags &= MDBX_DBG_DUMP | MDBX_DBG_LEGACY_MULTIOPEN; +#else + flags &= MDBX_DBG_ASSERT | MDBX_DBG_AUDIT | MDBX_DBG_JITTER | + MDBX_DBG_DUMP | MDBX_DBG_LEGACY_MULTIOPEN; +#endif +#if defined(__linux__) || defined(__gnu_linux__) + if ((mdbx_runtime_flags ^ flags) & MDBX_DBG_DUMP) { + /* http://man7.org/linux/man-pages/man5/core.5.html */ + const unsigned long dump_bits = + 1 << 3 /* Dump file-backed shared mappings */ + | 1 << 6 /* Dump shared huge pages */ + | 1 << 8 /* Dump shared DAX pages */; + const int core_filter_fd = + open("/proc/self/coredump_filter", O_TRUNC | O_RDWR); + if (core_filter_fd != -1) { + char buf[32]; + intptr_t bytes = pread(core_filter_fd, buf, sizeof(buf), 0); + if (bytes > 0 && (size_t)bytes < sizeof(buf)) { + buf[bytes] = 0; + const unsigned long present_mask = strtoul(buf, NULL, 16); + const unsigned long wanna_mask = (flags & MDBX_DBG_DUMP) + ? present_mask | dump_bits + : present_mask & ~dump_bits; + if (wanna_mask != present_mask) { + bytes = snprintf(buf, sizeof(buf), "0x%lx\n", wanna_mask); + if (bytes > 0 && (size_t)bytes < sizeof(buf)) { + bytes = pwrite(core_filter_fd, buf, bytes, 0); + (void)bytes; + } + } + } + close(core_filter_fd); + } + } +#endif /* Linux */ + mdbx_runtime_flags = (uint8_t)flags; + } + + if (-1 != (intptr_t)logger) + mdbx_debug_logger = logger; + return rc; +} + +static txnid_t __cold mdbx_oomkick(MDBX_env *env, const txnid_t laggard) { + mdbx_debug("%s", "DB size maxed out"); + + int retry; + for (retry = 0; retry < INT_MAX; ++retry) { + txnid_t oldest = mdbx_reclaiming_detent(env); + mdbx_assert(env, oldest < env->me_txn0->mt_txnid); + mdbx_assert(env, oldest >= laggard); + mdbx_assert(env, oldest >= *env->me_oldest); + if (oldest == laggard || unlikely(env->me_lck == NULL /* exclusive mode */)) + return oldest; + + if (MDBX_IS_ERROR(mdbx_reader_check0(env, false, NULL))) + break; + + MDBX_reader *asleep = nullptr; + MDBX_lockinfo *const lck = env->me_lck; + uint64_t oldest_retired = UINT64_MAX; + const unsigned snap_nreaders = lck->mti_numreaders; + for (unsigned i = 0; i < snap_nreaders; ++i) { + retry: + if (lck->mti_readers[i].mr_pid) { + /* mdbx_jitter4testing(true); */ + const uint64_t snap_retired = + lck->mti_readers[i].mr_snapshot_pages_retired; + const txnid_t snap_txnid = safe64_read(&lck->mti_readers[i].mr_txnid); + mdbx_memory_barrier(); + if (unlikely(snap_retired != + lck->mti_readers[i].mr_snapshot_pages_retired || + snap_txnid != safe64_read(&lck->mti_readers[i].mr_txnid))) + goto retry; + if (oldest > snap_txnid && + laggard <= /* ignore pending updates */ snap_txnid) { + oldest = snap_txnid; + oldest_retired = snap_retired; + asleep = &lck->mti_readers[i]; + } + } + } + + if (laggard < oldest || !asleep) { + if (retry && env->me_oom_func) { + /* LY: notify end of oom-loop */ + const txnid_t gap = oldest - laggard; + env->me_oom_func(env, 0, 0, laggard, + (gap < UINT_MAX) ? (unsigned)gap : UINT_MAX, 0, + -retry); + } + mdbx_notice("oom-kick: update oldest %" PRIaTXN " -> %" PRIaTXN, + *env->me_oldest, oldest); + mdbx_assert(env, *env->me_oldest <= oldest); + return *env->me_oldest = oldest; + } + + if (!env->me_oom_func) + break; + + uint32_t pid = asleep->mr_pid; + size_t tid = asleep->mr_tid; + if (safe64_read(&asleep->mr_txnid) != laggard || pid <= 0) + continue; + + const MDBX_meta *head_meta = mdbx_meta_head(env); + const txnid_t gap = + (mdbx_meta_txnid_stable(env, head_meta) - laggard) / MDBX_TXNID_STEP; + const uint64_t head_retired = head_meta->mm_pages_retired; + const size_t space = + (oldest_retired > head_retired) + ? pgno2bytes(env, (pgno_t)(oldest_retired - head_retired)) + : 0; + int rc = env->me_oom_func(env, pid, (mdbx_tid_t)tid, laggard, + (gap < UINT_MAX) ? (unsigned)gap : UINT_MAX, + space, retry); + if (rc < 0) + break; + + if (rc > 0) { + if (rc == 1) { + safe64_reset_compare(&asleep->mr_txnid, laggard); + } else { + safe64_reset(&asleep->mr_txnid, true); + asleep->mr_tid = 0; + asleep->mr_pid = 0; + } + lck->mti_readers_refresh_flag = true; + mdbx_flush_noncoherent_cpu_writeback(); + } + } + + if (retry && env->me_oom_func) { + /* LY: notify end of oom-loop */ + env->me_oom_func(env, 0, 0, laggard, 0, 0, -retry); + } + return mdbx_find_oldest(env->me_txn); +} + +int __cold mdbx_env_set_syncbytes(MDBX_env *env, size_t threshold) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(env->me_flags & (MDBX_RDONLY | MDBX_FATAL_ERROR))) + return MDBX_EACCESS; + + if (unlikely(!env->me_map)) + return MDBX_EPERM; + + *env->me_autosync_threshold = bytes2pgno(env, threshold + env->me_psize - 1); + if (threshold) { + int err = mdbx_env_sync_poll(env); + if (unlikely(MDBX_IS_ERROR(err))) + return err; + } + return MDBX_SUCCESS; +} + +int __cold mdbx_env_set_syncperiod(MDBX_env *env, unsigned seconds_16dot16) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + if (unlikely(env->me_flags & (MDBX_RDONLY | MDBX_FATAL_ERROR))) + return MDBX_EACCESS; + + if (unlikely(!env->me_map)) + return MDBX_EPERM; + + *env->me_autosync_period = mdbx_osal_16dot16_to_monotime(seconds_16dot16); + if (seconds_16dot16) { + int err = mdbx_env_sync_poll(env); + if (unlikely(MDBX_IS_ERROR(err))) + return err; + } + return MDBX_SUCCESS; +} + +int __cold mdbx_env_set_oomfunc(MDBX_env *env, MDBX_oom_func *oomfunc) { + if (unlikely(!env)) + return MDBX_EINVAL; + + if (unlikely(env->me_signature != MDBX_ME_SIGNATURE)) + return MDBX_EBADSIGN; + + env->me_oom_func = oomfunc; + return MDBX_SUCCESS; +} + +MDBX_oom_func *__cold mdbx_env_get_oomfunc(MDBX_env *env) { + return likely(env && env->me_signature == MDBX_ME_SIGNATURE) + ? env->me_oom_func + : NULL; +} + +#ifdef __SANITIZE_THREAD__ +/* LY: avoid tsan-trap by me_txn, mm_last_pg and mt_next_pgno */ +__attribute__((__no_sanitize_thread__, __noinline__)) +#endif +int mdbx_txn_straggler(MDBX_txn *txn, int *percent) +{ + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return (rc > 0) ? -rc : rc; + + MDBX_env *env = txn->mt_env; + if (unlikely((txn->mt_flags & MDBX_RDONLY) == 0)) { + if (percent) + *percent = + (int)((txn->mt_next_pgno * UINT64_C(100) + txn->mt_end_pgno / 2) / + txn->mt_end_pgno); + return 0; + } + + txnid_t recent; + MDBX_meta *meta; + do { + meta = mdbx_meta_head(env); + recent = mdbx_meta_txnid_fluid(env, meta); + if (percent) { + const pgno_t maxpg = meta->mm_geo.now; + *percent = (int)((meta->mm_geo.next * UINT64_C(100) + maxpg / 2) / maxpg); + } + } while (unlikely(recent != mdbx_meta_txnid_fluid(env, meta))); + + txnid_t lag = (recent - txn->mt_txnid) / MDBX_TXNID_STEP; + return (lag > INT_MAX) ? INT_MAX : (int)lag; +} + +typedef struct mdbx_walk_ctx { + void *mw_user; + MDBX_pgvisitor_func *mw_visitor; + MDBX_cursor mw_cursor; +} mdbx_walk_ctx_t; + +/* Depth-first tree traversal. */ +static int __cold mdbx_env_walk(mdbx_walk_ctx_t *ctx, const char *dbi, + pgno_t pgno, int deep) { + if (unlikely(pgno == P_INVALID)) + return MDBX_SUCCESS; /* empty db */ + + MDBX_page *mp; + int rc = mdbx_page_get(&ctx->mw_cursor, pgno, &mp, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + rc = mdbx_page_check(ctx->mw_cursor.mc_txn->mt_env, mp, false); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + const int nkeys = page_numkeys(mp); + size_t header_size = IS_LEAF2(mp) ? PAGEHDRSZ : PAGEHDRSZ + mp->mp_lower; + size_t unused_size = page_room(mp); + size_t payload_size = 0; + size_t align_bytes = 0; + MDBX_page_type_t type; + + /* LY: Don't use mask here, e.g bitwise + * (P_BRANCH|P_LEAF|P_LEAF2|P_META|P_OVERFLOW|P_SUBP). + * Pages should not me marked dirty/loose or otherwise. */ + switch (mp->mp_flags) { + case P_BRANCH: + type = MDBX_page_branch; + if (unlikely(nkeys < 2)) + return MDBX_CORRUPTED; + break; + case P_LEAF: + type = MDBX_page_leaf; + break; + case P_LEAF | P_LEAF2: + type = MDBX_page_dupfixed_leaf; + break; + default: + return MDBX_CORRUPTED; + } + + for (int i = 0; i < nkeys; + align_bytes += ((payload_size + align_bytes) & 1), i++) { + if (type == MDBX_page_dupfixed_leaf) { + /* LEAF2 pages have no mp_ptrs[] or node headers */ + payload_size += mp->mp_leaf2_ksize; + continue; + } + + MDBX_node *node = page_node(mp, i); + payload_size += NODESIZE + node_ks(node); + + if (type == MDBX_page_branch) { + assert(i > 0 || node_ks(node) == 0); + continue; + } + + assert(type == MDBX_page_leaf); + switch (node_flags(node)) { + case 0 /* usual node */: { + payload_size += node_ds(node); + } break; + + case F_BIGDATA /* long data on the large/overflow page */: { + payload_size += sizeof(pgno_t); + + const pgno_t large_pgno = node_largedata_pgno(node); + MDBX_page *op; + rc = mdbx_page_get(&ctx->mw_cursor, large_pgno, &op, NULL); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_page_check(ctx->mw_cursor.mc_txn->mt_env, op, false); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + /* LY: Don't use mask here, e.g bitwise + * (P_BRANCH|P_LEAF|P_LEAF2|P_META|P_OVERFLOW|P_SUBP). + * Pages should not me marked dirty/loose or otherwise. */ + if (unlikely(P_OVERFLOW != op->mp_flags)) + return MDBX_CORRUPTED; + + const size_t over_header = PAGEHDRSZ; + const size_t over_payload = node_ds(node); + const size_t over_unused = + pgno2bytes(ctx->mw_cursor.mc_txn->mt_env, op->mp_pages) - + over_payload - over_header; + + rc = ctx->mw_visitor( + large_pgno, op->mp_pages, ctx->mw_user, deep, dbi, + pgno2bytes(ctx->mw_cursor.mc_txn->mt_env, op->mp_pages), + MDBX_page_large, 1, over_payload, over_header, over_unused); + } break; + + case F_SUBDATA /* sub-db */: { + const size_t namelen = node_ks(node); + if (unlikely(namelen == 0 || node_ds(node) < sizeof(MDBX_db))) + return MDBX_CORRUPTED; + payload_size += node_ds(node); + } break; + + case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: { + if (unlikely(node_ds(node) != sizeof(MDBX_db))) + return MDBX_CORRUPTED; + payload_size += sizeof(MDBX_db); + } break; + + case F_DUPDATA /* short sub-page */: { + if (unlikely(node_ds(node) <= PAGEHDRSZ)) + return MDBX_CORRUPTED; + + MDBX_page *sp = node_data(node); + const int nsubkeys = page_numkeys(sp); + size_t subheader_size = + IS_LEAF2(sp) ? PAGEHDRSZ : PAGEHDRSZ + sp->mp_lower; + size_t subunused_size = page_room(sp); + size_t subpayload_size = 0; + size_t subalign_bytes = 0; + MDBX_page_type_t subtype; + + switch (sp->mp_flags & ~P_DIRTY /* ignore for sub-pages */) { + case P_LEAF | P_SUBP: + subtype = MDBX_subpage_leaf; + break; + case P_LEAF | P_LEAF2 | P_SUBP: + subtype = MDBX_subpage_dupfixed_leaf; + break; + default: + return MDBX_CORRUPTED; + } + + for (int j = 0; j < nsubkeys; + subalign_bytes += ((subpayload_size + subalign_bytes) & 1), j++) { + + if (subtype == MDBX_subpage_dupfixed_leaf) { + /* LEAF2 pages have no mp_ptrs[] or node headers */ + subpayload_size += sp->mp_leaf2_ksize; + } else { + assert(subtype == MDBX_subpage_leaf); + MDBX_node *subnode = page_node(sp, j); + subpayload_size += NODESIZE + node_ks(subnode) + node_ds(subnode); + if (unlikely(node_flags(subnode) != 0)) + return MDBX_CORRUPTED; + } + } + + rc = ctx->mw_visitor(pgno, 0, ctx->mw_user, deep + 1, dbi, node_ds(node), + subtype, nsubkeys, subpayload_size, subheader_size, + subunused_size + subalign_bytes); + header_size += subheader_size; + unused_size += subunused_size; + payload_size += subpayload_size; + align_bytes += subalign_bytes; + } break; + + default: + return MDBX_CORRUPTED; + } + + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + rc = ctx->mw_visitor(mp->mp_pgno, 1, ctx->mw_user, deep, dbi, + ctx->mw_cursor.mc_txn->mt_env->me_psize, type, nkeys, + payload_size, header_size, unused_size + align_bytes); + + if (unlikely(rc != MDBX_SUCCESS)) + return (rc == MDBX_RESULT_TRUE) ? MDBX_SUCCESS : rc; + + for (int i = 0; i < nkeys; i++) { + if (type == MDBX_page_dupfixed_leaf) + continue; + + MDBX_node *node = page_node(mp, i); + if (type == MDBX_page_branch) { + rc = mdbx_env_walk(ctx, dbi, node_pgno(node), deep + 1); + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc != MDBX_RESULT_TRUE) + return rc; + break; + } + continue; + } + + assert(type == MDBX_page_leaf); + MDBX_db db; + switch (node_flags(node)) { + default: + continue; + + case F_SUBDATA /* sub-db */: { + const size_t namelen = node_ks(node); + if (unlikely(namelen == 0 || node_ds(node) != sizeof(MDBX_db))) + return MDBX_CORRUPTED; + + char namebuf_onstask[142]; + char *const name = (namelen < sizeof(namebuf_onstask)) + ? namebuf_onstask + : mdbx_malloc(namelen + 1); + if (name) { + memcpy(name, node_key(node), namelen); + name[namelen] = 0; + memcpy(&db, node_data(node), sizeof(db)); + rc = mdbx_env_walk(ctx, name, db.md_root, deep + 1); + if (name != namebuf_onstask) + mdbx_free(name); + } else { + rc = MDBX_ENOMEM; + } + } break; + + case F_SUBDATA | F_DUPDATA /* dupsorted sub-tree */: + if (unlikely(node_ds(node) != sizeof(MDBX_db))) + return MDBX_CORRUPTED; + + memcpy(&db, node_data(node), sizeof(db)); + rc = mdbx_env_walk(ctx, dbi, db.md_root, deep + 1); + break; + } + + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + return MDBX_SUCCESS; +} + +int __cold mdbx_env_pgwalk(MDBX_txn *txn, MDBX_pgvisitor_func *visitor, + void *user) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + mdbx_walk_ctx_t ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.mw_cursor.mc_snum = 1; + ctx.mw_cursor.mc_txn = txn; + ctx.mw_user = user; + ctx.mw_visitor = visitor; + + rc = visitor(0, NUM_METAS, user, 0, MDBX_PGWALK_META, + pgno2bytes(txn->mt_env, NUM_METAS), MDBX_page_meta, NUM_METAS, + sizeof(MDBX_meta) * NUM_METAS, PAGEHDRSZ * NUM_METAS, + (txn->mt_env->me_psize - sizeof(MDBX_meta) - PAGEHDRSZ) * + NUM_METAS); + if (!MDBX_IS_ERROR(rc)) + rc = mdbx_env_walk(&ctx, MDBX_PGWALK_GC, txn->mt_dbs[FREE_DBI].md_root, 0); + if (!MDBX_IS_ERROR(rc)) + rc = + mdbx_env_walk(&ctx, MDBX_PGWALK_MAIN, txn->mt_dbs[MAIN_DBI].md_root, 0); + if (!MDBX_IS_ERROR(rc)) + rc = visitor(P_INVALID, 0, user, INT_MIN, NULL, 0, MDBX_page_void, 0, 0, 0, + 0); + return rc; +} + +int mdbx_canary_put(MDBX_txn *txn, const mdbx_canary *canary) { + int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (likely(canary)) { + if (txn->mt_canary.x == canary->x && txn->mt_canary.y == canary->y && + txn->mt_canary.z == canary->z) + return MDBX_SUCCESS; + txn->mt_canary.x = canary->x; + txn->mt_canary.y = canary->y; + txn->mt_canary.z = canary->z; + } + txn->mt_canary.v = txn->mt_txnid; + + if ((txn->mt_flags & MDBX_TXN_DIRTY) == 0) { + txn->mt_flags |= MDBX_TXN_DIRTY; + *txn->mt_env->me_unsynced_pages += 1; + } + return MDBX_SUCCESS; +} + +int mdbx_canary_get(MDBX_txn *txn, mdbx_canary *canary) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(canary == NULL)) + return MDBX_EINVAL; + + *canary = txn->mt_canary; + return MDBX_SUCCESS; +} + +int mdbx_cursor_on_first(MDBX_cursor *mc) { + if (unlikely(mc == NULL)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + if (!(mc->mc_flags & C_INITIALIZED)) + return MDBX_RESULT_FALSE; + + for (unsigned i = 0; i < mc->mc_snum; ++i) { + if (mc->mc_ki[i]) + return MDBX_RESULT_FALSE; + } + + return MDBX_RESULT_TRUE; +} + +int mdbx_cursor_on_last(MDBX_cursor *mc) { + if (unlikely(mc == NULL)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + if (!(mc->mc_flags & C_INITIALIZED)) + return MDBX_RESULT_FALSE; + + for (unsigned i = 0; i < mc->mc_snum; ++i) { + unsigned nkeys = page_numkeys(mc->mc_pg[i]); + if (mc->mc_ki[i] < nkeys - 1) + return MDBX_RESULT_FALSE; + } + + return MDBX_RESULT_TRUE; +} + +int mdbx_cursor_eof(MDBX_cursor *mc) { + if (unlikely(mc == NULL)) + return MDBX_EINVAL; + + if (unlikely(mc->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + if ((mc->mc_flags & C_INITIALIZED) == 0) + return MDBX_RESULT_TRUE; + + if (mc->mc_snum == 0) + return MDBX_RESULT_TRUE; + + if ((mc->mc_flags & C_EOF) && + mc->mc_ki[mc->mc_top] >= page_numkeys(mc->mc_pg[mc->mc_top])) + return MDBX_RESULT_TRUE; + + return MDBX_RESULT_FALSE; +} + +//------------------------------------------------------------------------------ + +struct diff_result { + ptrdiff_t diff; + int level; + int root_nkeys; +}; + +/* calculates: r = x - y */ +__hot static int cursor_diff(const MDBX_cursor *const __restrict x, + const MDBX_cursor *const __restrict y, + struct diff_result *const __restrict r) { + r->diff = 0; + r->level = 0; + r->root_nkeys = 0; + + if (unlikely(y->mc_signature != MDBX_MC_SIGNATURE || + x->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = check_txn(x->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(x->mc_txn != y->mc_txn)) + return MDBX_BAD_TXN; + + if (unlikely(y->mc_dbi != x->mc_dbi)) + return MDBX_EINVAL; + + if (unlikely(!(y->mc_flags & x->mc_flags & C_INITIALIZED))) + return MDBX_ENODATA; + + while (likely(r->level < y->mc_snum && r->level < x->mc_snum)) { + if (unlikely(y->mc_pg[r->level] != x->mc_pg[r->level])) + return MDBX_PROBLEM; + + int nkeys = page_numkeys(y->mc_pg[r->level]); + assert(nkeys > 0); + if (r->level == 0) + r->root_nkeys = nkeys; + + const int limit_ki = nkeys - 1; + const int x_ki = x->mc_ki[r->level]; + const int y_ki = y->mc_ki[r->level]; + r->diff = ((x_ki < limit_ki) ? x_ki : limit_ki) - + ((y_ki < limit_ki) ? y_ki : limit_ki); + if (r->diff == 0) { + r->level += 1; + continue; + } + + while (unlikely(r->diff == 1) && + likely(r->level + 1 < y->mc_snum && r->level + 1 < x->mc_snum)) { + r->level += 1; + /* DB'PAGEs: 0------------------>MAX + * + * CURSORs: y < x + * STACK[i ]: | + * STACK[+1]: ...y++N|0++x... + */ + nkeys = page_numkeys(y->mc_pg[r->level]); + r->diff = (nkeys - y->mc_ki[r->level]) + x->mc_ki[r->level]; + assert(r->diff > 0); + } + + while (unlikely(r->diff == -1) && + likely(r->level + 1 < y->mc_snum && r->level + 1 < x->mc_snum)) { + r->level += 1; + /* DB'PAGEs: 0------------------>MAX + * + * CURSORs: x < y + * STACK[i ]: | + * STACK[+1]: ...x--N|0--y... + */ + nkeys = page_numkeys(x->mc_pg[r->level]); + r->diff = -(nkeys - x->mc_ki[r->level]) - y->mc_ki[r->level]; + assert(r->diff < 0); + } + + return MDBX_SUCCESS; + } + + r->diff = CMP2INT(x->mc_flags & C_EOF, y->mc_flags & C_EOF); + return MDBX_SUCCESS; +} + +__hot static ptrdiff_t estimate(const MDBX_db *db, + struct diff_result *const __restrict dr) { + /* root: branch-page => scale = leaf-factor * branch-factor^(N-1) + * level-1: branch-page(s) => scale = leaf-factor * branch-factor^2 + * level-2: branch-page(s) => scale = leaf-factor * branch-factor + * level-N: branch-page(s) => scale = leaf-factor + * leaf-level: leaf-page(s) => scale = 1 + */ + ptrdiff_t btree_power = db->md_depth - 2 - dr->level; + if (btree_power < 0) + return dr->diff; + + ptrdiff_t estimated = + (ptrdiff_t)db->md_entries * dr->diff / (ptrdiff_t)db->md_leaf_pages; + if (btree_power == 0) + return estimated; + + if (db->md_depth < 4) { + assert(dr->level == 0 && btree_power == 1); + return (ptrdiff_t)db->md_entries * dr->diff / (ptrdiff_t)dr->root_nkeys; + } + + /* average_branchpage_fillfactor = total(branch_entries) / branch_pages + total(branch_entries) = leaf_pages + branch_pages - 1 (root page) */ + const size_t log2_fixedpoint = sizeof(size_t) - 1; + const size_t half = UINT64_C(1) << (log2_fixedpoint - 1); + const size_t factor = + ((db->md_leaf_pages + db->md_branch_pages - 1) << log2_fixedpoint) / + db->md_branch_pages; + while (1) { + switch ((size_t)btree_power) { + default: { + const size_t square = (factor * factor + half) >> log2_fixedpoint; + const size_t quad = (square * square + half) >> log2_fixedpoint; + do { + estimated = estimated * quad + half; + estimated >>= log2_fixedpoint; + btree_power -= 4; + } while (btree_power >= 4); + continue; + } + case 3: + estimated = estimated * factor + half; + estimated >>= log2_fixedpoint; + __fallthrough /* fall through */; + case 2: + estimated = estimated * factor + half; + estimated >>= log2_fixedpoint; + __fallthrough /* fall through */; + case 1: + estimated = estimated * factor + half; + estimated >>= log2_fixedpoint; + __fallthrough /* fall through */; + case 0: + if (unlikely(estimated > (ptrdiff_t)db->md_entries)) + return (ptrdiff_t)db->md_entries; + if (unlikely(estimated < -(ptrdiff_t)db->md_entries)) + return -(ptrdiff_t)db->md_entries; + return estimated; + } + } +} + +__hot int mdbx_estimate_distance(const MDBX_cursor *first, + const MDBX_cursor *last, + ptrdiff_t *distance_items) { + if (unlikely(first == NULL || last == NULL || distance_items == NULL)) + return MDBX_EINVAL; + + *distance_items = 0; + struct diff_result dr; + int rc = cursor_diff(last, first, &dr); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(dr.diff == 0) && + F_ISSET(first->mc_db->md_flags & first->mc_db->md_flags, + MDBX_DUPSORT | C_INITIALIZED)) { + first = &first->mc_xcursor->mx_cursor; + last = &last->mc_xcursor->mx_cursor; + rc = cursor_diff(first, last, &dr); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + if (likely(dr.diff != 0)) + *distance_items = estimate(first->mc_db, &dr); + + return MDBX_SUCCESS; +} + +int mdbx_estimate_move(const MDBX_cursor *cursor, MDBX_val *key, MDBX_val *data, + MDBX_cursor_op move_op, ptrdiff_t *distance_items) { + if (unlikely(cursor == NULL || distance_items == NULL || + move_op == MDBX_GET_CURRENT || move_op == MDBX_GET_MULTIPLE)) + return MDBX_EINVAL; + + if (unlikely(cursor->mc_signature != MDBX_MC_SIGNATURE)) + return MDBX_EBADSIGN; + + int rc = check_txn(cursor->mc_txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (!(cursor->mc_flags & C_INITIALIZED)) + return MDBX_ENODATA; + + MDBX_cursor_couple next; + mdbx_cursor_copy(cursor, &next.outer); + next.outer.mc_xcursor = NULL; + if (cursor->mc_db->md_flags & MDBX_DUPSORT) { + next.outer.mc_xcursor = &next.inner; + rc = mdbx_xcursor_init0(&next.outer); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + MDBX_xcursor *mx = &container_of(cursor, MDBX_cursor_couple, outer)->inner; + mdbx_cursor_copy(&mx->mx_cursor, &next.inner.mx_cursor); + } + + MDBX_val stub = {0, 0}; + if (data == NULL) { + const unsigned mask = + 1 << MDBX_GET_BOTH | 1 << MDBX_GET_BOTH_RANGE | 1 << MDBX_SET_KEY; + if (unlikely(mask & (1 << move_op))) + return MDBX_EINVAL; + data = &stub; + } + + if (key == NULL) { + const unsigned mask = 1 << MDBX_GET_BOTH | 1 << MDBX_GET_BOTH_RANGE | + 1 << MDBX_SET_KEY | 1 << MDBX_SET | + 1 << MDBX_SET_RANGE; + if (unlikely(mask & (1 << move_op))) + return MDBX_EINVAL; + key = &stub; + } + + rc = mdbx_cursor_get(&next.outer, key, data, move_op); + if (unlikely(rc != MDBX_SUCCESS && + (rc != MDBX_NOTFOUND || !(next.outer.mc_flags & C_INITIALIZED)))) + return rc; + + return mdbx_estimate_distance(cursor, &next.outer, distance_items); +} + +static int mdbx_is_samedata(const MDBX_val *a, const MDBX_val *b) { + return a->iov_len == b->iov_len && + memcmp(a->iov_base, b->iov_base, a->iov_len) == 0; +} + +int mdbx_estimate_range(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *begin_key, + MDBX_val *begin_data, MDBX_val *end_key, + MDBX_val *end_data, ptrdiff_t *size_items) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!size_items)) + return MDBX_EINVAL; + + if (unlikely(begin_data && (begin_key == NULL || begin_key == MDBX_EPSILON))) + return MDBX_EINVAL; + + if (unlikely(end_data && (end_key == NULL || end_key == MDBX_EPSILON))) + return MDBX_EINVAL; + + if (unlikely(begin_key == MDBX_EPSILON && end_key == MDBX_EPSILON)) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + MDBX_cursor_couple begin; + /* LY: first, initialize cursor to refresh a DB in case it have DB_STALE */ + rc = mdbx_cursor_init(&begin.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(begin.outer.mc_db->md_entries == 0)) { + *size_items = 0; + return MDBX_SUCCESS; + } + + if (!begin_key) { + if (unlikely(!end_key)) { + /* LY: FIRST..LAST case */ + *size_items = (ptrdiff_t)begin.outer.mc_db->md_entries; + return MDBX_SUCCESS; + } + MDBX_val stub = {0, 0}; + rc = mdbx_cursor_first(&begin.outer, &stub, &stub); + if (unlikely(end_key == MDBX_EPSILON)) { + /* LY: FIRST..+epsilon case */ + return (rc == MDBX_SUCCESS) + ? mdbx_cursor_count(&begin.outer, (size_t *)size_items) + : rc; + } + } else { + if (unlikely(begin_key == MDBX_EPSILON)) { + if (end_key == NULL) { + /* LY: -epsilon..LAST case */ + MDBX_val stub = {0, 0}; + rc = mdbx_cursor_last(&begin.outer, &stub, &stub); + return (rc == MDBX_SUCCESS) + ? mdbx_cursor_count(&begin.outer, (size_t *)size_items) + : rc; + } + /* LY: -epsilon..value case */ + assert(end_key != MDBX_EPSILON); + begin_key = end_key; + } else if (unlikely(end_key == MDBX_EPSILON)) { + /* LY: value..+epsilon case */ + assert(begin_key != MDBX_EPSILON); + end_key = begin_key; + } + if (end_key && !begin_data && !end_data && + (begin_key == end_key || mdbx_is_samedata(begin_key, end_key))) { + /* LY: single key case */ + int exact = 0; + rc = mdbx_cursor_set(&begin.outer, begin_key, NULL, MDBX_SET, &exact); + if (unlikely(rc != MDBX_SUCCESS)) { + *size_items = 0; + return (rc == MDBX_NOTFOUND) ? MDBX_SUCCESS : rc; + } + *size_items = 1; + if (begin.outer.mc_xcursor != NULL) { + MDBX_node *node = page_node(begin.outer.mc_pg[begin.outer.mc_top], + begin.outer.mc_ki[begin.outer.mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + /* LY: return the number of duplicates for given key */ + mdbx_tassert(txn, + begin.outer.mc_xcursor == &begin.inner && + (begin.inner.mx_cursor.mc_flags & C_INITIALIZED)); + *size_items = + (sizeof(*size_items) >= sizeof(begin.inner.mx_db.md_entries) || + begin.inner.mx_db.md_entries <= PTRDIFF_MAX) + ? (size_t)begin.inner.mx_db.md_entries + : PTRDIFF_MAX; + } + } + return MDBX_SUCCESS; + } else { + rc = mdbx_cursor_set(&begin.outer, begin_key, begin_data, + begin_data ? MDBX_GET_BOTH_RANGE : MDBX_SET_RANGE, + NULL); + } + } + + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc != MDBX_NOTFOUND || !(begin.outer.mc_flags & C_INITIALIZED)) + return rc; + } + + MDBX_cursor_couple end; + rc = mdbx_cursor_init(&end.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + if (!end_key) { + MDBX_val stub = {0, 0}; + rc = mdbx_cursor_last(&end.outer, &stub, &stub); + } else { + rc = mdbx_cursor_set(&end.outer, end_key, end_data, + end_data ? MDBX_GET_BOTH_RANGE : MDBX_SET_RANGE, NULL); + } + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc != MDBX_NOTFOUND || !(end.outer.mc_flags & C_INITIALIZED)) + return rc; + } + + rc = mdbx_estimate_distance(&begin.outer, &end.outer, size_items); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + assert(*size_items >= -(ptrdiff_t)begin.outer.mc_db->md_entries && + *size_items <= (ptrdiff_t)begin.outer.mc_db->md_entries); + +#if 0 /* LY: Was decided to returns as-is (i.e. negative) the estimation \ + * results for an inverted ranges. */ + + /* Commit 8ddfd1f34ad7cf7a3c4aa75d2e248ca7e639ed63 + Change-Id: If59eccf7311123ab6384c4b93f9b1fed5a0a10d1 */ + + if (*size_items < 0) { + /* LY: inverted range case */ + *size_items += (ptrdiff_t)begin.outer.mc_db->md_entries; + } else if (*size_items == 0 && begin_key && end_key) { + int cmp = begin.outer.mc_dbx->md_cmp(&origin_begin_key, &origin_end_key); + if (cmp == 0 && (begin.inner.mx_cursor.mc_flags & C_INITIALIZED) && + begin_data && end_data) + cmp = begin.outer.mc_dbx->md_dcmp(&origin_begin_data, &origin_end_data); + if (cmp > 0) { + /* LY: inverted range case with empty scope */ + *size_items = (ptrdiff_t)begin.outer.mc_db->md_entries; + } + } + assert(*size_items >= 0 && + *size_items <= (ptrdiff_t)begin.outer.mc_db->md_entries); +#endif + + return MDBX_SUCCESS; +} + +//------------------------------------------------------------------------------ + +/* Позволяет обновить или удалить существующую запись с получением + * в old_data предыдущего значения данных. При этом если new_data равен + * нулю, то выполняется удаление, иначе обновление/вставка. + * + * Текущее значение может находиться в уже измененной (грязной) странице. + * В этом случае страница будет перезаписана при обновлении, а само старое + * значение утрачено. Поэтому исходно в old_data должен быть передан + * дополнительный буфер для копирования старого значения. + * Если переданный буфер слишком мал, то функция вернет -1, установив + * old_data->iov_len в соответствующее значение. + * + * Для не-уникальных ключей также возможен второй сценарий использования, + * когда посредством old_data из записей с одинаковым ключом для + * удаления/обновления выбирается конкретная. Для выбора этого сценария + * во flags следует одновременно указать MDBX_CURRENT и MDBX_NOOVERWRITE. + * Именно эта комбинация выбрана, так как она лишена смысла, и этим позволяет + * идентифицировать запрос такого сценария. + * + * Функция может быть замещена соответствующими операциями с курсорами + * после двух доработок (TODO): + * - внешняя аллокация курсоров, в том числе на стеке (без malloc). + * - получения статуса страницы по адресу (знать о P_DIRTY). + */ +int mdbx_replace(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *new_data, + MDBX_val *old_data, unsigned flags) { + int rc = check_txn_rw(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!key || !old_data || old_data == new_data)) + return MDBX_EINVAL; + + if (unlikely(old_data->iov_base == NULL && old_data->iov_len)) + return MDBX_EINVAL; + + if (unlikely(new_data == NULL && !(flags & MDBX_CURRENT))) + return MDBX_EINVAL; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(flags & ~(MDBX_NOOVERWRITE | MDBX_NODUPDATA | MDBX_RESERVE | + MDBX_APPEND | MDBX_APPENDDUP | MDBX_CURRENT))) + return MDBX_EINVAL; + + MDBX_cursor_couple cx; + rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + cx.outer.mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = &cx.outer; + + MDBX_val present_key = *key; + if (F_ISSET(flags, MDBX_CURRENT | MDBX_NOOVERWRITE)) { + /* в old_data значение для выбора конкретного дубликата */ + if (unlikely(!(txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT))) { + rc = MDBX_EINVAL; + goto bailout; + } + + /* убираем лишний бит, он был признаком запрошенного режима */ + flags -= MDBX_NOOVERWRITE; + + rc = mdbx_cursor_get(&cx.outer, &present_key, old_data, MDBX_GET_BOTH); + if (rc != MDBX_SUCCESS) + goto bailout; + + if (new_data) { + /* обновление конкретного дубликата */ + if (mdbx_is_samedata(old_data, new_data)) + /* если данные совпадают, то ничего делать не надо */ + goto bailout; + } + } else { + /* в old_data буфер для сохранения предыдущего значения */ + if (unlikely(new_data && old_data->iov_base == new_data->iov_base)) + return MDBX_EINVAL; + MDBX_val present_data; + rc = mdbx_cursor_get(&cx.outer, &present_key, &present_data, MDBX_SET_KEY); + if (unlikely(rc != MDBX_SUCCESS)) { + old_data->iov_base = NULL; + old_data->iov_len = 0; + if (rc != MDBX_NOTFOUND || (flags & MDBX_CURRENT)) + goto bailout; + } else if (flags & MDBX_NOOVERWRITE) { + rc = MDBX_KEYEXIST; + *old_data = present_data; + goto bailout; + } else { + MDBX_page *page = cx.outer.mc_pg[cx.outer.mc_top]; + if (txn->mt_dbs[dbi].md_flags & MDBX_DUPSORT) { + if (flags & MDBX_CURRENT) { + /* для не-уникальных ключей позволяем update/delete только если ключ + * один */ + MDBX_node *node = page_node(page, cx.outer.mc_ki[cx.outer.mc_top]); + if (F_ISSET(node_flags(node), F_DUPDATA)) { + mdbx_tassert(txn, XCURSOR_INITED(&cx.outer) && + cx.outer.mc_xcursor->mx_db.md_entries > 1); + if (cx.outer.mc_xcursor->mx_db.md_entries > 1) { + rc = MDBX_EMULTIVAL; + goto bailout; + } + } + /* если данные совпадают, то ничего делать не надо */ + if (new_data && mdbx_is_samedata(&present_data, new_data)) { + *old_data = *new_data; + goto bailout; + } + /* В оригинальной LMDB флажок MDBX_CURRENT здесь приведет + * к замене данных без учета MDBX_DUPSORT сортировки, + * но здесь это в любом случае допустимо, так как мы + * проверили что для ключа есть только одно значение. */ + } else if ((flags & MDBX_NODUPDATA) && + mdbx_is_samedata(&present_data, new_data)) { + /* если данные совпадают и установлен MDBX_NODUPDATA */ + rc = MDBX_KEYEXIST; + goto bailout; + } + } else { + /* если данные совпадают, то ничего делать не надо */ + if (new_data && mdbx_is_samedata(&present_data, new_data)) { + *old_data = *new_data; + goto bailout; + } + flags |= MDBX_CURRENT; + } + + if (IS_DIRTY(page)) { + if (unlikely(old_data->iov_len < present_data.iov_len)) { + old_data->iov_base = NULL; + old_data->iov_len = present_data.iov_len; + rc = MDBX_RESULT_TRUE; + goto bailout; + } + memcpy(old_data->iov_base, present_data.iov_base, present_data.iov_len); + old_data->iov_len = present_data.iov_len; + } else { + *old_data = present_data; + } + } + } + + if (likely(new_data)) + rc = mdbx_cursor_put(&cx.outer, key, new_data, flags); + else + rc = mdbx_cursor_del(&cx.outer, 0); + +bailout: + txn->mt_cursors[dbi] = cx.outer.mc_next; + return rc; +} + +/* Функция сообщает находится ли указанный адрес в "грязной" странице у + * заданной пишущей транзакции. В конечном счете это позволяет избавиться от + * лишнего копирования данных из НЕ-грязных страниц. + * + * "Грязные" страницы - это те, которые уже были изменены в ходе пишущей + * транзакции. Соответственно, какие-либо дальнейшие изменения могут привести + * к перезаписи таких страниц. Поэтому все функции, выполняющие изменения, в + * качестве аргументов НЕ должны получать указатели на данные в таких + * страницах. В свою очередь "НЕ грязные" страницы перед модификацией будут + * скопированы. + * + * Другими словами, данные из "грязных" страниц должны быть либо скопированы + * перед передачей в качестве аргументов для дальнейших модификаций, либо + * отвергнуты на стадии проверки корректности аргументов. + * + * Таким образом, функция позволяет как избавиться от лишнего копирования, + * так и выполнить более полную проверку аргументов. + * + * ВАЖНО: Передаваемый указатель должен указывать на начало данных. Только + * так гарантируется что актуальный заголовок страницы будет физически + * расположен в той-же странице памяти, в том числе для многостраничных + * P_OVERFLOW страниц с длинными данными. */ +int mdbx_is_dirty(const MDBX_txn *txn, const void *ptr) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (txn->mt_flags & MDBX_RDONLY) + return MDBX_RESULT_FALSE; + + const MDBX_env *env = txn->mt_env; + const ptrdiff_t offset = (uint8_t *)ptr - env->me_map; + if (offset >= 0) { + const pgno_t pgno = bytes2pgno(env, offset); + if (likely(pgno < txn->mt_next_pgno)) { + const MDBX_page *page = pgno2page(env, pgno); + if (unlikely(page->mp_pgno != pgno)) { + /* The ptr pointed into middle of a large page, + * not to the beginning of a data. */ + return MDBX_EINVAL; + } + if (unlikely(page->mp_flags & (P_DIRTY | P_LOOSE | P_KEEP))) + return MDBX_RESULT_TRUE; + if (likely(txn->tw.spill_pages == nullptr)) + return MDBX_RESULT_FALSE; + return mdbx_pnl_exist(txn->tw.spill_pages, pgno << 1) ? MDBX_RESULT_TRUE + : MDBX_RESULT_FALSE; + } + if ((size_t)offset < env->me_dxb_mmap.limit) { + /* Указатель адресует что-то в пределах mmap, но за границей + * распределенных страниц. Такое может случится если mdbx_is_dirty() + * вызывает после операции, в ходе которой гразная страница попала + * в loose и затем была возвращена в нераспределенное пространство. */ + return MDBX_RESULT_TRUE; + } + } + + /* Страница вне используемого mmap-диапазона, т.е. либо в функцию был + * передан некорректный адрес, либо адрес в теневой странице, которая была + * выделена посредством malloc(). + * + * Для WRITE_MAP режима такая страница однозначно "не грязная", + * а для режимов без WRITE_MAP следует просматривать списки dirty + * и spilled страниц у каких-либо транзакций (в том числе дочерних). + * + * Поэтому для WRITE_MAP возвращаем false, а для остальных режимов + * всегда true. Такая логика имеет ряд преимуществ: + * - не тратим время на просмотр списков; + * - результат всегда безопасен (может быть ложно-положительным, + * но не ложно-отрицательным); + * - результат не зависит от вложенности транзакций и от относительного + * положения переданной транзакции в этой рекурсии. */ + return (env->me_flags & MDBX_WRITEMAP) ? MDBX_RESULT_FALSE : MDBX_RESULT_TRUE; +} + +int mdbx_dbi_sequence(MDBX_txn *txn, MDBX_dbi dbi, uint64_t *result, + uint64_t increment) { + int rc = check_txn(txn, MDBX_TXN_BLOCKED); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(TXN_DBI_CHANGED(txn, dbi))) + return MDBX_BAD_DBI; + + if (unlikely(txn->mt_dbflags[dbi] & DB_STALE)) { + rc = mdbx_fetch_sdb(txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + } + + MDBX_db *dbs = &txn->mt_dbs[dbi]; + if (likely(result)) + *result = dbs->md_seq; + + if (likely(increment > 0)) { + if (unlikely(txn->mt_flags & MDBX_RDONLY)) + return MDBX_EACCESS; + + uint64_t new = dbs->md_seq + increment; + if (unlikely(new < increment)) + return MDBX_RESULT_TRUE; + + mdbx_tassert(txn, new > dbs->md_seq); + dbs->md_seq = new; + txn->mt_flags |= MDBX_TXN_DIRTY; + txn->mt_dbflags[dbi] |= DB_DIRTY; + } + + return MDBX_SUCCESS; +} + +/*----------------------------------------------------------------------------*/ + +__cold intptr_t mdbx_limits_keysize_max(intptr_t pagesize) { + if (pagesize < 1) + pagesize = (intptr_t)mdbx_syspagesize(); + else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || + pagesize > (intptr_t)MAX_PAGESIZE || + !is_powerof2((size_t)pagesize))) + return (MDBX_EINVAL > 0) ? -MDBX_EINVAL : MDBX_EINVAL; + + return mdbx_maxkey(mdbx_nodemax(pagesize)); +} + +__cold intptr_t mdbx_limits_dbsize_min(intptr_t pagesize) { + if (pagesize < 1) + pagesize = (intptr_t)mdbx_syspagesize(); + else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || + pagesize > (intptr_t)MAX_PAGESIZE || + !is_powerof2((size_t)pagesize))) + return (MDBX_EINVAL > 0) ? -MDBX_EINVAL : MDBX_EINVAL; + + return MIN_PAGENO * pagesize; +} + +__cold intptr_t mdbx_limits_dbsize_max(intptr_t pagesize) { + if (pagesize < 1) + pagesize = (intptr_t)mdbx_syspagesize(); + else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || + pagesize > (intptr_t)MAX_PAGESIZE || + !is_powerof2((size_t)pagesize))) + return (MDBX_EINVAL > 0) ? -MDBX_EINVAL : MDBX_EINVAL; + + const uint64_t limit = MAX_PAGENO * (uint64_t)pagesize; + return (limit < (intptr_t)MAX_MAPSIZE) ? (intptr_t)limit + : (intptr_t)MAX_MAPSIZE; +} + +__cold intptr_t mdbx_limits_txnsize_max(intptr_t pagesize) { + if (pagesize < 1) + pagesize = (intptr_t)mdbx_syspagesize(); + else if (unlikely(pagesize < (intptr_t)MIN_PAGESIZE || + pagesize > (intptr_t)MAX_PAGESIZE || + !is_powerof2((size_t)pagesize))) + return (MDBX_EINVAL > 0) ? -MDBX_EINVAL : MDBX_EINVAL; + + return pagesize * (MDBX_DPL_TXNFULL - 1); +} + +/*** Attribute support functions for Nexenta **********************************/ +#ifdef MDBX_NEXENTA_ATTRS + +static __inline int mdbx_attr_peek(MDBX_val *data, mdbx_attr_t *attrptr) { + if (unlikely(data->iov_len < sizeof(mdbx_attr_t))) + return MDBX_INCOMPATIBLE; + + if (likely(attrptr != NULL)) + *attrptr = *(mdbx_attr_t *)data->iov_base; + data->iov_len -= sizeof(mdbx_attr_t); + data->iov_base = + likely(data->iov_len > 0) ? ((mdbx_attr_t *)data->iov_base) + 1 : NULL; + + return MDBX_SUCCESS; +} + +static __inline int mdbx_attr_poke(MDBX_val *reserved, MDBX_val *data, + mdbx_attr_t attr, unsigned flags) { + mdbx_attr_t *space = reserved->iov_base; + if (flags & MDBX_RESERVE) { + if (likely(data != NULL)) { + data->iov_base = data->iov_len ? space + 1 : NULL; + } + } else { + *space = attr; + if (likely(data != NULL)) { + memcpy(space + 1, data->iov_base, data->iov_len); + } + } + + return MDBX_SUCCESS; +} + +int mdbx_cursor_get_attr(MDBX_cursor *mc, MDBX_val *key, MDBX_val *data, + mdbx_attr_t *attrptr, MDBX_cursor_op op) { + int rc = mdbx_cursor_get(mc, key, data, op); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + return mdbx_attr_peek(data, attrptr); +} + +int mdbx_get_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, + uint64_t *attrptr) { + int rc = mdbx_get(txn, dbi, key, data); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + return mdbx_attr_peek(data, attrptr); +} + +int mdbx_put_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, + mdbx_attr_t attr, unsigned flags) { + MDBX_val reserve; + reserve.iov_base = NULL; + reserve.iov_len = (data ? data->iov_len : 0) + sizeof(mdbx_attr_t); + + int rc = mdbx_put(txn, dbi, key, &reserve, flags | MDBX_RESERVE); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + return mdbx_attr_poke(&reserve, data, attr, flags); +} + +int mdbx_cursor_put_attr(MDBX_cursor *cursor, MDBX_val *key, MDBX_val *data, + mdbx_attr_t attr, unsigned flags) { + MDBX_val reserve; + reserve.iov_base = NULL; + reserve.iov_len = (data ? data->iov_len : 0) + sizeof(mdbx_attr_t); + + int rc = mdbx_cursor_put(cursor, key, &reserve, flags | MDBX_RESERVE); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + return mdbx_attr_poke(&reserve, data, attr, flags); +} + +int mdbx_set_attr(MDBX_txn *txn, MDBX_dbi dbi, MDBX_val *key, MDBX_val *data, + mdbx_attr_t attr) { + if (unlikely(!key || !txn)) + return MDBX_EINVAL; + + if (unlikely(txn->mt_signature != MDBX_MT_SIGNATURE)) + return MDBX_VERSION_MISMATCH; + + if (unlikely(!TXN_DBI_EXIST(txn, dbi, DB_USRVALID))) + return MDBX_EINVAL; + + if (unlikely(txn->mt_flags & (MDBX_RDONLY | MDBX_TXN_BLOCKED))) + return (txn->mt_flags & MDBX_RDONLY) ? MDBX_EACCESS : MDBX_BAD_TXN; + + MDBX_cursor_couple cx; + MDBX_val old_data; + int rc = mdbx_cursor_init(&cx.outer, txn, dbi); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + rc = mdbx_cursor_set(&cx.outer, key, &old_data, MDBX_SET, NULL); + if (unlikely(rc != MDBX_SUCCESS)) { + if (rc == MDBX_NOTFOUND && data) { + cx.outer.mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = &cx.outer; + rc = mdbx_cursor_put_attr(&cx.outer, key, data, attr, 0); + txn->mt_cursors[dbi] = cx.outer.mc_next; + } + return rc; + } + + mdbx_attr_t old_attr = 0; + rc = mdbx_attr_peek(&old_data, &old_attr); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + + if (old_attr == attr && (!data || (data->iov_len == old_data.iov_len && + memcmp(data->iov_base, old_data.iov_base, + old_data.iov_len) == 0))) + return MDBX_SUCCESS; + + cx.outer.mc_next = txn->mt_cursors[dbi]; + txn->mt_cursors[dbi] = &cx.outer; + rc = mdbx_cursor_put_attr(&cx.outer, key, data ? data : &old_data, attr, + MDBX_CURRENT); + txn->mt_cursors[dbi] = cx.outer.mc_next; + return rc; +} +#endif /* MDBX_NEXENTA_ATTRS */ + +/******************************************************************************/ +/* *INDENT-OFF* */ +/* clang-format off */ + +__dll_export +#ifdef __attribute_used__ + __attribute_used__ +#elif defined(__GNUC__) || __has_attribute(__used__) + __attribute__((__used__)) +#endif +#ifdef __attribute_externally_visible__ + __attribute_externally_visible__ +#elif (defined(__GNUC__) && !defined(__clang__)) || \ + __has_attribute(__externally_visible__) + __attribute__((__externally_visible__)) +#endif + const mdbx_build_info mdbx_build = { +#ifdef MDBX_BUILD_TIMESTAMP + MDBX_BUILD_TIMESTAMP +#else + __DATE__ " " __TIME__ +#endif /* MDBX_BUILD_TIMESTAMP */ + + , +#ifdef MDBX_BUILD_TARGET + MDBX_BUILD_TARGET +#else + #if defined(__ANDROID__) + "Android" + #elif defined(__linux__) || defined(__gnu_linux__) + "Linux" + #elif defined(EMSCRIPTEN) || defined(__EMSCRIPTEN__) + "webassembly" + #elif defined(__CYGWIN__) + "CYGWIN" + #elif defined(_WIN64) || defined(_WIN32) || defined(__TOS_WIN__) \ + || defined(__WINDOWS__) + "Windows" + #elif defined(__APPLE__) + #if (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) \ + || (defined(TARGET_IPHONE_SIMULATOR) && TARGET_IPHONE_SIMULATOR) + "iOS" + #else + "MacOS" + #endif + #elif defined(__FreeBSD__) + "FreeBSD" + #elif defined(__DragonFly__) + "DragonFlyBSD" + #elif defined(__NetBSD__) || defined(__NETBSD__) + "NetBSD" + #elif defined(__OpenBSD__) + "OpenBSD" + #elif defined(__bsdi__) + "UnixBSDI" + #elif defined(__MACH__) + "MACH" + #elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) + "HPUX" + #elif defined(_AIX) + "AIX" + #elif defined(__sun) && defined(__SVR4) + "Solaris" + #elif defined(__BSD__) || defined(BSD) + "UnixBSD" + #elif defined(__unix__) || defined(UNIX) || defined(__unix) \ + || defined(__UNIX) || defined(__UNIX__) + "UNIX" + #elif defined(_POSIX_VERSION) + "POSIX" STRINGIFY(_POSIX_VERSION) + #else + "UnknownOS" + #endif /* Target OS */ + + "-" + + #if defined(__amd64__) + "AMD64" + #elif defined(__ia32__) + "IA32" + #elif defined(__e2k__) || defined(__elbrus__) + "Elbrus" + #elif defined(__alpha__) || defined(__alpha) || defined(_M_ALPHA) + "Alpha" + #elif defined(__aarch64__) || defined(_M_ARM64) + "ARM64" + #elif defined(__arm__) || defined(__thumb__) || defined(__TARGET_ARCH_ARM) \ + || defined(__TARGET_ARCH_THUMB) || defined(_ARM) || defined(_M_ARM) \ + || defined(_M_ARMT) || defined(__arm) + "ARM" + #elif defined(__mips64) || defined(__mips64__) || (defined(__mips) && (__mips >= 64)) + "MIPS64" + #elif if defined(__mips__) || defined(__mips) || defined(_R4000) || defined(__MIPS__) + "MIPS" + #elif defined(__hppa64__) || defined(__HPPA64__) || defined(__hppa64) + "PARISC64" + #elif defined(__hppa__) || defined(__HPPA__) || defined(__hppa) + "PARISC" + #elif defined(__ia64__) || defined(__ia64) || defined(_IA64) \ + || defined(__IA64__) || defined(_M_IA64) || defined(__itanium__) + "Itanium" + #elif defined(__powerpc64__) || defined(__ppc64__) || defined(__ppc64) \ + || defined(__powerpc64) || defined(_ARCH_PPC64) + "PowerPC64" + #elif defined(__powerpc__) || defined(__ppc__) || defined(__powerpc) \ + || defined(__ppc) || defined(_ARCH_PPC) || defined(__PPC__) || defined(__POWERPC__) + "PowerPC" + #elif defined(__sparc64__) || defined(__sparc64) + "SPARC64" + #elif defined(__sparc__) || defined(__sparc) + "SPARC" + #elif defined(__s390__) || defined(__s390) || defined(__zarch__) || defined(__zarch) + "S390" + #else + "UnknownARCH" + #endif +#endif /* MDBX_BUILD_TARGET */ + +#ifdef MDBX_BUILD_CONFIG +# if defined(_MSC_VER) +# pragma message("Configuration-depended MDBX_BUILD_CONFIG: " MDBX_BUILD_CONFIG) +# endif + "-" MDBX_BUILD_CONFIG +#endif /* MDBX_BUILD_CONFIG */ + , + "MDBX_DEBUG=" STRINGIFY(MDBX_DEBUG) +#ifdef MDBX_LOGLEVEL_BUILD + " MDBX_LOGLEVEL_BUILD=" STRINGIFY(MDBX_LOGLEVEL_BUILD) +#endif /* MDBX_LOGLEVEL_BUILD */ + " MDBX_WORDBITS=" STRINGIFY(MDBX_WORDBITS) + " BYTE_ORDER=" +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + "LITTLE_ENDIAN" +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + "BIG_ENDIAN" +#else + #error "FIXME: Unsupported byte order" +#endif /* __BYTE_ORDER__ */ + " MDBX_TXN_CHECKPID=" MDBX_TXN_CHECKPID_CONFIG + " MDBX_TXN_CHECKOWNER=" MDBX_TXN_CHECKOWNER_CONFIG + " MDBX_64BIT_ATOMIC=" MDBX_64BIT_ATOMIC_CONFIG + " MDBX_64BIT_CAS=" MDBX_64BIT_CAS_CONFIG + " MDBX_TRUST_RTC=" MDBX_TRUST_RTC_CONFIG +#ifdef __SANITIZE_ADDRESS__ + " SANITIZE_ADDRESS=YES" +#endif /* __SANITIZE_ADDRESS__ */ +#ifdef MDBX_USE_VALGRIND + " MDBX_USE_VALGRIND=YES" +#endif /* MDBX_USE_VALGRIND */ +#ifdef _GNU_SOURCE + " _GNU_SOURCE=YES" +#else + " _GNU_SOURCE=NO" +#endif /* _GNU_SOURCE */ +#ifdef __APPLE__ + " MDBX_OSX_SPEED_INSTEADOF_DURABILITY=" STRINGIFY(MDBX_OSX_SPEED_INSTEADOF_DURABILITY) +#endif /* MacOS */ +#if defined(_WIN32) || defined(_WIN64) + " MDBX_AVOID_CRT=" STRINGIFY(MDBX_AVOID_CRT) + " MDBX_CONFIG_MANUAL_TLS_CALLBACK=" STRINGIFY(MDBX_CONFIG_MANUAL_TLS_CALLBACK) + " MDBX_BUILD_SHARED_LIBRARY=" STRINGIFY(MDBX_BUILD_SHARED_LIBRARY) + " WINVER=" STRINGIFY(WINVER) +#else /* Windows */ + " MDBX_USE_ROBUST=" MDBX_USE_ROBUST_CONFIG + " MDBX_USE_OFDLOCKS=" MDBX_USE_OFDLOCKS_CONFIG +#endif /* !Windows */ +#ifdef MDBX_OSAL_LOCK + " MDBX_OSAL_LOCK=" STRINGIFY(MDBX_OSAL_LOCK) +#endif + " MDBX_CACHELINE_SIZE=" STRINGIFY(MDBX_CACHELINE_SIZE) + " MDBX_CPU_WRITEBACK_IS_COHERENT=" STRINGIFY(MDBX_CPU_WRITEBACK_IS_COHERENT) + " MDBX_UNALIGNED_OK=" STRINGIFY(MDBX_UNALIGNED_OK) + " MDBX_PNL_ASCENDING=" STRINGIFY(MDBX_PNL_ASCENDING) + , +#ifdef MDBX_BUILD_COMPILER + MDBX_BUILD_COMPILER +#else + #ifdef __INTEL_COMPILER + "Intel C/C++ " STRINGIFY(__INTEL_COMPILER) + #elsif defined(__apple_build_version__) + "Apple clang " STRINGIFY(__apple_build_version__) + #elif defined(__ibmxl__) + "IBM clang C " STRINGIFY(__ibmxl_version__) "." STRINGIFY(__ibmxl_release__) + "." STRINGIFY(__ibmxl_modification__) "." STRINGIFY(__ibmxl_ptf_fix_level__) + #elif defined(__clang__) + "clang " STRINGIFY(__clang_version__) + #elif defined(__MINGW64__) + "MINGW-64 " STRINGIFY(__MINGW64_MAJOR_VERSION) "." STRINGIFY(__MINGW64_MINOR_VERSION) + #elif defined(__MINGW32__) + "MINGW-32 " STRINGIFY(__MINGW32_MAJOR_VERSION) "." STRINGIFY(__MINGW32_MINOR_VERSION) + #elif defined(__IBMC__) + "IBM C " STRINGIFY(__IBMC__) + #elif defined(__GNUC__) + "GNU C/C++ " + #ifdef __VERSION__ + __VERSION__ + #else + STRINGIFY(__GNUC__) "." STRINGIFY(__GNUC_MINOR__) "." STRINGIFY(__GNUC_PATCHLEVEL__) + #endif + #elif defined(_MSC_VER) + "MSVC " STRINGIFY(_MSC_FULL_VER) "-" STRINGIFY(_MSC_BUILD) + #else + "Unknown compiler" + #endif +#endif /* MDBX_BUILD_COMPILER */ + , +#ifdef MDBX_BUILD_FLAGS + MDBX_BUILD_FLAGS +#endif /* MDBX_BUILD_FLAGS */ +#ifdef MDBX_BUILD_FLAGS_CONFIG + MDBX_BUILD_FLAGS_CONFIG +#endif /* MDBX_BUILD_FLAGS_CONFIG */ +}; + +#ifdef __SANITIZE_ADDRESS__ +LIBMDBX_API __attribute__((__weak__)) const char *__asan_default_options() { + return "symbolize=1:allow_addr2line=1:" +#ifdef _DEBUG + "debug=1:" +#endif /* _DEBUG */ + "report_globals=1:" + "replace_str=1:replace_intrin=1:" + "malloc_context_size=9:" + "detect_leaks=1:" + "check_printf=1:" + "detect_deadlocks=1:" +#ifndef LTO_ENABLED + "check_initialization_order=1:" +#endif + "detect_stack_use_after_return=1:" + "intercept_tls_get_addr=1:" + "decorate_proc_maps=1:" + "abort_on_error=1"; +} +#endif /* __SANITIZE_ADDRESS__ */ + +/* *INDENT-ON* */ +/* clang-format on */ diff --git a/contrib/db/libmdbx/src/elements/defs.h b/contrib/db/libmdbx/src/elements/defs.h new file mode 100644 index 00000000..aba07f9a --- /dev/null +++ b/contrib/db/libmdbx/src/elements/defs.h @@ -0,0 +1,450 @@ +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once +/* *INDENT-OFF* */ +/* clang-format off */ + +#ifndef __GNUC_PREREQ +# if defined(__GNUC__) && defined(__GNUC_MINOR__) +# define __GNUC_PREREQ(maj, min) \ + ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GNUC_PREREQ(maj, min) (0) +# endif +#endif /* __GNUC_PREREQ */ + +#ifndef __CLANG_PREREQ +# ifdef __clang__ +# define __CLANG_PREREQ(maj,min) \ + ((__clang_major__ << 16) + __clang_minor__ >= ((maj) << 16) + (min)) +# else +# define __CLANG_PREREQ(maj,min) (0) +# endif +#endif /* __CLANG_PREREQ */ + +#ifndef __GLIBC_PREREQ +# if defined(__GLIBC__) && defined(__GLIBC_MINOR__) +# define __GLIBC_PREREQ(maj, min) \ + ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min)) +# else +# define __GLIBC_PREREQ(maj, min) (0) +# endif +#endif /* __GLIBC_PREREQ */ + +#ifndef __has_attribute +# define __has_attribute(x) (0) +#endif + +#ifndef __has_feature +# define __has_feature(x) (0) +#endif + +#ifndef __has_extension +# define __has_extension(x) (0) +#endif + +#ifndef __has_builtin +# define __has_builtin(x) (0) +#endif + +#ifndef __has_warning +# define __has_warning(x) (0) +#endif + +#ifndef __has_include +# define __has_include(x) (0) +#endif + +#if __has_feature(thread_sanitizer) +# define __SANITIZE_THREAD__ 1 +#endif + +#if __has_feature(address_sanitizer) +# define __SANITIZE_ADDRESS__ 1 +#endif + +/*----------------------------------------------------------------------------*/ + +#ifndef __extern_C +# ifdef __cplusplus +# define __extern_C extern "C" +# else +# define __extern_C +# endif +#endif /* __extern_C */ + +#ifndef __cplusplus +# ifndef bool +# define bool _Bool +# endif +# ifndef true +# define true (1) +# endif +# ifndef false +# define false (0) +# endif +#endif + +#if !defined(nullptr) && !defined(__cplusplus) || (__cplusplus < 201103L && !defined(_MSC_VER)) +# define nullptr NULL +#endif + +/*----------------------------------------------------------------------------*/ + +#ifndef __always_inline +# if defined(__GNUC__) || __has_attribute(__always_inline__) +# define __always_inline __inline __attribute__((__always_inline__)) +# elif defined(_MSC_VER) +# define __always_inline __forceinline +# else +# define __always_inline +# endif +#endif /* __always_inline */ + +#ifndef __noinline +# if defined(__GNUC__) || __has_attribute(__noinline__) +# define __noinline __attribute__((__noinline__)) +# elif defined(_MSC_VER) +# define __noinline __declspec(noinline) +# elif defined(__SUNPRO_C) || defined(__sun) || defined(sun) +# define __noinline inline +# elif !defined(__INTEL_COMPILER) +# define __noinline /* FIXME ? */ +# endif +#endif /* __noinline */ + +#ifndef __must_check_result +# if defined(__GNUC__) || __has_attribute(__warn_unused_result__) +# define __must_check_result __attribute__((__warn_unused_result__)) +# else +# define __must_check_result +# endif +#endif /* __must_check_result */ + +#ifndef __maybe_unused +# if defined(__GNUC__) || __has_attribute(__unused__) +# define __maybe_unused __attribute__((__unused__)) +# else +# define __maybe_unused +# endif +#endif /* __maybe_unused */ + +#ifndef __deprecated +# if defined(__GNUC__) || __has_attribute(__deprecated__) +# define __deprecated __attribute__((__deprecated__)) +# elif defined(_MSC_VER) +# define __deprecated __declspec(deprecated) +# else +# define __deprecated +# endif +#endif /* __deprecated */ + +#if !defined(__noop) && !defined(_MSC_VER) +# define __noop(...) do {} while(0) +#endif /* __noop */ + +#ifndef __fallthrough +# if __GNUC_PREREQ(7, 0) || __has_attribute(__fallthrough__) +# define __fallthrough __attribute__((__fallthrough__)) +# else +# define __fallthrough __noop() +# endif +#endif /* __fallthrough */ + +#ifndef __unreachable +# if __GNUC_PREREQ(4,5) +# define __unreachable() __builtin_unreachable() +# elif defined(_MSC_VER) +# define __unreachable() __assume(0) +# else +# define __unreachable() __noop() +# endif +#endif /* __unreachable */ + +#ifndef __prefetch +# if defined(__GNUC__) || defined(__clang__) +# define __prefetch(ptr) __builtin_prefetch(ptr) +# else +# define __prefetch(ptr) __noop(ptr) +# endif +#endif /* __prefetch */ + +#ifndef __noreturn +# if defined(__GNUC__) || __has_attribute(__noreturn__) +# define __noreturn __attribute__((__noreturn__)) +# elif defined(_MSC_VER) +# define __noreturn __declspec(noreturn) +# else +# define __noreturn +# endif +#endif /* __noreturn */ + +#ifndef __nothrow +# if defined(__cplusplus) +# if __cplusplus < 201703L +# define __nothrow throw() +# else +# define __nothrow noexcept(true) +# endif /* __cplusplus */ +# elif defined(__GNUC__) || __has_attribute(__nothrow__) +# define __nothrow __attribute__((__nothrow__)) +# elif defined(_MSC_VER) && defined(__cplusplus) +# define __nothrow __declspec(nothrow) +# else +# define __nothrow +# endif +#endif /* __nothrow */ + +#ifndef __pure_function + /* Many functions have no effects except the return value and their + * return value depends only on the parameters and/or global variables. + * Such a function can be subject to common subexpression elimination + * and loop optimization just as an arithmetic operator would be. + * These functions should be declared with the attribute pure. */ +# if defined(__GNUC__) || __has_attribute(__pure__) +# define __pure_function __attribute__((__pure__)) +# else +# define __pure_function +# endif +#endif /* __pure_function */ + +#ifndef __const_function + /* Many functions do not examine any values except their arguments, + * and have no effects except the return value. Basically this is just + * slightly more strict class than the PURE attribute, since function + * is not allowed to read global memory. + * + * Note that a function that has pointer arguments and examines the + * data pointed to must not be declared const. Likewise, a function + * that calls a non-const function usually must not be const. + * It does not make sense for a const function to return void. */ +# if defined(__GNUC__) || __has_attribute(__const__) +# define __const_function __attribute__((__const__)) +# else +# define __const_function +# endif +#endif /* __const_function */ + +#ifndef __hidden +# if defined(__GNUC__) || __has_attribute(__visibility__) +# define __hidden __attribute__((__visibility__("hidden"))) +# else +# define __hidden +# endif +#endif /* __hidden */ + +#ifndef __optimize +# if defined(__OPTIMIZE__) +# if defined(__clang__) && !__has_attribute(__optimize__) +# define __optimize(ops) +# elif defined(__GNUC__) || __has_attribute(__optimize__) +# define __optimize(ops) __attribute__((__optimize__(ops))) +# else +# define __optimize(ops) +# endif +# else +# define __optimize(ops) +# endif +#endif /* __optimize */ + +#ifndef __hot +# if defined(__OPTIMIZE__) +# if defined(__e2k__) +# define __hot __attribute__((__hot__)) __optimize(3) +# elif defined(__clang__) && !__has_attribute(__hot_) \ + && __has_attribute(__section__) && (defined(__linux__) || defined(__gnu_linux__)) + /* just put frequently used functions in separate section */ +# define __hot __attribute__((__section__("text.hot"))) __optimize("O3") +# elif defined(__GNUC__) || __has_attribute(__hot__) +# define __hot __attribute__((__hot__)) __optimize("O3") +# else +# define __hot __optimize("O3") +# endif +# else +# define __hot +# endif +#endif /* __hot */ + +#ifndef __cold +# if defined(__OPTIMIZE__) +# if defined(__e2k__) +# define __cold __attribute__((__cold__)) __optimize(1) +# elif defined(__clang__) && !__has_attribute(cold) \ + && __has_attribute(__section__) && (defined(__linux__) || defined(__gnu_linux__)) + /* just put infrequently used functions in separate section */ +# define __cold __attribute__((__section__("text.unlikely"))) __optimize("Os") +# elif defined(__GNUC__) || __has_attribute(cold) +# define __cold __attribute__((__cold__)) __optimize("Os") +# else +# define __cold __optimize("Os") +# endif +# else +# define __cold +# endif +#endif /* __cold */ + +#ifndef __flatten +# if defined(__OPTIMIZE__) && (defined(__GNUC__) || __has_attribute(__flatten__)) +# define __flatten __attribute__((__flatten__)) +# else +# define __flatten +# endif +#endif /* __flatten */ + +#ifndef likely +# if (defined(__GNUC__) || defined(__clang__)) && !defined(__COVERITY__) +# define likely(cond) __builtin_expect(!!(cond), 1) +# else +# define likely(x) (x) +# endif +#endif /* likely */ + +#ifndef unlikely +# if (defined(__GNUC__) || defined(__clang__)) && !defined(__COVERITY__) +# define unlikely(cond) __builtin_expect(!!(cond), 0) +# else +# define unlikely(x) (x) +# endif +#endif /* unlikely */ + +/* Workaround for Coverity Scan */ +#if defined(__COVERITY__) && __GNUC_PREREQ(7, 0) && !defined(__cplusplus) +typedef float _Float32; +typedef double _Float32x; +typedef double _Float64; +typedef long double _Float64x; +typedef float _Float128 __attribute__((__mode__(__TF__))); +typedef __complex__ float __cfloat128 __attribute__ ((__mode__ (__TC__))); +typedef _Complex float __cfloat128 __attribute__ ((__mode__ (__TC__))); +#endif /* Workaround for Coverity Scan */ + +#ifndef __printf_args +# if defined(__GNUC__) || __has_attribute(__format__) +# define __printf_args(format_index, first_arg) \ + __attribute__((__format__(printf, format_index, first_arg))) +# else +# define __printf_args(format_index, first_arg) +# endif +#endif /* __printf_args */ + +#ifndef __anonymous_struct_extension__ +# if defined(__GNUC__) +# define __anonymous_struct_extension__ __extension__ +# else +# define __anonymous_struct_extension__ +# endif +#endif /* __anonymous_struct_extension__ */ + +#ifndef __Wpedantic_format_voidptr + static __inline const void* __pure_function + __Wpedantic_format_voidptr(const void* ptr) {return ptr;} +# define __Wpedantic_format_voidptr(ARG) __Wpedantic_format_voidptr(ARG) +#endif /* __Wpedantic_format_voidptr */ + +/*----------------------------------------------------------------------------*/ + +#if defined(MDBX_USE_VALGRIND) +# include +# ifndef VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE + /* LY: available since Valgrind 3.10 */ +# define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s) +# define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s) +# endif +#elif !defined(RUNNING_ON_VALGRIND) +# define VALGRIND_CREATE_MEMPOOL(h,r,z) +# define VALGRIND_DESTROY_MEMPOOL(h) +# define VALGRIND_MEMPOOL_TRIM(h,a,s) +# define VALGRIND_MEMPOOL_ALLOC(h,a,s) +# define VALGRIND_MEMPOOL_FREE(h,a) +# define VALGRIND_MEMPOOL_CHANGE(h,a,b,s) +# define VALGRIND_MAKE_MEM_NOACCESS(a,s) +# define VALGRIND_MAKE_MEM_DEFINED(a,s) +# define VALGRIND_MAKE_MEM_UNDEFINED(a,s) +# define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s) +# define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s) +# define VALGRIND_CHECK_MEM_IS_ADDRESSABLE(a,s) (0) +# define VALGRIND_CHECK_MEM_IS_DEFINED(a,s) (0) +# define RUNNING_ON_VALGRIND (0) +#endif /* MDBX_USE_VALGRIND */ + +#ifdef __SANITIZE_ADDRESS__ +# include +#elif !defined(ASAN_POISON_MEMORY_REGION) +# define ASAN_POISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +# define ASAN_UNPOISON_MEMORY_REGION(addr, size) \ + ((void)(addr), (void)(size)) +#endif /* __SANITIZE_ADDRESS__ */ + +/*----------------------------------------------------------------------------*/ + +#ifndef ARRAY_LENGTH +# ifdef __cplusplus + template + char (&__ArraySizeHelper(T (&array)[N]))[N]; +# define ARRAY_LENGTH(array) (sizeof(::__ArraySizeHelper(array))) +# else +# define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0])) +# endif +#endif /* ARRAY_LENGTH */ + +#ifndef ARRAY_END +# define ARRAY_END(array) (&array[ARRAY_LENGTH(array)]) +#endif /* ARRAY_END */ + +#ifndef STRINGIFY +# define STRINGIFY_HELPER(x) #x +# define STRINGIFY(x) STRINGIFY_HELPER(x) +#endif /* STRINGIFY */ + +#define CONCAT(a,b) a##b +#define XCONCAT(a,b) CONCAT(a,b) + +#ifndef offsetof +# define offsetof(type, member) __builtin_offsetof(type, member) +#endif /* offsetof */ + +#ifndef container_of +# define container_of(ptr, type, member) \ + ((type *)((char *)(ptr) - offsetof(type, member))) +#endif /* container_of */ + +#define MDBX_TETRAD(a, b, c, d) \ + ((uint32_t)(a) << 24 | (uint32_t)(b) << 16 | (uint32_t)(c) << 8 | (d)) + +#define MDBX_STRING_TETRAD(str) MDBX_TETRAD(str[0], str[1], str[2], str[3]) + +#define FIXME "FIXME: " __FILE__ ", " STRINGIFY(__LINE__) + +#ifndef STATIC_ASSERT_MSG +# if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) \ + || __has_feature(c_static_assert) +# define STATIC_ASSERT_MSG(expr, msg) _Static_assert(expr, msg) +# elif defined(static_assert) +# define STATIC_ASSERT_MSG(expr, msg) static_assert(expr, msg) +# elif defined(_MSC_VER) +# include +# define STATIC_ASSERT_MSG(expr, msg) _STATIC_ASSERT(expr) +# else +# define STATIC_ASSERT_MSG(expr, msg) switch (0) {case 0:case (expr):;} +# endif +#endif /* STATIC_ASSERT */ + +#ifndef STATIC_ASSERT +# define STATIC_ASSERT(expr) STATIC_ASSERT_MSG(expr, #expr) +#endif + +/* *INDENT-ON* */ +/* clang-format on */ diff --git a/contrib/db/libmdbx/src/elements/internals.h b/contrib/db/libmdbx/src/elements/internals.h new file mode 100644 index 00000000..6aba3740 --- /dev/null +++ b/contrib/db/libmdbx/src/elements/internals.h @@ -0,0 +1,1367 @@ +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +#pragma once +#ifdef MDBX_CONFIG_H +#include MDBX_CONFIG_H +#endif + +/* *INDENT-OFF* */ +/* clang-format off */ + +/* In case the MDBX_DEBUG is undefined set it corresponding to NDEBUG */ +#ifndef MDBX_DEBUG +# ifdef NDEBUG +# define MDBX_DEBUG 0 +# else +# define MDBX_DEBUG 1 +# endif +#endif + +/* Undefine the NDEBUG if debugging is enforced by MDBX_DEBUG */ +#if MDBX_DEBUG +# undef NDEBUG +#endif + +#define MDBX_OSX_WANNA_DURABILITY 0 /* using fcntl(F_FULLFSYNC) with 5-10 times slowdown */ +#define MDBX_OSX_WANNA_SPEED 1 /* using fsync() with chance of data lost on power failure */ +#ifndef MDBX_OSX_SPEED_INSTEADOF_DURABILITY +# define MDBX_OSX_SPEED_INSTEADOF_DURABILITY MDBX_OSX_WANNA_DURABILITY +#endif + +#ifdef MDBX_ALLOY +/* Amalgamated build */ +# define MDBX_INTERNAL_FUNC static +# define MDBX_INTERNAL_VAR static +#else +/* Non-amalgamated build */ +# define MDBX_INTERNAL_FUNC +# define MDBX_INTERNAL_VAR extern +#endif /* MDBX_ALLOY */ + +#ifndef MDBX_DISABLE_GNU_SOURCE +#define MDBX_DISABLE_GNU_SOURCE 0 +#endif +#if MDBX_DISABLE_GNU_SOURCE +#undef _GNU_SOURCE +#elif defined(__linux__) || defined(__gnu_linux__) +#define _GNU_SOURCE +#endif + +/*----------------------------------------------------------------------------*/ + +/* Should be defined before any includes */ +#ifndef _FILE_OFFSET_BITS +# define _FILE_OFFSET_BITS 64 +#endif + +#ifdef __APPLE__ +#define _DARWIN_C_SOURCE +#endif + +#ifdef _MSC_VER +# if _MSC_VER < 1400 +# error "Microsoft Visual C++ 8.0 (Visual Studio 2005) or later version is required" +# endif +# ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +# endif +#if _MSC_VER > 1800 +# pragma warning(disable : 4464) /* relative include path contains '..' */ +#endif +#if _MSC_VER > 1913 +# pragma warning(disable : 5045) /* Compiler will insert Spectre mitigation... */ +#endif +#pragma warning(disable : 4710) /* 'xyz': function not inlined */ +#pragma warning(disable : 4711) /* function 'xyz' selected for automatic inline expansion */ +#pragma warning(disable : 4201) /* nonstandard extension used : nameless struct / union */ +#pragma warning(disable : 4702) /* unreachable code */ +#pragma warning(disable : 4706) /* assignment within conditional expression */ +#pragma warning(disable : 4127) /* conditional expression is constant */ +#pragma warning(disable : 4324) /* 'xyz': structure was padded due to alignment specifier */ +#pragma warning(disable : 4310) /* cast truncates constant value */ +#pragma warning(disable : 4820) /* bytes padding added after data member for aligment */ +#pragma warning(disable : 4548) /* expression before comma has no effect; expected expression with side - effect */ +#pragma warning(disable : 4366) /* the result of the unary '&' operator may be unaligned */ +#pragma warning(disable : 4200) /* nonstandard extension used: zero-sized array in struct/union */ +#endif /* _MSC_VER (warnings) */ + +#include "../../mdbx.h" +#include "defs.h" + +#if defined(__GNUC__) && !__GNUC_PREREQ(4,2) + /* Actualy libmdbx was not tested with compilers older than GCC from RHEL6. + * But you could remove this #error and try to continue at your own risk. + * In such case please don't rise up an issues related ONLY to old compilers. + */ +# warning "libmdbx required GCC >= 4.2" +#endif + +#if defined(__clang__) && !__CLANG_PREREQ(3,8) + /* Actualy libmdbx was not tested with CLANG older than 3.8. + * But you could remove this #error and try to continue at your own risk. + * In such case please don't rise up an issues related ONLY to old compilers. + */ +# warning "libmdbx required CLANG >= 3.8" +#endif + +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2,12) + /* Actualy libmdbx was not tested with something older than glibc 2.12 (from RHEL6). + * But you could remove this #error and try to continue at your own risk. + * In such case please don't rise up an issues related ONLY to old systems. + */ +# warning "libmdbx required at least GLIBC 2.12." +#endif + +#ifdef __SANITIZE_THREAD__ +# warning "libmdbx don't compatible with ThreadSanitizer, you will get a lot of false-positive issues." +#endif /* __SANITIZE_THREAD__ */ + +#if __has_warning("-Wconstant-logical-operand") +# if defined(__clang__) +# pragma clang diagnostic ignored "-Wconstant-logical-operand" +# elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wconstant-logical-operand" +# else +# pragma warning disable "constant-logical-operand" +# endif +#endif /* -Wconstant-logical-operand */ + +#if defined(__LCC__) && (__LCC__ <= 121) + /* bug #2798 */ +# pragma diag_suppress alignment_reduction_ignored +#elif defined(__ICC) +# pragma warning(disable: 3453 1366) +#elif __has_warning("-Walignment-reduction-ignored") +# if defined(__clang__) +# pragma clang diagnostic ignored "-Walignment-reduction-ignored" +# elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Walignment-reduction-ignored" +# else +# pragma warning disable "alignment-reduction-ignored" +# endif +#endif /* -Walignment-reduction-ignored */ + +#include "osal.h" + +/* *INDENT-ON* */ +/* clang-format on */ + +#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul +#define MDBX_WORDBITS 64 +#else +#define MDBX_WORDBITS 32 +#endif /* MDBX_WORDBITS */ + +#ifndef MDBX_64BIT_ATOMIC +#if MDBX_WORDBITS >= 64 +#define MDBX_64BIT_ATOMIC 1 +#else +#define MDBX_64BIT_ATOMIC 0 +#endif +#define MDBX_64BIT_ATOMIC_CONFIG "AUTO=" STRINGIFY(MDBX_64BIT_ATOMIC) +#else +#define MDBX_64BIT_ATOMIC_CONFIG STRINGIFY(MDBX_64BIT_ATOMIC) +#endif /* MDBX_64BIT_ATOMIC */ + +#ifndef MDBX_64BIT_CAS +#if defined(ATOMIC_LLONG_LOCK_FREE) +#if ATOMIC_LLONG_LOCK_FREE > 1 +#define MDBX_64BIT_CAS 1 +#else +#define MDBX_64BIT_CAS 0 +#endif +#elif defined(__GCC_ATOMIC_LLONG_LOCK_FREE) +#if __GCC_ATOMIC_LLONG_LOCK_FREE > 1 +#define MDBX_64BIT_CAS 1 +#else +#define MDBX_64BIT_CAS 0 +#endif +#elif defined(__CLANG_ATOMIC_LLONG_LOCK_FREE) +#if __CLANG_ATOMIC_LLONG_LOCK_FREE > 1 +#define MDBX_64BIT_CAS 1 +#else +#define MDBX_64BIT_CAS 0 +#endif +#elif defined(_MSC_VER) || defined(__APPLE__) +#define MDBX_64BIT_CAS 1 +#else +#define MDBX_64BIT_CAS MDBX_64BIT_ATOMIC +#endif +#define MDBX_64BIT_CAS_CONFIG "AUTO=" STRINGIFY(MDBX_64BIT_CAS) +#else +#define MDBX_64BIT_CAS_CONFIG STRINGIFY(MDBX_64BIT_CAS) +#endif /* MDBX_64BIT_CAS */ + +/* Some platforms define the EOWNERDEAD error code even though they + * don't support Robust Mutexes. Compile with -DMDBX_USE_ROBUST=0. */ +#ifndef MDBX_USE_ROBUST +/* Howard Chu: Android currently lacks Robust Mutex support */ +#if defined(EOWNERDEAD) && !defined(__ANDROID__) && !defined(__APPLE__) && \ + (!defined(__GLIBC__) || \ + __GLIBC_PREREQ( \ + 2, \ + 10) /* LY: glibc before 2.10 has a troubles with Robust Mutex too. */ \ + || _POSIX_C_SOURCE >= 200809L) +#define MDBX_USE_ROBUST 1 +#else +#define MDBX_USE_ROBUST 0 +#endif +#define MDBX_USE_ROBUST_CONFIG "AUTO=" STRINGIFY(MDBX_USE_ROBUST) +#else +#define MDBX_USE_ROBUST_CONFIG STRINGIFY(MDBX_USE_ROBUST) +#endif /* MDBX_USE_ROBUST */ + +#ifndef MDBX_USE_OFDLOCKS +#if defined(F_OFD_SETLK) && defined(F_OFD_SETLKW) && defined(F_OFD_GETLK) && \ + !defined(MDBX_SAFE4QEMU) +#define MDBX_USE_OFDLOCKS 1 +#else +#define MDBX_USE_OFDLOCKS 0 +#endif +#define MDBX_USE_OFDLOCKS_CONFIG "AUTO=" STRINGIFY(MDBX_USE_OFDLOCKS) +#else +#define MDBX_USE_OFDLOCKS_CONFIG STRINGIFY(MDBX_USE_OFDLOCKS) +#endif /* MDBX_USE_OFDLOCKS */ + +/* Controls checking PID against reuse DB environment after the fork() */ +#ifndef MDBX_TXN_CHECKPID +#if defined(MADV_DONTFORK) || defined(_WIN32) || defined(_WIN64) +/* PID check could be ommited: + * - on Linux when madvise(MADV_DONTFORK) is available. i.e. after the fork() + * mapped pages will not be available for child process. + * - in Windows where fork() not available. */ +#define MDBX_TXN_CHECKPID 0 +#else +#define MDBX_TXN_CHECKPID 1 +#endif +#define MDBX_TXN_CHECKPID_CONFIG "AUTO=" STRINGIFY(MDBX_TXN_CHECKPID) +#else +#define MDBX_TXN_CHECKPID_CONFIG STRINGIFY(MDBX_TXN_CHECKPID) +#endif /* MDBX_TXN_CHECKPID */ + +/* Controls checking transaction owner thread against misuse transactions from + * other threads. */ +#ifndef MDBX_TXN_CHECKOWNER +#define MDBX_TXN_CHECKOWNER 1 +#define MDBX_TXN_CHECKOWNER_CONFIG "AUTO=" STRINGIFY(MDBX_TXN_CHECKOWNER) +#else +#define MDBX_TXN_CHECKOWNER_CONFIG STRINGIFY(MDBX_TXN_CHECKOWNER) +#endif /* MDBX_TXN_CHECKOWNER */ + +#define mdbx_sourcery_anchor XCONCAT(mdbx_sourcery_, MDBX_BUILD_SOURCERY) +#if defined(MDBX_TOOLS) +extern LIBMDBX_API const char *const mdbx_sourcery_anchor; +#endif + +/* Does a system have battery-backed Real-Time Clock or just a fake. */ +#ifndef MDBX_TRUST_RTC +#if defined(__linux__) || defined(__gnu_linux__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) +#define MDBX_TRUST_RTC 0 /* a lot of embedded systems have a fake RTC */ +#else +#define MDBX_TRUST_RTC 1 +#endif +#define MDBX_TRUST_RTC_CONFIG "AUTO=" STRINGIFY(MDBX_TRUST_RTC) +#else +#define MDBX_TRUST_RTC_CONFIG STRINGIFY(MDBX_TRUST_RTC) +#endif /* MDBX_TRUST_RTC */ + +/*----------------------------------------------------------------------------*/ +/* Basic constants and types */ + +/* The minimum number of keys required in a database page. + * Setting this to a larger value will place a smaller bound on the + * maximum size of a data item. Data items larger than this size will + * be pushed into overflow pages instead of being stored directly in + * the B-tree node. This value used to default to 4. With a page size + * of 4096 bytes that meant that any item larger than 1024 bytes would + * go into an overflow page. That also meant that on average 2-3KB of + * each overflow page was wasted space. The value cannot be lower than + * 2 because then there would no longer be a tree structure. With this + * value, items larger than 2KB will go into overflow pages, and on + * average only 1KB will be wasted. */ +#define MDBX_MINKEYS 2 + +/* A stamp that identifies a file as an MDBX file. + * There's nothing special about this value other than that it is easily + * recognizable, and it will reflect any byte order mismatches. */ +#define MDBX_MAGIC UINT64_C(/* 56-bit prime */ 0x59659DBDEF4C11) + +/* The version number for a database's datafile format. */ +#define MDBX_DATA_VERSION 2 +/* The version number for a database's lockfile format. */ +#define MDBX_LOCK_VERSION 3 + +/* handle for the DB used to track free pages. */ +#define FREE_DBI 0 +/* handle for the default DB. */ +#define MAIN_DBI 1 +/* Number of DBs in metapage (free and main) - also hardcoded elsewhere */ +#define CORE_DBS 2 +#define MAX_DBI (INT16_MAX - CORE_DBS) +#if MAX_DBI != MDBX_MAX_DBI +#error "Opps, MAX_DBI != MDBX_MAX_DBI" +#endif + +/* Number of meta pages - also hardcoded elsewhere */ +#define NUM_METAS 3 + +/* A page number in the database. + * + * MDBX uses 32 bit for page numbers. This limits database + * size up to 2^44 bytes, in case of 4K pages. */ +typedef uint32_t pgno_t; +#define PRIaPGNO PRIu32 +#define MAX_PAGENO UINT32_C(0x7FFFffff) +#define MIN_PAGENO NUM_METAS + +/* A transaction ID. */ +typedef uint64_t txnid_t; +#define PRIaTXN PRIi64 +#define MIN_TXNID UINT64_C(1) +/* LY: for testing non-atomic 64-bit txnid on 32-bit arches. + * #define MDBX_TXNID_STEP (UINT32_MAX / 3) */ +#ifndef MDBX_TXNID_STEP +#if MDBX_64BIT_CAS +#define MDBX_TXNID_STEP 1u +#else +#define MDBX_TXNID_STEP 2u +#endif +#endif /* MDBX_TXNID_STEP */ + +/* Used for offsets within a single page. + * Since memory pages are typically 4 or 8KB in size, 12-13 bits, + * this is plenty. */ +typedef uint16_t indx_t; + +#define MEGABYTE ((size_t)1 << 20) + +/*----------------------------------------------------------------------------*/ +/* Core structures for database and shared memory (i.e. format definition) */ +#pragma pack(push, 1) + +typedef union mdbx_safe64 { + volatile uint64_t inconsistent; +#if MDBX_64BIT_ATOMIC + volatile uint64_t atomic; +#endif /* MDBX_64BIT_ATOMIC */ + struct { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + volatile uint32_t low; + volatile uint32_t high; +#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + volatile uint32_t high; + volatile uint32_t low; +#else +#error "FIXME: Unsupported byte order" +#endif /* __BYTE_ORDER__ */ + }; +} mdbx_safe64_t; + +#define SAFE64_INVALID_THRESHOLD UINT64_C(0xffffFFFF00000000) + +/* Information about a single database in the environment. */ +typedef struct MDBX_db { + uint16_t md_flags; /* see mdbx_dbi_open */ + uint16_t md_depth; /* depth of this tree */ + uint32_t md_xsize; /* also ksize for LEAF2 pages */ + pgno_t md_root; /* the root page of this tree */ + pgno_t md_branch_pages; /* number of internal pages */ + pgno_t md_leaf_pages; /* number of leaf pages */ + pgno_t md_overflow_pages; /* number of overflow pages */ + uint64_t md_seq; /* table sequence counter */ + uint64_t md_entries; /* number of data items */ + uint64_t md_mod_txnid; /* txnid of last commited modification */ +} MDBX_db; + +/* database size-related parameters */ +typedef struct mdbx_geo_t { + uint16_t grow; /* datafile growth step in pages */ + uint16_t shrink; /* datafile shrink threshold in pages */ + pgno_t lower; /* minimal size of datafile in pages */ + pgno_t upper; /* maximal size of datafile in pages */ + pgno_t now; /* current size of datafile in pages */ + pgno_t next; /* first unused page in the datafile, + * but actually the file may be shorter. */ +} mdbx_geo_t; + +/* Meta page content. + * A meta page is the start point for accessing a database snapshot. + * Pages 0-1 are meta pages. Transaction N writes meta page (N % 2). */ +typedef struct MDBX_meta { + /* Stamp identifying this as an MDBX file. + * It must be set to MDBX_MAGIC with MDBX_DATA_VERSION. */ + uint64_t mm_magic_and_version; + + /* txnid that committed this page, the first of a two-phase-update pair */ + mdbx_safe64_t mm_txnid_a; + + uint16_t mm_extra_flags; /* extra DB flags, zero (nothing) for now */ + uint8_t mm_validator_id; /* ID of checksum and page validation method, + * zero (nothing) for now */ + uint8_t mm_extra_pagehdr; /* extra bytes in the page header, + * zero (nothing) for now */ + + mdbx_geo_t mm_geo; /* database size-related parameters */ + + MDBX_db mm_dbs[CORE_DBS]; /* first is free space, 2nd is main db */ + /* The size of pages used in this DB */ +#define mm_psize mm_dbs[FREE_DBI].md_xsize +/* Any persistent environment flags, see mdbx_env */ +#define mm_flags mm_dbs[FREE_DBI].md_flags + mdbx_canary mm_canary; + +#define MDBX_DATASIGN_NONE 0u +#define MDBX_DATASIGN_WEAK 1u +#define SIGN_IS_WEAK(sign) ((sign) == MDBX_DATASIGN_WEAK) +#define SIGN_IS_STEADY(sign) ((sign) > MDBX_DATASIGN_WEAK) +#define META_IS_WEAK(meta) SIGN_IS_WEAK((meta)->mm_datasync_sign) +#define META_IS_STEADY(meta) SIGN_IS_STEADY((meta)->mm_datasync_sign) + volatile uint64_t mm_datasync_sign; + + /* txnid that committed this page, the second of a two-phase-update pair */ + mdbx_safe64_t mm_txnid_b; + + /* Number of non-meta pages which were put in GC after COW. May be 0 in case + * DB was previously handled by libmdbx without corresponding feature. + * This value in couple with mr_snapshot_pages_retired allows fast estimation + * of "how much reader is restraining GC recycling". */ + uint64_t mm_pages_retired; +} MDBX_meta; + +/* Common header for all page types. The page type depends on mp_flags. + * + * P_BRANCH and P_LEAF pages have unsorted 'MDBX_node's at the end, with + * sorted mp_ptrs[] entries referring to them. Exception: P_LEAF2 pages + * omit mp_ptrs and pack sorted MDBX_DUPFIXED values after the page header. + * + * P_OVERFLOW records occupy one or more contiguous pages where only the + * first has a page header. They hold the real data of F_BIGDATA nodes. + * + * P_SUBP sub-pages are small leaf "pages" with duplicate data. + * A node with flag F_DUPDATA but not F_SUBDATA contains a sub-page. + * (Duplicate data can also go in sub-databases, which use normal pages.) + * + * P_META pages contain MDBX_meta, the start point of an MDBX snapshot. + * + * Each non-metapage up to MDBX_meta.mm_last_pg is reachable exactly once + * in the snapshot: Either used by a database or listed in a GC record. */ +typedef struct MDBX_page { + union { + struct MDBX_page *mp_next; /* for in-memory list of freed pages */ + uint64_t mp_validator; /* checksum of page content or a txnid during + * which the page has been updated */ + }; + uint16_t mp_leaf2_ksize; /* key size if this is a LEAF2 page */ +#define P_BRANCH 0x01 /* branch page */ +#define P_LEAF 0x02 /* leaf page */ +#define P_OVERFLOW 0x04 /* overflow page */ +#define P_META 0x08 /* meta page */ +#define P_DIRTY 0x10 /* dirty page, also set for P_SUBP pages */ +#define P_LEAF2 0x20 /* for MDBX_DUPFIXED records */ +#define P_SUBP 0x40 /* for MDBX_DUPSORT sub-pages */ +#define P_LOOSE 0x4000 /* page was dirtied then freed, can be reused */ +#define P_KEEP 0x8000 /* leave this page alone during spill */ + uint16_t mp_flags; + union { + struct { + indx_t mp_lower; /* lower bound of free space */ + indx_t mp_upper; /* upper bound of free space */ + }; + uint32_t mp_pages; /* number of overflow pages */ + }; + pgno_t mp_pgno; /* page number */ + + /* dynamic size */ + indx_t mp_ptrs[/* C99 */]; +} MDBX_page; + +/* Size of the page header, excluding dynamic data at the end */ +#define PAGEHDRSZ ((unsigned)offsetof(MDBX_page, mp_ptrs)) + +#pragma pack(pop) + +/* Reader Lock Table + * + * Readers don't acquire any locks for their data access. Instead, they + * simply record their transaction ID in the reader table. The reader + * mutex is needed just to find an empty slot in the reader table. The + * slot's address is saved in thread-specific data so that subsequent + * read transactions started by the same thread need no further locking to + * proceed. + * + * If MDBX_NOTLS is set, the slot address is not saved in thread-specific data. + * No reader table is used if the database is on a read-only filesystem. + * + * Since the database uses multi-version concurrency control, readers don't + * actually need any locking. This table is used to keep track of which + * readers are using data from which old transactions, so that we'll know + * when a particular old transaction is no longer in use. Old transactions + * that have discarded any data pages can then have those pages reclaimed + * for use by a later write transaction. + * + * The lock table is constructed such that reader slots are aligned with the + * processor's cache line size. Any slot is only ever used by one thread. + * This alignment guarantees that there will be no contention or cache + * thrashing as threads update their own slot info, and also eliminates + * any need for locking when accessing a slot. + * + * A writer thread will scan every slot in the table to determine the oldest + * outstanding reader transaction. Any freed pages older than this will be + * reclaimed by the writer. The writer doesn't use any locks when scanning + * this table. This means that there's no guarantee that the writer will + * see the most up-to-date reader info, but that's not required for correct + * operation - all we need is to know the upper bound on the oldest reader, + * we don't care at all about the newest reader. So the only consequence of + * reading stale information here is that old pages might hang around a + * while longer before being reclaimed. That's actually good anyway, because + * the longer we delay reclaiming old pages, the more likely it is that a + * string of contiguous pages can be found after coalescing old pages from + * many old transactions together. */ + +/* The actual reader record, with cacheline padding. */ +typedef struct MDBX_reader { + /* Current Transaction ID when this transaction began, or (txnid_t)-1. + * Multiple readers that start at the same time will probably have the + * same ID here. Again, it's not important to exclude them from + * anything; all we need to know is which version of the DB they + * started from so we can avoid overwriting any data used in that + * particular version. */ + mdbx_safe64_t /* txnid_t */ mr_txnid; + + /* The information we store in a single slot of the reader table. + * In addition to a transaction ID, we also record the process and + * thread ID that owns a slot, so that we can detect stale information, + * e.g. threads or processes that went away without cleaning up. + * + * NOTE: We currently don't check for stale records. + * We simply re-init the table when we know that we're the only process + * opening the lock file. */ + + /* The thread ID of the thread owning this txn. */ +#if MDBX_WORDBITS >= 64 + volatile uint64_t mr_tid; +#else + volatile uint32_t mr_tid; + volatile uint32_t mr_aba_curer; /* CSN to resolve ABA_problems on 32-bit arch, + unused for now */ +#endif + /* The process ID of the process owning this reader txn. */ + volatile uint32_t mr_pid; + + /* The number of pages used in the reader's MVCC snapshot, + * i.e. the value of meta->mm_geo.next and txn->mt_next_pgno */ + volatile pgno_t mr_snapshot_pages_used; + /* Number of retired pages at the time this reader starts transaction. So, + * at any time the difference mm_pages_retired - mr_snapshot_pages_retired + * will give the number of pages which this reader restraining from reuse. */ + volatile uint64_t mr_snapshot_pages_retired; +} MDBX_reader; + +/* The header for the reader table (a memory-mapped lock file). */ +typedef struct MDBX_lockinfo { + /* Stamp identifying this as an MDBX file. + * It must be set to MDBX_MAGIC with with MDBX_LOCK_VERSION. */ + uint64_t mti_magic_and_version; + + /* Format of this lock file. Must be set to MDBX_LOCK_FORMAT. */ + uint32_t mti_os_and_format; + + /* Flags which environment was opened. */ + volatile uint32_t mti_envmode; + + /* Threshold of un-synced-with-disk pages for auto-sync feature, + * zero means no-threshold, i.e. auto-sync is disabled. */ + volatile pgno_t mti_autosync_threshold; + + /* Low 32-bit of txnid with which meta-pages was synced, + * i.e. for sync-polling in the MDBX_NOMETASYNC mode. */ + volatile uint32_t mti_meta_sync_txnid; + + /* Period for timed auto-sync feature, i.e. at the every steady checkpoint + * the mti_unsynced_timeout sets to the current_time + mti_autosync_period. + * The time value is represented in a suitable system-dependent form, for + * example clock_gettime(CLOCK_BOOTTIME) or clock_gettime(CLOCK_MONOTONIC). + * Zero means timed auto-sync is disabled. */ + volatile uint64_t mti_autosync_period; + + /* Marker to distinguish uniqueness of DB/CLK.*/ + volatile uint64_t mti_bait_uniqueness; + + /* The analogue /proc/sys/kernel/random/boot_id or similar to determine + * whether the system was rebooted after the last use of the database files. + * If there was no reboot, but there is no need to rollback to the last + * steady sync point. Zeros mean that no relevant information is available + * from the system. */ + volatile bin128_t mti_bootid; + + alignas(MDBX_CACHELINE_SIZE) /* cacheline ---------------------------------*/ +#ifdef MDBX_OSAL_LOCK + /* Mutex protecting write-txn. */ + MDBX_OSAL_LOCK mti_wmutex; +#endif + + volatile txnid_t mti_oldest_reader; + + /* Timestamp of the last steady sync. Value is represented in a suitable + * system-dependent form, for example clock_gettime(CLOCK_BOOTTIME) or + * clock_gettime(CLOCK_MONOTONIC). */ + volatile uint64_t mti_sync_timestamp; + + /* Number un-synced-with-disk pages for auto-sync feature. */ + volatile pgno_t mti_unsynced_pages; + + /* Number of page which was discarded last time by madvise(MADV_FREE). */ + volatile pgno_t mti_discarded_tail; + + /* Timestamp of the last readers check. */ + volatile uint64_t mti_reader_check_timestamp; + + alignas(MDBX_CACHELINE_SIZE) /* cacheline ---------------------------------*/ + +#ifdef MDBX_OSAL_LOCK + /* Mutex protecting readers registration access to this table. */ + MDBX_OSAL_LOCK mti_rmutex; +#endif + + /* The number of slots that have been used in the reader table. + * This always records the maximum count, it is not decremented + * when readers release their slots. */ + volatile unsigned mti_numreaders; + volatile unsigned mti_readers_refresh_flag; + + alignas(MDBX_CACHELINE_SIZE) /* cacheline ---------------------------------*/ + MDBX_reader mti_readers[/* C99 */]; +} MDBX_lockinfo; + +/* Lockfile format signature: version, features and field layout */ +#define MDBX_LOCK_FORMAT \ + (MDBX_OSAL_LOCK_SIGN * 27733 + (unsigned)sizeof(MDBX_reader) * 13 + \ + (unsigned)offsetof(MDBX_reader, mr_snapshot_pages_used) * 251 + \ + (unsigned)offsetof(MDBX_lockinfo, mti_oldest_reader) * 83 + \ + (unsigned)offsetof(MDBX_lockinfo, mti_numreaders) * 37 + \ + (unsigned)offsetof(MDBX_lockinfo, mti_readers) * 29) + +#define MDBX_DATA_MAGIC ((MDBX_MAGIC << 8) + MDBX_DATA_VERSION) +#define MDBX_DATA_MAGIC_DEVEL ((MDBX_MAGIC << 8) + 255) + +#define MDBX_LOCK_MAGIC ((MDBX_MAGIC << 8) + MDBX_LOCK_VERSION) + +#ifndef MDBX_ASSUME_MALLOC_OVERHEAD +#define MDBX_ASSUME_MALLOC_OVERHEAD (sizeof(void *) * 2u) +#endif /* MDBX_ASSUME_MALLOC_OVERHEAD */ + +/* The maximum size of a database page. + * + * It is 64K, but value-PAGEHDRSZ must fit in MDBX_page.mp_upper. + * + * MDBX will use database pages < OS pages if needed. + * That causes more I/O in write transactions: The OS must + * know (read) the whole page before writing a partial page. + * + * Note that we don't currently support Huge pages. On Linux, + * regular data files cannot use Huge pages, and in general + * Huge pages aren't actually pageable. We rely on the OS + * demand-pager to read our data and page it out when memory + * pressure from other processes is high. So until OSs have + * actual paging support for Huge pages, they're not viable. */ +#define MAX_PAGESIZE MDBX_MAX_PAGESIZE +#define MIN_PAGESIZE MDBX_MIN_PAGESIZE + +#define MIN_MAPSIZE (MIN_PAGESIZE * MIN_PAGENO) +#if defined(_WIN32) || defined(_WIN64) +#define MAX_MAPSIZE32 UINT32_C(0x38000000) +#else +#define MAX_MAPSIZE32 UINT32_C(0x7ff80000) +#endif +#define MAX_MAPSIZE64 (MAX_PAGENO * (uint64_t)MAX_PAGESIZE) + +#if MDBX_WORDBITS >= 64 +#define MAX_MAPSIZE MAX_MAPSIZE64 +#define MDBX_READERS_LIMIT \ + ((65536 - sizeof(MDBX_lockinfo)) / sizeof(MDBX_reader)) +#else +#define MDBX_READERS_LIMIT 1024 +#define MAX_MAPSIZE MAX_MAPSIZE32 +#endif /* MDBX_WORDBITS */ + +/*----------------------------------------------------------------------------*/ +/* Two kind lists of pages (aka PNL) */ + +/* An PNL is an Page Number List, a sorted array of IDs. The first element of + * the array is a counter for how many actual page-numbers are in the list. + * PNLs are sorted in descending order, this allow cut off a page with lowest + * pgno (at the tail) just truncating the list */ +#define MDBX_PNL_ASCENDING 0 +typedef pgno_t *MDBX_PNL; + +#if MDBX_PNL_ASCENDING +#define MDBX_PNL_ORDERED(first, last) ((first) < (last)) +#define MDBX_PNL_DISORDERED(first, last) ((first) >= (last)) +#else +#define MDBX_PNL_ORDERED(first, last) ((first) > (last)) +#define MDBX_PNL_DISORDERED(first, last) ((first) <= (last)) +#endif + +/* List of txnid, only for MDBX_txn.tw.lifo_reclaimed */ +typedef txnid_t *MDBX_TXL; + +/* An Dirty-Page list item is an pgno/pointer pair. */ +typedef union MDBX_DP { + struct { + pgno_t pgno; + MDBX_page *ptr; + }; + struct { + unsigned sorted; + unsigned length; + }; +} MDBX_DP; + +/* An DPL (dirty-page list) is a sorted array of MDBX_DPs. + * The first element's length member is a count of how many actual + * elements are in the array. */ +typedef MDBX_DP *MDBX_DPL; + +/* PNL sizes - likely should be even bigger */ +#define MDBX_PNL_GRANULATE 1024 +#define MDBX_PNL_INITIAL \ + (MDBX_PNL_GRANULATE - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(pgno_t)) +#define MDBX_PNL_MAX \ + ((1u << 24) - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(pgno_t)) +#define MDBX_DPL_TXNFULL (MDBX_PNL_MAX / 4) + +#define MDBX_TXL_GRANULATE 32 +#define MDBX_TXL_INITIAL \ + (MDBX_TXL_GRANULATE - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t)) +#define MDBX_TXL_MAX \ + ((1u << 17) - 2 - MDBX_ASSUME_MALLOC_OVERHEAD / sizeof(txnid_t)) + +#define MDBX_PNL_ALLOCLEN(pl) ((pl)[-1]) +#define MDBX_PNL_SIZE(pl) ((pl)[0]) +#define MDBX_PNL_FIRST(pl) ((pl)[1]) +#define MDBX_PNL_LAST(pl) ((pl)[MDBX_PNL_SIZE(pl)]) +#define MDBX_PNL_BEGIN(pl) (&(pl)[1]) +#define MDBX_PNL_END(pl) (&(pl)[MDBX_PNL_SIZE(pl) + 1]) + +#if MDBX_PNL_ASCENDING +#define MDBX_PNL_LEAST(pl) MDBX_PNL_FIRST(pl) +#define MDBX_PNL_MOST(pl) MDBX_PNL_LAST(pl) +#else +#define MDBX_PNL_LEAST(pl) MDBX_PNL_LAST(pl) +#define MDBX_PNL_MOST(pl) MDBX_PNL_FIRST(pl) +#endif + +#define MDBX_PNL_SIZEOF(pl) ((MDBX_PNL_SIZE(pl) + 1) * sizeof(pgno_t)) +#define MDBX_PNL_IS_EMPTY(pl) (MDBX_PNL_SIZE(pl) == 0) + +/*----------------------------------------------------------------------------*/ +/* Internal structures */ + +/* Auxiliary DB info. + * The information here is mostly static/read-only. There is + * only a single copy of this record in the environment. */ +typedef struct MDBX_dbx { + MDBX_val md_name; /* name of the database */ + MDBX_cmp_func *md_cmp; /* function for comparing keys */ + MDBX_cmp_func *md_dcmp; /* function for comparing data items */ +} MDBX_dbx; + +/* A database transaction. + * Every operation requires a transaction handle. */ +struct MDBX_txn { +#define MDBX_MT_SIGNATURE UINT32_C(0x93D53A31) + size_t mt_signature; + MDBX_txn *mt_parent; /* parent of a nested txn */ + /* Nested txn under this txn, set together with flag MDBX_TXN_HAS_CHILD */ + MDBX_txn *mt_child; + mdbx_geo_t mt_geo; + /* next unallocated page */ +#define mt_next_pgno mt_geo.next + /* corresponding to the current size of datafile */ +#define mt_end_pgno mt_geo.now + + /* Transaction Flags */ + /* mdbx_txn_begin() flags */ +#define MDBX_TXN_BEGIN_FLAGS \ + (MDBX_NOMETASYNC | MDBX_NOSYNC | MDBX_MAPASYNC | MDBX_RDONLY | MDBX_TRYTXN) + /* internal txn flags */ +#define MDBX_TXN_FINISHED 0x01 /* txn is finished or never began */ +#define MDBX_TXN_ERROR 0x02 /* txn is unusable after an error */ +#define MDBX_TXN_DIRTY 0x04 /* must write, even if dirty list is empty */ +#define MDBX_TXN_SPILLS 0x08 /* txn or a parent has spilled pages */ +#define MDBX_TXN_HAS_CHILD 0x10 /* txn has an MDBX_txn.mt_child */ + /* most operations on the txn are currently illegal */ +#define MDBX_TXN_BLOCKED \ + (MDBX_TXN_FINISHED | MDBX_TXN_ERROR | MDBX_TXN_HAS_CHILD) + unsigned mt_flags; + /* The ID of this transaction. IDs are integers incrementing from 1. + * Only committed write transactions increment the ID. If a transaction + * aborts, the ID may be re-used by the next writer. */ + txnid_t mt_txnid; + MDBX_env *mt_env; /* the DB environment */ + /* Array of records for each DB known in the environment. */ + MDBX_dbx *mt_dbxs; + /* Array of MDBX_db records for each known DB */ + MDBX_db *mt_dbs; + /* Array of sequence numbers for each DB handle */ + unsigned *mt_dbiseqs; + + /* Transaction DB Flags */ +#define DB_DIRTY MDBX_TBL_DIRTY /* DB was written in this txn */ +#define DB_STALE MDBX_TBL_STALE /* Named-DB record is older than txnID */ +#define DB_FRESH MDBX_TBL_FRESH /* Named-DB handle opened in this txn */ +#define DB_CREAT MDBX_TBL_CREAT /* Named-DB handle created in this txn */ +#define DB_VALID 0x10 /* DB handle is valid, see also MDBX_VALID */ +#define DB_USRVALID 0x20 /* As DB_VALID, but not set for FREE_DBI */ +#define DB_DUPDATA 0x40 /* DB is MDBX_DUPSORT data */ +#define DB_AUDITED 0x80 /* Internal flag for accounting during audit */ + /* In write txns, array of cursors for each DB */ + MDBX_cursor **mt_cursors; + /* Array of flags for each DB */ + uint8_t *mt_dbflags; + /* Number of DB records in use, or 0 when the txn is finished. + * This number only ever increments until the txn finishes; we + * don't decrement it when individual DB handles are closed. */ + MDBX_dbi mt_numdbs; + size_t mt_owner; /* thread ID that owns this transaction */ + mdbx_canary mt_canary; + + union { + struct { + /* For read txns: This thread/txn's reader table slot, or NULL. */ + MDBX_reader *reader; + } to; + struct { + pgno_t *reclaimed_pglist; /* Reclaimed GC pages */ + txnid_t last_reclaimed; /* ID of last used record */ + pgno_t loose_refund_wl /* FIXME: describe */; + /* dirtylist room: Dirty array size - dirty pages visible to this txn. + * Includes ancestor txns' dirty pages not hidden by other txns' + * dirty/spilled pages. Thus commit(nested txn) has room to merge + * dirtylist into mt_parent after freeing hidden mt_parent pages. */ + unsigned dirtyroom; + /* For write txns: Modified pages. Sorted when not MDBX_WRITEMAP. */ + MDBX_DPL dirtylist; + /* The list of reclaimed txns from GC */ + MDBX_TXL lifo_reclaimed; + /* The list of pages that became unused during this transaction. */ + MDBX_PNL retired_pages; + /* The list of loose pages that became unused and may be reused + * in this transaction, linked through `mp_next`. */ + MDBX_page *loose_pages; + /* Number of loose pages (tw.loose_pages) */ + unsigned loose_count; + /* Number of retired to parent pages (tw.retired2parent_pages) */ + unsigned retired2parent_count; + /* The list of parent's txn dirty pages that retired (became unused) + * in this transaction, linked through `mp_next`. */ + MDBX_page *retired2parent_pages; + /* The sorted list of dirty pages we temporarily wrote to disk + * because the dirty list was full. page numbers in here are + * shifted left by 1, deleted slots have the LSB set. */ + MDBX_PNL spill_pages; + } tw; + }; +}; + +/* Enough space for 2^32 nodes with minimum of 2 keys per node. I.e., plenty. + * At 4 keys per node, enough for 2^64 nodes, so there's probably no need to + * raise this on a 64 bit machine. */ +#if MDBX_WORDBITS >= 64 +#define CURSOR_STACK 28 +#else +#define CURSOR_STACK 20 +#endif + +struct MDBX_xcursor; + +/* Cursors are used for all DB operations. + * A cursor holds a path of (page pointer, key index) from the DB + * root to a position in the DB, plus other state. MDBX_DUPSORT + * cursors include an xcursor to the current data item. Write txns + * track their cursors and keep them up to date when data moves. + * Exception: An xcursor's pointer to a P_SUBP page can be stale. + * (A node with F_DUPDATA but no F_SUBDATA contains a subpage). */ +struct MDBX_cursor { +#define MDBX_MC_SIGNATURE UINT32_C(0xFE05D5B1) +#define MDBX_MC_READY4CLOSE UINT32_C(0x2817A047) +#define MDBX_MC_WAIT4EOT UINT32_C(0x90E297A7) + uint32_t mc_signature; + /* The database handle this cursor operates on */ + MDBX_dbi mc_dbi; + /* Next cursor on this DB in this txn */ + MDBX_cursor *mc_next; + /* Backup of the original cursor if this cursor is a shadow */ + MDBX_cursor *mc_backup; + /* Context used for databases with MDBX_DUPSORT, otherwise NULL */ + struct MDBX_xcursor *mc_xcursor; + /* The transaction that owns this cursor */ + MDBX_txn *mc_txn; + /* The database record for this cursor */ + MDBX_db *mc_db; + /* The database auxiliary record for this cursor */ + MDBX_dbx *mc_dbx; + /* The mt_dbflag for this database */ + uint8_t *mc_dbflag; + uint16_t mc_snum; /* number of pushed pages */ + uint16_t mc_top; /* index of top page, normally mc_snum-1 */ + /* Cursor state flags. */ +#define C_INITIALIZED 0x01 /* cursor has been initialized and is valid */ +#define C_EOF 0x02 /* No more data */ +#define C_SUB 0x04 /* Cursor is a sub-cursor */ +#define C_DEL 0x08 /* last op was a cursor_del */ +#define C_UNTRACK 0x10 /* Un-track cursor when closing */ +#define C_RECLAIMING 0x20 /* GC lookup is prohibited */ +#define C_GCFREEZE 0x40 /* reclaimed_pglist must not be updated */ + unsigned mc_flags; /* see mdbx_cursor */ + MDBX_page *mc_pg[CURSOR_STACK]; /* stack of pushed pages */ + indx_t mc_ki[CURSOR_STACK]; /* stack of page indices */ +}; + +/* Context for sorted-dup records. + * We could have gone to a fully recursive design, with arbitrarily + * deep nesting of sub-databases. But for now we only handle these + * levels - main DB, optional sub-DB, sorted-duplicate DB. */ +typedef struct MDBX_xcursor { + /* A sub-cursor for traversing the Dup DB */ + MDBX_cursor mx_cursor; + /* The database record for this Dup DB */ + MDBX_db mx_db; + /* The auxiliary DB record for this Dup DB */ + MDBX_dbx mx_dbx; + /* The mt_dbflag for this Dup DB */ + uint8_t mx_dbflag; +} MDBX_xcursor; + +typedef struct MDBX_cursor_couple { + MDBX_cursor outer; + MDBX_xcursor inner; +} MDBX_cursor_couple; + +/* The database environment. */ +struct MDBX_env { +#define MDBX_ME_SIGNATURE UINT32_C(0x9A899641) + size_t me_signature; + mdbx_mmap_t me_dxb_mmap; /* The main data file */ +#define me_map me_dxb_mmap.dxb +#define me_fd me_dxb_mmap.fd + mdbx_mmap_t me_lck_mmap; /* The lock file */ +#define me_lfd me_lck_mmap.fd +#define me_lck me_lck_mmap.lck + + /* Failed to update the meta page. Probably an I/O error. */ +#define MDBX_FATAL_ERROR UINT32_C(0x80000000) + /* Additional flag for mdbx_sync_locked() */ +#define MDBX_SHRINK_ALLOWED UINT32_C(0x40000000) + /* Some fields are initialized. */ +#define MDBX_ENV_ACTIVE UINT32_C(0x20000000) + /* me_txkey is set */ +#define MDBX_ENV_TXKEY UINT32_C(0x10000000) + uint32_t me_flags; /* see mdbx_env */ + unsigned me_psize; /* DB page size, inited from me_os_psize */ + unsigned me_psize2log; /* log2 of DB page size */ + unsigned me_os_psize; /* OS page size, from mdbx_syspagesize() */ + unsigned me_maxreaders; /* size of the reader table */ + mdbx_fastmutex_t me_dbi_lock; + MDBX_dbi me_numdbs; /* number of DBs opened */ + MDBX_dbi me_maxdbs; /* size of the DB table */ + uint32_t me_pid; /* process ID of this env */ + mdbx_thread_key_t me_txkey; /* thread-key for readers */ + char *me_path; /* path to the DB files */ + void *me_pbuf; /* scratch area for DUPSORT put() */ + MDBX_txn *me_txn; /* current write transaction */ + MDBX_txn *me_txn0; /* prealloc'd write transaction */ +#ifdef MDBX_OSAL_LOCK + MDBX_OSAL_LOCK *me_wmutex; /* write-txn mutex */ +#endif + MDBX_dbx *me_dbxs; /* array of static DB info */ + uint16_t *me_dbflags; /* array of flags from MDBX_db.md_flags */ + unsigned *me_dbiseqs; /* array of dbi sequence numbers */ + volatile txnid_t *me_oldest; /* ID of oldest reader last time we looked */ + MDBX_page *me_dpages; /* list of malloc'd blocks for re-use */ + /* PNL of pages that became unused in a write txn */ + MDBX_PNL me_retired_pages; + /* MDBX_DP of pages written during a write txn. Length MDBX_DPL_TXNFULL. */ + MDBX_DPL me_dirtylist; + /* Number of freelist items that can fit in a single overflow page */ + unsigned me_maxgc_ov1page; + /* Max size of a node on a page */ + unsigned me_nodemax; + unsigned me_maxkey_limit; /* max size of a key */ + uint32_t me_live_reader; /* have liveness lock in reader table */ + void *me_userctx; /* User-settable context */ + volatile uint64_t *me_sync_timestamp; + volatile uint64_t *me_autosync_period; + volatile pgno_t *me_unsynced_pages; + volatile pgno_t *me_autosync_threshold; + volatile pgno_t *me_discarded_tail; + volatile uint32_t *me_meta_sync_txnid; + MDBX_oom_func *me_oom_func; /* Callback for kicking laggard readers */ + struct { +#ifdef MDBX_OSAL_LOCK + MDBX_OSAL_LOCK wmutex; +#endif + txnid_t oldest; + uint64_t sync_timestamp; + uint64_t autosync_period; + pgno_t autosync_pending; + pgno_t autosync_threshold; + pgno_t discarded_tail; + uint32_t meta_sync_txnid; + } me_lckless_stub; +#if MDBX_DEBUG + MDBX_assert_func *me_assert_func; /* Callback for assertion failures */ +#endif +#ifdef MDBX_USE_VALGRIND + int me_valgrind_handle; +#endif +#if defined(MDBX_USE_VALGRIND) || defined(__SANITIZE_ADDRESS__) + pgno_t me_poison_edge; +#endif + MDBX_env *me_lcklist_next; + + /* struct me_dbgeo used for accepting db-geo params from user for the new + * database creation, i.e. when mdbx_env_set_geometry() was called before + * mdbx_env_open(). */ + struct { + size_t lower; /* minimal size of datafile */ + size_t upper; /* maximal size of datafile */ + size_t now; /* current size of datafile */ + size_t grow; /* step to grow datafile */ + size_t shrink; /* threshold to shrink datafile */ + } me_dbgeo; + +#if defined(_WIN32) || defined(_WIN64) + MDBX_srwlock me_remap_guard; + /* Workaround for LockFileEx and WriteFile multithread bug */ + CRITICAL_SECTION me_windowsbug_lock; +#else + mdbx_fastmutex_t me_remap_guard; +#endif +}; + +/*----------------------------------------------------------------------------*/ +/* Debug and Logging stuff */ + +#define MDBX_RUNTIME_FLAGS_INIT \ + ((MDBX_DEBUG) > 0) * MDBX_DBG_ASSERT + ((MDBX_DEBUG) > 1) * MDBX_DBG_AUDIT + +#ifndef mdbx_runtime_flags /* avoid override from tools */ +MDBX_INTERNAL_VAR uint8_t mdbx_runtime_flags; +#endif +#ifndef mdbx_runtime_flags /* avoid override from tools */ +MDBX_INTERNAL_VAR uint8_t mdbx_loglevel; +#endif +MDBX_INTERNAL_VAR MDBX_debug_func *mdbx_debug_logger; + +MDBX_INTERNAL_FUNC void mdbx_debug_log(int type, const char *function, int line, + const char *fmt, ...) + __printf_args(4, 5); + +MDBX_INTERNAL_FUNC void mdbx_panic(const char *fmt, ...) __printf_args(1, 2); + +#if MDBX_DEBUG + +#define mdbx_assert_enabled() unlikely(mdbx_runtime_flags &MDBX_DBG_ASSERT) + +#define mdbx_audit_enabled() unlikely(mdbx_runtime_flags &MDBX_DBG_AUDIT) + +#ifdef MDBX_LOGLEVEL_BUILD +#define mdbx_log_enabled(msg) \ + (msg <= MDBX_LOGLEVEL_BUILD && unlikely(msg <= mdbx_loglevel)) +#else +#define mdbx_log_enabled(msg) unlikely(msg <= mdbx_loglevel) +#endif /* MDBX_LOGLEVEL_BUILD */ + +#else /* MDBX_DEBUG */ + +#define mdbx_audit_enabled() (0) + +#if !defined(NDEBUG) || defined(MDBX_FORCE_ASSERT) +#define mdbx_assert_enabled() (1) +#else +#define mdbx_assert_enabled() (0) +#endif /* NDEBUG */ + +#ifdef MDBX_LOGLEVEL_BUILD +#define mdbx_log_enabled(msg) (msg <= MDBX_LOGLEVEL_BUILD) +#else +#define mdbx_log_enabled(msg) (0) +#endif /* MDBX_LOGLEVEL_BUILD */ + +#endif /* MDBX_DEBUG */ + +MDBX_INTERNAL_FUNC void mdbx_assert_fail(const MDBX_env *env, const char *msg, + const char *func, int line); + +#define mdbx_debug_extra(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_EXTRA)) \ + mdbx_debug_log(MDBX_LOG_EXTRA, __func__, __LINE__, fmt, __VA_ARGS__); \ + } while (0) + +#define mdbx_debug_extra_print(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_EXTRA)) \ + mdbx_debug_log(MDBX_LOG_EXTRA, NULL, 0, fmt, __VA_ARGS__); \ + } while (0) + +#define mdbx_trace(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_TRACE)) \ + mdbx_debug_log(MDBX_LOG_TRACE, __func__, __LINE__, fmt "\n", \ + __VA_ARGS__); \ + } while (0) + +#define mdbx_debug(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_DEBUG)) \ + mdbx_debug_log(MDBX_LOG_DEBUG, __func__, __LINE__, fmt "\n", \ + __VA_ARGS__); \ + } while (0) + +#define mdbx_debug_print(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_DEBUG)) \ + mdbx_debug_log(MDBX_LOG_DEBUG, NULL, 0, fmt, __VA_ARGS__); \ + } while (0) + +#define mdbx_verbose(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_VERBOSE)) \ + mdbx_debug_log(MDBX_LOG_VERBOSE, __func__, __LINE__, fmt "\n", \ + __VA_ARGS__); \ + } while (0) + +#define mdbx_notice(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_NOTICE)) \ + mdbx_debug_log(MDBX_LOG_NOTICE, __func__, __LINE__, fmt "\n", \ + __VA_ARGS__); \ + } while (0) + +#define mdbx_warning(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_WARN)) \ + mdbx_debug_log(MDBX_LOG_WARN, __func__, __LINE__, fmt "\n", \ + __VA_ARGS__); \ + } while (0) + +#define mdbx_error(fmt, ...) \ + do { \ + if (mdbx_log_enabled(MDBX_LOG_ERROR)) \ + mdbx_debug_log(MDBX_LOG_ERROR, __func__, __LINE__, fmt "\n", \ + __VA_ARGS__); \ + } while (0) + +#define mdbx_fatal(fmt, ...) \ + mdbx_debug_log(MDBX_LOG_FATAL, __func__, __LINE__, fmt "\n", __VA_ARGS__); + +#define mdbx_ensure_msg(env, expr, msg) \ + do { \ + if (unlikely(!(expr))) \ + mdbx_assert_fail(env, msg, __func__, __LINE__); \ + } while (0) + +#define mdbx_ensure(env, expr) mdbx_ensure_msg(env, expr, #expr) + +/* assert(3) variant in environment context */ +#define mdbx_assert(env, expr) \ + do { \ + if (mdbx_assert_enabled()) \ + mdbx_ensure(env, expr); \ + } while (0) + +/* assert(3) variant in cursor context */ +#define mdbx_cassert(mc, expr) mdbx_assert((mc)->mc_txn->mt_env, expr) + +/* assert(3) variant in transaction context */ +#define mdbx_tassert(txn, expr) mdbx_assert((txn)->mt_env, expr) + +#ifndef MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#undef assert +#define assert(expr) mdbx_assert(NULL, expr) +#endif + +/*----------------------------------------------------------------------------*/ +/* Internal prototypes */ + +MDBX_INTERNAL_FUNC int mdbx_reader_check0(MDBX_env *env, int rlocked, + int *dead); +MDBX_INTERNAL_FUNC int mdbx_rthc_alloc(mdbx_thread_key_t *key, + MDBX_reader *begin, MDBX_reader *end); +MDBX_INTERNAL_FUNC void mdbx_rthc_remove(const mdbx_thread_key_t key); + +MDBX_INTERNAL_FUNC void mdbx_rthc_global_init(void); +MDBX_INTERNAL_FUNC void mdbx_rthc_global_dtor(void); +MDBX_INTERNAL_FUNC void mdbx_rthc_thread_dtor(void *ptr); + +#define MDBX_IS_ERROR(rc) \ + ((rc) != MDBX_RESULT_TRUE && (rc) != MDBX_RESULT_FALSE) + +/* Internal error codes, not exposed outside libmdbx */ +#define MDBX_NO_ROOT (MDBX_LAST_ERRCODE + 10) + +/* Debuging output value of a cursor DBI: Negative in a sub-cursor. */ +#define DDBI(mc) \ + (((mc)->mc_flags & C_SUB) ? -(int)(mc)->mc_dbi : (int)(mc)->mc_dbi) + +/* Key size which fits in a DKBUF. */ +#define DKBUF_MAXKEYSIZE 511 /* FIXME */ + +#if MDBX_DEBUG +#define DKBUF char _kbuf[DKBUF_MAXKEYSIZE * 4 + 2] +#define DKEY(x) mdbx_dump_val(x, _kbuf, DKBUF_MAXKEYSIZE * 2 + 1) +#define DVAL(x) \ + mdbx_dump_val(x, _kbuf + DKBUF_MAXKEYSIZE * 2 + 1, DKBUF_MAXKEYSIZE * 2 + 1) +#else +#define DKBUF ((void)(0)) +#define DKEY(x) ("-") +#define DVAL(x) ("-") +#endif + +/* An invalid page number. + * Mainly used to denote an empty tree. */ +#define P_INVALID (~(pgno_t)0) + +/* Test if the flags f are set in a flag word w. */ +#define F_ISSET(w, f) (((w) & (f)) == (f)) + +/* Round n up to an even number. */ +#define EVEN(n) (((n) + 1U) & -2) /* sign-extending -2 to match n+1U */ + +/* Default size of memory map. + * This is certainly too small for any actual applications. Apps should + * always set the size explicitly using mdbx_env_set_mapsize(). */ +#define DEFAULT_MAPSIZE MEGABYTE + +/* Number of slots in the reader table. + * This value was chosen somewhat arbitrarily. The 61 is a prime number, + * and such readers plus a couple mutexes fit into single 4KB page. + * Applications should set the table size using mdbx_env_set_maxreaders(). */ +#define DEFAULT_READERS 61 + +/* Test if a page is a leaf page */ +#define IS_LEAF(p) (((p)->mp_flags & P_LEAF) != 0) +/* Test if a page is a LEAF2 page */ +#define IS_LEAF2(p) unlikely(((p)->mp_flags & P_LEAF2) != 0) +/* Test if a page is a branch page */ +#define IS_BRANCH(p) (((p)->mp_flags & P_BRANCH) != 0) +/* Test if a page is an overflow page */ +#define IS_OVERFLOW(p) unlikely(((p)->mp_flags & P_OVERFLOW) != 0) +/* Test if a page is a sub page */ +#define IS_SUBP(p) (((p)->mp_flags & P_SUBP) != 0) +/* Test if a page is dirty */ +#define IS_DIRTY(p) (((p)->mp_flags & P_DIRTY) != 0) + +#define PAGETYPE(p) ((p)->mp_flags & (P_BRANCH | P_LEAF | P_LEAF2 | P_OVERFLOW)) + +/* Header for a single key/data pair within a page. + * Used in pages of type P_BRANCH and P_LEAF without P_LEAF2. + * We guarantee 2-byte alignment for 'MDBX_node's. + * + * mn_lo and mn_hi are used for data size on leaf nodes, and for child + * pgno on branch nodes. On 64 bit platforms, mn_flags is also used + * for pgno. (Branch nodes have no flags). Lo and hi are in host byte + * order in case some accesses can be optimized to 32-bit word access. + * + * Leaf node flags describe node contents. F_BIGDATA says the node's + * data part is the page number of an overflow page with actual data. + * F_DUPDATA and F_SUBDATA can be combined giving duplicate data in + * a sub-page/sub-database, and named databases (just F_SUBDATA). */ +typedef struct MDBX_node { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + union { + struct { + uint16_t mn_lo, mn_hi; /* part of data size or pgno */ + }; + uint32_t mn_dsize; + uint32_t mn_pgno32; + }; + uint8_t mn_flags; /* see mdbx_node flags */ + uint8_t mn_extra; + uint16_t mn_ksize; /* key size */ +#else + uint16_t mn_ksize; /* key size */ + uint8_t mn_extra; + uint8_t mn_flags; /* see mdbx_node flags */ + union { + uint32_t mn_pgno32; + uint32_t mn_dsize; + struct { + uint16_t mn_hi, mn_lo; /* part of data size or pgno */ + }; + }; +#endif + + /* mdbx_node Flags */ +#define F_BIGDATA 0x01 /* data put on overflow page */ +#define F_SUBDATA 0x02 /* data is a sub-database */ +#define F_DUPDATA 0x04 /* data has duplicates */ + + /* valid flags for mdbx_node_add() */ +#define NODE_ADD_FLAGS (F_DUPDATA | F_SUBDATA | MDBX_RESERVE | MDBX_APPEND) + uint8_t mn_data[/* C99 */]; /* key and data are appended here */ +} MDBX_node; + +#define MDBX_VALID 0x8000 /* DB handle is valid, for me_dbflags */ +#define PERSISTENT_FLAGS (0xffff & ~(MDBX_VALID)) +/* mdbx_dbi_open() flags */ +#define VALID_FLAGS \ + (MDBX_REVERSEKEY | MDBX_DUPSORT | MDBX_INTEGERKEY | MDBX_DUPFIXED | \ + MDBX_INTEGERDUP | MDBX_REVERSEDUP | MDBX_CREATE) + +/* max number of pages to commit in one writev() call */ +#define MDBX_COMMIT_PAGES 64 +#if defined(IOV_MAX) && IOV_MAX < MDBX_COMMIT_PAGES /* sysconf(_SC_IOV_MAX) */ +#undef MDBX_COMMIT_PAGES +#define MDBX_COMMIT_PAGES IOV_MAX +#endif + +/* + * / + * | -1, a < b + * CMP2INT(a,b) = < 0, a == b + * | 1, a > b + * \ + */ +#if 1 +/* LY: fast enough on most systems */ +#define CMP2INT(a, b) (((b) > (a)) ? -1 : (a) > (b)) +#else +#define CMP2INT(a, b) (((a) > (b)) - ((b) > (a))) +#endif + +/* Do not spill pages to disk if txn is getting full, may fail instead */ +#define MDBX_NOSPILL 0x8000 + +static __maybe_unused __inline pgno_t pgno_add(pgno_t base, pgno_t augend) { + assert(base <= MAX_PAGENO); + return (augend < MAX_PAGENO - base) ? base + augend : MAX_PAGENO; +} + +static __maybe_unused __inline pgno_t pgno_sub(pgno_t base, pgno_t subtrahend) { + assert(base >= MIN_PAGENO); + return (subtrahend < base - MIN_PAGENO) ? base - subtrahend : MIN_PAGENO; +} + +static __maybe_unused __inline void mdbx_jitter4testing(bool tiny) { +#if MDBX_DEBUG + if (MDBX_DBG_JITTER & mdbx_runtime_flags) + mdbx_osal_jitter(tiny); +#else + (void)tiny; +#endif +} diff --git a/contrib/db/libmdbx/src/elements/lck-posix.c b/contrib/db/libmdbx/src/elements/lck-posix.c new file mode 100644 index 00000000..147778da --- /dev/null +++ b/contrib/db/libmdbx/src/elements/lck-posix.c @@ -0,0 +1,551 @@ +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "internals.h" + +/*----------------------------------------------------------------------------*/ +/* global constructor/destructor */ + +#if defined(__linux__) || defined(__gnu_linux__) +#include +#ifndef MDBX_ALLOY +uint32_t mdbx_linux_kernel_version; +#endif /* MDBX_ALLOY */ +#endif /* Linux */ + +static __cold __attribute__((__constructor__)) void +mdbx_global_constructor(void) { +#if defined(__linux__) || defined(__gnu_linux__) + struct utsname buffer; + if (uname(&buffer) == 0) { + int i = 0; + char *p = buffer.release; + while (*p && i < 4) { + if (*p >= '0' && *p <= '9') { + long number = strtol(p, &p, 10); + if (number > 0) { + if (number > 255) + number = 255; + mdbx_linux_kernel_version += number << (24 - i * 8); + } + ++i; + } else { + ++p; + } + } + } +#endif /* Linux */ + + mdbx_rthc_global_init(); +} + +static __cold __attribute__((__destructor__)) void +mdbx_global_destructor(void) { + mdbx_rthc_global_dtor(); +} + +/*----------------------------------------------------------------------------*/ +/* lck */ + +/* Описание реализации блокировок для POSIX & Linux: + * + * lck-файл отображается в память, в нём организуется таблица читателей и + * размещаются совместно используемые posix-мьютексы (futex). Посредством + * этих мьютексов (см struct MDBX_lockinfo) реализуются: + * - Блокировка таблицы читателей для регистрации, + * т.е. функции mdbx_rdt_lock() и mdbx_rdt_unlock(). + * - Блокировка БД для пишущих транзакций, + * т.е. функции mdbx_txn_lock() и mdbx_txn_unlock(). + * + * Остальной функционал реализуется отдельно посредством файловых блокировок: + * - Первоначальный захват БД в режиме exclusive/shared и последующий перевод + * в операционный режим, функции mdbx_lck_seize() и mdbx_lck_downgrade(). + * - Проверка присутствие процессов-читателей, + * т.е. функции mdbx_rpid_set(), mdbx_rpid_clear() и mdbx_rpid_check(). + * + * Для блокировки файлов используется fcntl(F_SETLK), так как: + * - lockf() оперирует только эксклюзивной блокировкой и требует + * открытия файла в RW-режиме. + * - flock() не гарантирует атомарности при смене блокировок + * и оперирует только всем файлом целиком. + * - Для контроля процессов-читателей используются однобайтовые + * range-блокировки lck-файла посредством fcntl(F_SETLK). При этом + * в качестве позиции используется pid процесса-читателя. + * - Для первоначального захвата и shared/exclusive выполняется блокировка + * основного файла БД и при успехе lck-файла. + * + * ---------------------------------------------------------------------------- + * УДЕРЖИВАЕМЫЕ БЛОКИРОВКИ В ЗАВИСИМОСТИ ОТ РЕЖИМА И СОСТОЯНИЯ + * + * Эксклюзивный режим без lck-файла: + * = заблокирован весь dxb-файл посредством F_RDLCK или F_WRLCK, + * в зависимости от MDBX_RDONLY. + * + * Не-операционный режим на время пере-инициализации и разрушении lck-файла: + * = F_WRLCK блокировка первого байта lck-файла, другие процессы ждут её + * снятия при получении F_RDLCK через F_SETLKW. + * - блокировки dxb-файла могут меняться до снятие эксклюзивной блокировки + * lck-файла: + * + для НЕ-эксклюзивного режима блокировка pid-байта в dxb-файле + * посредством F_RDLCK или F_WRLCK, в зависимости от MDBX_RDONLY. + * + для ЭКСКЛЮЗИВНОГО режима блокировка pid-байта всего dxb-файла + * посредством F_RDLCK или F_WRLCK, в зависимости от MDBX_RDONLY. + * + * ОПЕРАЦИОННЫЙ режим с lck-файлом: + * = F_RDLCK блокировка первого байта lck-файла, другие процессы не могут + * получить F_WRLCK и таким образом видят что БД используется. + * + F_WRLCK блокировка pid-байта в clk-файле после первой транзакции чтения. + * + для НЕ-эксклюзивного режима блокировка pid-байта в dxb-файле + * посредством F_RDLCK или F_WRLCK, в зависимости от MDBX_RDONLY. + * + для ЭКСКЛЮЗИВНОГО режима блокировка pid-байта всего dxb-файла + * посредством F_RDLCK или F_WRLCK, в зависимости от MDBX_RDONLY. + */ + +#if MDBX_USE_OFDLOCKS +static int op_setlk, op_setlkw, op_getlk; +static void __cold choice_fcntl() { + assert(!op_setlk && !op_setlkw && !op_getlk); + if ((mdbx_runtime_flags & MDBX_DBG_LEGACY_MULTIOPEN) == 0 +#if defined(__linux__) || defined(__gnu_linux__) + && mdbx_linux_kernel_version > + 0x030f0000 /* OFD locks are available since 3.15, but engages here + only for 3.16 and larer kernels (LTS) for reliability reasons */ +#endif /* linux */ + ) { + op_setlk = F_OFD_SETLK; + op_setlkw = F_OFD_SETLKW; + op_getlk = F_OFD_GETLK; + return; + } + op_setlk = F_SETLK; + op_setlkw = F_SETLKW; + op_getlk = F_GETLK; +} +#else +#define op_setlk F_SETLK +#define op_setlkw F_SETLKW +#define op_getlk F_GETLK +#endif /* MDBX_USE_OFDLOCKS */ + +#ifndef OFF_T_MAX +#define OFF_T_MAX \ + ((sizeof(off_t) > 4 ? INT64_MAX : INT32_MAX) & ~(size_t)0xffff) +#endif + +static int lck_op(mdbx_filehandle_t fd, int cmd, int lck, off_t offset, + off_t len) { + mdbx_jitter4testing(true); + for (;;) { + struct flock lock_op; + memset(&lock_op, 0, sizeof(lock_op)); + lock_op.l_type = lck; + lock_op.l_whence = SEEK_SET; + lock_op.l_start = offset; + lock_op.l_len = len; + int rc = fcntl(fd, cmd, &lock_op); + mdbx_jitter4testing(true); + if (rc != -1) { + if (cmd == op_getlk) { + /* Checks reader by pid. Returns: + * MDBX_RESULT_TRUE - if pid is live (unable to acquire lock) + * MDBX_RESULT_FALSE - if pid is dead (lock acquired). */ + return (lock_op.l_type == F_UNLCK) ? MDBX_RESULT_FALSE + : MDBX_RESULT_TRUE; + } + return MDBX_SUCCESS; + } + rc = errno; + if (rc != EINTR || cmd == op_setlkw) { + mdbx_assert(nullptr, MDBX_IS_ERROR(rc)); + return rc; + } + } +} + +MDBX_INTERNAL_FUNC int mdbx_rpid_set(MDBX_env *env) { + assert(env->me_lfd != INVALID_HANDLE_VALUE); + assert(env->me_pid > 0); + if (unlikely(mdbx_getpid() != env->me_pid)) + return MDBX_PANIC; + return lck_op(env->me_lfd, op_setlk, F_WRLCK, env->me_pid, 1); +} + +MDBX_INTERNAL_FUNC int mdbx_rpid_clear(MDBX_env *env) { + assert(env->me_lfd != INVALID_HANDLE_VALUE); + assert(env->me_pid > 0); + return lck_op(env->me_lfd, op_setlk, F_UNLCK, env->me_pid, 1); +} + +MDBX_INTERNAL_FUNC int mdbx_rpid_check(MDBX_env *env, uint32_t pid) { + assert(env->me_lfd != INVALID_HANDLE_VALUE); + assert(pid > 0); + return lck_op(env->me_lfd, op_getlk, F_WRLCK, pid, 1); +} + +/*---------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC int __cold mdbx_lck_seize(MDBX_env *env) { + assert(env->me_fd != INVALID_HANDLE_VALUE); + if (unlikely(mdbx_getpid() != env->me_pid)) + return MDBX_PANIC; +#if MDBX_USE_OFDLOCKS + if (unlikely(op_setlk == 0)) + choice_fcntl(); +#endif /* MDBX_USE_OFDLOCKS */ + + int rc; + if (env->me_lfd == INVALID_HANDLE_VALUE) { + /* LY: without-lck mode (e.g. exclusive or on read-only filesystem) */ + rc = + lck_op(env->me_fd, op_setlk, + (env->me_flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX); + if (rc != MDBX_SUCCESS) { + mdbx_error("%s(%s) failed: errcode %u", __func__, "without-lck", rc); + mdbx_assert(env, MDBX_IS_ERROR(rc)); + return rc; + } + return MDBX_RESULT_TRUE /* Done: return with exclusive locking. */; + } + + /* Firstly try to get exclusive locking. */ + rc = lck_op(env->me_lfd, op_setlk, F_WRLCK, 0, 1); + if (rc == MDBX_SUCCESS) { + continue_dxb_exclusive: + rc = + lck_op(env->me_fd, op_setlk, + (env->me_flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX); + if (rc == MDBX_SUCCESS) + return MDBX_RESULT_TRUE /* Done: return with exclusive locking. */; + + /* the cause may be a collision with POSIX's file-lock recovery. */ + if (!(rc == EAGAIN || rc == EACCES || rc == EBUSY || rc == EWOULDBLOCK || + rc == EDEADLK)) { + mdbx_error("%s(%s) failed: errcode %u", __func__, "dxb-exclusive", rc); + mdbx_assert(env, MDBX_IS_ERROR(rc)); + return rc; + } + + /* Fallback to lck-shared */ + rc = lck_op(env->me_lfd, op_setlk, F_RDLCK, 0, 1); + if (rc != MDBX_SUCCESS) { + mdbx_error("%s(%s) failed: errcode %u", __func__, "fallback-shared", rc); + mdbx_assert(env, MDBX_IS_ERROR(rc)); + return rc; + } + /* Done: return with shared locking. */ + return MDBX_RESULT_FALSE; + } + + /* Wait for lck-shared now. */ + /* Here may be await during transient processes, for instance until another + * competing process doesn't call lck_downgrade(). */ + rc = lck_op(env->me_lfd, op_setlkw, F_RDLCK, 0, 1); + if (rc != MDBX_SUCCESS) { + mdbx_error("%s(%s) failed: errcode %u", __func__, "try-shared", rc); + mdbx_assert(env, MDBX_IS_ERROR(rc)); + return rc; + } + + /* Lock against another process operating in without-lck or exclusive mode. */ + rc = + lck_op(env->me_fd, op_setlk, + (env->me_flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, env->me_pid, 1); + if (rc != MDBX_SUCCESS) { + mdbx_error("%s(%s) failed: errcode %u", __func__, + "lock-against-without-lck", rc); + mdbx_assert(env, MDBX_IS_ERROR(rc)); + return rc; + } + + /* got shared, retry exclusive */ + rc = lck_op(env->me_lfd, op_setlk, F_WRLCK, 0, 1); + if (rc == MDBX_SUCCESS) + goto continue_dxb_exclusive; + + if (rc == EAGAIN || rc == EACCES || rc == EBUSY || rc == EWOULDBLOCK || + rc == EDEADLK) + return MDBX_RESULT_FALSE /* Done: exclusive is unavailable, + but shared locks are alive. */ + ; + + mdbx_error("%s(%s) failed: errcode %u", __func__, "try-exclusive", rc); + mdbx_assert(env, MDBX_IS_ERROR(rc)); + return rc; +} + +MDBX_INTERNAL_FUNC int mdbx_lck_downgrade(MDBX_env *env) { + assert(env->me_lfd != INVALID_HANDLE_VALUE); + if (unlikely(mdbx_getpid() != env->me_pid)) + return MDBX_PANIC; + + int rc = MDBX_SUCCESS; + if ((env->me_flags & MDBX_EXCLUSIVE) == 0) { + rc = lck_op(env->me_fd, op_setlk, F_UNLCK, 0, env->me_pid); + if (rc == MDBX_SUCCESS) + rc = lck_op(env->me_fd, op_setlk, F_UNLCK, env->me_pid + 1, + OFF_T_MAX - env->me_pid - 1); + } + if (rc == MDBX_SUCCESS) + rc = lck_op(env->me_lfd, op_setlk, F_RDLCK, 0, 1); + if (unlikely(rc != 0)) { + mdbx_error("%s(%s) failed: errcode %u", __func__, "lck", rc); + assert(MDBX_IS_ERROR(rc)); + } + return rc; +} + +MDBX_INTERNAL_FUNC int __cold mdbx_lck_destroy(MDBX_env *env, + MDBX_env *inprocess_neighbor) { + if (unlikely(mdbx_getpid() != env->me_pid)) + return MDBX_PANIC; + + int rc = MDBX_SUCCESS; + if (env->me_lfd != INVALID_HANDLE_VALUE && !inprocess_neighbor && + env->me_lck && + /* try get exclusive access */ + lck_op(env->me_lfd, op_setlk, F_WRLCK, 0, OFF_T_MAX) == 0 && + lck_op(env->me_fd, op_setlk, + (env->me_flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, 0, OFF_T_MAX)) { + mdbx_verbose("%s: got exclusive, drown mutexes", __func__); + rc = pthread_mutex_destroy(&env->me_lck->mti_rmutex); + if (rc == 0) + rc = pthread_mutex_destroy(&env->me_lck->mti_wmutex); + mdbx_assert(env, rc == 0); + if (rc == 0) { + memset(env->me_lck, 0x81, sizeof(MDBX_lockinfo)); + msync(env->me_lck, env->me_os_psize, MS_ASYNC); + } + mdbx_jitter4testing(false); + } + + /* 1) POSIX's fcntl() locks (i.e. when op_setlk == F_SETLK) should be restored + * after file was closed. + * + * 2) File locks would be released (by kernel) while the file-descriptors will + * be closed. But to avoid false-positive EACCESS and EDEADLK from the kernel, + * locks should be released here explicitly with properly order. */ + + /* close dxb and restore lock */ + if (env->me_fd != INVALID_HANDLE_VALUE) { + if (unlikely(close(env->me_fd) != 0) && rc == MDBX_SUCCESS) + rc = errno; + env->me_fd = INVALID_HANDLE_VALUE; + if (op_setlk == F_SETLK && inprocess_neighbor && rc == MDBX_SUCCESS) { + /* restore file-lock */ + rc = lck_op( + inprocess_neighbor->me_fd, F_SETLKW, + (inprocess_neighbor->me_flags & MDBX_RDONLY) ? F_RDLCK : F_WRLCK, + (inprocess_neighbor->me_flags & MDBX_EXCLUSIVE) + ? 0 + : inprocess_neighbor->me_pid, + (inprocess_neighbor->me_flags & MDBX_EXCLUSIVE) ? OFF_T_MAX : 1); + } + } + + /* close clk and restore locks */ + if (env->me_lfd != INVALID_HANDLE_VALUE) { + if (unlikely(close(env->me_lfd) != 0) && rc == MDBX_SUCCESS) + rc = errno; + env->me_lfd = INVALID_HANDLE_VALUE; + if (op_setlk == F_SETLK && inprocess_neighbor && rc == MDBX_SUCCESS) { + /* restore file-locks */ + rc = lck_op(inprocess_neighbor->me_lfd, F_SETLKW, F_RDLCK, 0, 1); + if (rc == MDBX_SUCCESS && inprocess_neighbor->me_live_reader) + rc = mdbx_rpid_set(inprocess_neighbor); + } + } + + if (inprocess_neighbor && rc != MDBX_SUCCESS) + inprocess_neighbor->me_flags |= MDBX_FATAL_ERROR; + return rc; +} + +/*---------------------------------------------------------------------------*/ + +static int mdbx_mutex_failed(MDBX_env *env, pthread_mutex_t *mutex, + const int rc); + +MDBX_INTERNAL_FUNC int __cold mdbx_lck_init(MDBX_env *env, + MDBX_env *inprocess_neighbor, + int global_uniqueness_flag) { + if (inprocess_neighbor) + return MDBX_SUCCESS /* currently don't need any initialization + if LCK already opened/used inside current process */ + ; + + /* FIXME: Unfortunately, there is no other reliable way but to long testing + * on each platform. On the other hand, behavior like FreeBSD is incorrect + * and we can expect it to be rare. Moreover, even on FreeBSD without + * additional in-process initialization, the probability of an problem + * occurring is vanishingly small, and the symptom is a return of EINVAL + * while locking a mutex. In other words, in the worst case, the problem + * results in an EINVAL error at the start of the transaction, but NOT data + * loss, nor database corruption, nor other fatal troubles. Thus, the code + * below I am inclined to think the workaround for erroneous platforms (like + * FreeBSD), rather than a defect of libmdbx. */ +#if defined(__FreeBSD__) + /* seems that shared mutexes on FreeBSD required in-process initialization */ + (void)global_uniqueness_flag; +#else + /* shared mutexes on many other platforms (including Darwin and Linux's + * futexes) doesn't need any addition in-process initialization */ + if (global_uniqueness_flag != MDBX_RESULT_TRUE) + return MDBX_SUCCESS; +#endif + + pthread_mutexattr_t ma; + int rc = pthread_mutexattr_init(&ma); + if (rc) + return rc; + + rc = pthread_mutexattr_setpshared(&ma, PTHREAD_PROCESS_SHARED); + if (rc) + goto bailout; + +#if MDBX_USE_ROBUST +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 12) && \ + !defined(pthread_mutex_consistent) && _POSIX_C_SOURCE < 200809L + rc = pthread_mutexattr_setrobust_np(&ma, PTHREAD_MUTEX_ROBUST_NP); +#else + rc = pthread_mutexattr_setrobust(&ma, PTHREAD_MUTEX_ROBUST); +#endif + if (rc) + goto bailout; +#endif /* MDBX_USE_ROBUST */ + +#if _POSIX_C_SOURCE >= 199506L && !defined(MDBX_SAFE4QEMU) + rc = pthread_mutexattr_setprotocol(&ma, PTHREAD_PRIO_INHERIT); + if (rc == ENOTSUP) + rc = pthread_mutexattr_setprotocol(&ma, PTHREAD_PRIO_NONE); + if (rc) + goto bailout; +#endif /* PTHREAD_PRIO_INHERIT */ + + rc = pthread_mutexattr_settype(&ma, PTHREAD_MUTEX_ERRORCHECK); + if (rc) + goto bailout; + + rc = pthread_mutex_init(&env->me_lck->mti_rmutex, &ma); + if (rc) + goto bailout; + rc = pthread_mutex_init(&env->me_lck->mti_wmutex, &ma); + +bailout: + pthread_mutexattr_destroy(&ma); + return rc; +} + +static int mdbx_robust_lock(MDBX_env *env, pthread_mutex_t *mutex) { + mdbx_jitter4testing(true); + int rc = pthread_mutex_lock(mutex); + if (unlikely(rc != 0)) + rc = mdbx_mutex_failed(env, mutex, rc); + return rc; +} + +static int mdbx_robust_trylock(MDBX_env *env, pthread_mutex_t *mutex) { + mdbx_jitter4testing(true); + int rc = pthread_mutex_trylock(mutex); + if (unlikely(rc != 0 && rc != EBUSY)) + rc = mdbx_mutex_failed(env, mutex, rc); + return (rc != EBUSY) ? rc : MDBX_BUSY; +} + +static int mdbx_robust_unlock(MDBX_env *env, pthread_mutex_t *mutex) { + int rc = pthread_mutex_unlock(mutex); + mdbx_jitter4testing(true); + if (unlikely(rc != 0)) + env->me_flags |= MDBX_FATAL_ERROR; + return rc; +} + +MDBX_INTERNAL_FUNC int mdbx_rdt_lock(MDBX_env *env) { + mdbx_trace("%s", ">>"); + int rc = mdbx_robust_lock(env, &env->me_lck->mti_rmutex); + mdbx_trace("<< rc %d", rc); + return rc; +} + +MDBX_INTERNAL_FUNC void mdbx_rdt_unlock(MDBX_env *env) { + mdbx_trace("%s", ">>"); + int rc = mdbx_robust_unlock(env, &env->me_lck->mti_rmutex); + mdbx_trace("<< rc %d", rc); + if (unlikely(MDBX_IS_ERROR(rc))) + mdbx_panic("%s() failed: errcode %d\n", __func__, rc); +} + +int mdbx_txn_lock(MDBX_env *env, bool dontwait) { + mdbx_trace("%s", ">>"); + int rc = dontwait ? mdbx_robust_trylock(env, env->me_wmutex) + : mdbx_robust_lock(env, env->me_wmutex); + mdbx_trace("<< rc %d", rc); + return MDBX_IS_ERROR(rc) ? rc : MDBX_SUCCESS; +} + +void mdbx_txn_unlock(MDBX_env *env) { + mdbx_trace("%s", ">>"); + int rc = mdbx_robust_unlock(env, env->me_wmutex); + mdbx_trace("<< rc %d", rc); + if (unlikely(MDBX_IS_ERROR(rc))) + mdbx_panic("%s() failed: errcode %d\n", __func__, rc); +} + +static int __cold mdbx_mutex_failed(MDBX_env *env, pthread_mutex_t *mutex, + const int err) { + int rc = err; +#if MDBX_USE_ROBUST + if (err == EOWNERDEAD) { + /* We own the mutex. Clean up after dead previous owner. */ + + int rlocked = (env->me_lck && mutex == &env->me_lck->mti_rmutex); + rc = MDBX_SUCCESS; + if (!rlocked) { + if (unlikely(env->me_txn)) { + /* env is hosed if the dead thread was ours */ + env->me_flags |= MDBX_FATAL_ERROR; + env->me_txn = NULL; + rc = MDBX_PANIC; + } + } + mdbx_notice("%cmutex owner died, %s", (rlocked ? 'r' : 'w'), + (rc ? "this process' env is hosed" : "recovering")); + + int check_rc = mdbx_reader_check0(env, rlocked, NULL); + check_rc = (check_rc == MDBX_SUCCESS) ? MDBX_RESULT_TRUE : check_rc; + +#if defined(__GLIBC__) && !__GLIBC_PREREQ(2, 12) && \ + !defined(pthread_mutex_consistent) && _POSIX_C_SOURCE < 200809L + int mreco_rc = pthread_mutex_consistent_np(mutex); +#else + int mreco_rc = pthread_mutex_consistent(mutex); +#endif + check_rc = (mreco_rc == 0) ? check_rc : mreco_rc; + + if (unlikely(mreco_rc)) + mdbx_error("mutex recovery failed, %s", mdbx_strerror(mreco_rc)); + + rc = (rc == MDBX_SUCCESS) ? check_rc : rc; + if (MDBX_IS_ERROR(rc)) + pthread_mutex_unlock(mutex); + return rc; + } +#else + (void)mutex; +#endif /* MDBX_USE_ROBUST */ + + mdbx_error("mutex (un)lock failed, %s", mdbx_strerror(err)); + if (rc != EDEADLK) + env->me_flags |= MDBX_FATAL_ERROR; + return rc; +} diff --git a/contrib/db/libmdbx/src/elements/lck-windows.c b/contrib/db/libmdbx/src/elements/lck-windows.c new file mode 100644 index 00000000..5d74bb8c --- /dev/null +++ b/contrib/db/libmdbx/src/elements/lck-windows.c @@ -0,0 +1,777 @@ +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "internals.h" + +/* PREAMBLE FOR WINDOWS: + * + * We are not concerned for performance here. + * If you are running Windows a performance could NOT be the goal. + * Otherwise please use Linux. + * + * Regards, + * LY + */ + +static void mdbx_winnt_import(void); + +#if MDBX_BUILD_SHARED_LIBRARY +#if MDBX_AVOID_CRT && defined(NDEBUG) +/* DEBUG/CHECKED builds still require MSVC's CRT for runtime checks. + * + * Define dll's entry point only for Release build when NDEBUG is defined and + * MDBX_AVOID_CRT=ON. if the entry point isn't defined then MSVC's will + * automatically use DllMainCRTStartup() from CRT library, which also + * automatically call DllMain() from our mdbx.dll */ +#pragma comment(linker, "/ENTRY:DllMain") +#endif /* MDBX_AVOID_CRT */ + +BOOL APIENTRY DllMain(HANDLE module, DWORD reason, LPVOID reserved) +#else +#if !MDBX_CONFIG_MANUAL_TLS_CALLBACK +static +#endif /* !MDBX_CONFIG_MANUAL_TLS_CALLBACK */ + void NTAPI + mdbx_dll_handler(PVOID module, DWORD reason, PVOID reserved) +#endif /* MDBX_BUILD_SHARED_LIBRARY */ +{ + (void)reserved; + switch (reason) { + case DLL_PROCESS_ATTACH: + mdbx_winnt_import(); + mdbx_rthc_global_init(); + break; + case DLL_PROCESS_DETACH: + mdbx_rthc_global_dtor(); + break; + + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + mdbx_rthc_thread_dtor(module); + break; + } +#if MDBX_BUILD_SHARED_LIBRARY + return TRUE; +#endif +} + +#if !MDBX_BUILD_SHARED_LIBRARY && !MDBX_CONFIG_MANUAL_TLS_CALLBACK +/* *INDENT-OFF* */ +/* clang-format off */ +#if defined(_MSC_VER) +# pragma const_seg(push) +# pragma data_seg(push) + +# ifdef _WIN64 + /* kick a linker to create the TLS directory if not already done */ +# pragma comment(linker, "/INCLUDE:_tls_used") + /* Force some symbol references. */ +# pragma comment(linker, "/INCLUDE:mdbx_tls_anchor") + /* specific const-segment for WIN64 */ +# pragma const_seg(".CRT$XLB") + const +# else + /* kick a linker to create the TLS directory if not already done */ +# pragma comment(linker, "/INCLUDE:__tls_used") + /* Force some symbol references. */ +# pragma comment(linker, "/INCLUDE:_mdbx_tls_anchor") + /* specific data-segment for WIN32 */ +# pragma data_seg(".CRT$XLB") +# endif + + __declspec(allocate(".CRT$XLB")) PIMAGE_TLS_CALLBACK mdbx_tls_anchor = mdbx_dll_handler; +# pragma data_seg(pop) +# pragma const_seg(pop) + +#elif defined(__GNUC__) +# ifdef _WIN64 + const +# endif + PIMAGE_TLS_CALLBACK mdbx_tls_anchor __attribute__((__section__(".CRT$XLB"), used)) = mdbx_dll_handler; +#else +# error FIXME +#endif +/* *INDENT-ON* */ +/* clang-format on */ +#endif /* !MDBX_BUILD_SHARED_LIBRARY && !MDBX_CONFIG_MANUAL_TLS_CALLBACK */ + +/*----------------------------------------------------------------------------*/ + +#define LCK_SHARED 0 +#define LCK_EXCLUSIVE LOCKFILE_EXCLUSIVE_LOCK +#define LCK_WAITFOR 0 +#define LCK_DONTWAIT LOCKFILE_FAIL_IMMEDIATELY + +static __inline BOOL flock(mdbx_filehandle_t fd, DWORD flags, uint64_t offset, + size_t bytes) { + OVERLAPPED ov; + ov.hEvent = 0; + ov.Offset = (DWORD)offset; + ov.OffsetHigh = HIGH_DWORD(offset); + return LockFileEx(fd, flags, 0, (DWORD)bytes, HIGH_DWORD(bytes), &ov); +} + +static __inline BOOL funlock(mdbx_filehandle_t fd, uint64_t offset, + size_t bytes) { + return UnlockFile(fd, (DWORD)offset, HIGH_DWORD(offset), (DWORD)bytes, + HIGH_DWORD(bytes)); +} + +/*----------------------------------------------------------------------------*/ +/* global `write` lock for write-txt processing, + * exclusive locking both meta-pages) */ + +#define LCK_MAXLEN (1u + (size_t)(MAXSSIZE_T)) +#define LCK_META_OFFSET 0 +#define LCK_META_LEN 0x10000u +#define LCK_BODY_OFFSET LCK_META_LEN +#define LCK_BODY_LEN (LCK_MAXLEN - LCK_BODY_OFFSET) +#define LCK_META LCK_META_OFFSET, LCK_META_LEN +#define LCK_BODY LCK_BODY_OFFSET, LCK_BODY_LEN +#define LCK_WHOLE 0, LCK_MAXLEN + +int mdbx_txn_lock(MDBX_env *env, bool dontwait) { + if (dontwait) { + if (!TryEnterCriticalSection(&env->me_windowsbug_lock)) + return MDBX_BUSY; + } else { + EnterCriticalSection(&env->me_windowsbug_lock); + } + + if ((env->me_flags & MDBX_EXCLUSIVE) || + flock(env->me_fd, + dontwait ? (LCK_EXCLUSIVE | LCK_DONTWAIT) + : (LCK_EXCLUSIVE | LCK_WAITFOR), + LCK_BODY)) + return MDBX_SUCCESS; + int rc = GetLastError(); + LeaveCriticalSection(&env->me_windowsbug_lock); + return (!dontwait || rc != ERROR_LOCK_VIOLATION) ? rc : MDBX_BUSY; +} + +void mdbx_txn_unlock(MDBX_env *env) { + int rc = + (env->me_flags & MDBX_EXCLUSIVE) ? TRUE : funlock(env->me_fd, LCK_BODY); + LeaveCriticalSection(&env->me_windowsbug_lock); + if (!rc) + mdbx_panic("%s failed: errcode %u", __func__, GetLastError()); +} + +/*----------------------------------------------------------------------------*/ +/* global `read` lock for readers registration, + * exclusive locking `mti_numreaders` (second) cacheline */ + +#define LCK_LO_OFFSET 0 +#define LCK_LO_LEN offsetof(MDBX_lockinfo, mti_numreaders) +#define LCK_UP_OFFSET LCK_LO_LEN +#define LCK_UP_LEN (sizeof(MDBX_lockinfo) - LCK_UP_OFFSET) +#define LCK_LOWER LCK_LO_OFFSET, LCK_LO_LEN +#define LCK_UPPER LCK_UP_OFFSET, LCK_UP_LEN + +MDBX_INTERNAL_FUNC int mdbx_rdt_lock(MDBX_env *env) { + mdbx_srwlock_AcquireShared(&env->me_remap_guard); + if (env->me_lfd == INVALID_HANDLE_VALUE) + return MDBX_SUCCESS; /* readonly database in readonly filesystem */ + + /* transite from S-? (used) to S-E (locked), e.g. exclusive lock upper-part */ + if ((env->me_flags & MDBX_EXCLUSIVE) || + flock(env->me_lfd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_UPPER)) + return MDBX_SUCCESS; + + int rc = GetLastError(); + mdbx_srwlock_ReleaseShared(&env->me_remap_guard); + return rc; +} + +MDBX_INTERNAL_FUNC void mdbx_rdt_unlock(MDBX_env *env) { + if (env->me_lfd != INVALID_HANDLE_VALUE) { + /* transite from S-E (locked) to S-? (used), e.g. unlock upper-part */ + if ((env->me_flags & MDBX_EXCLUSIVE) == 0 && + !funlock(env->me_lfd, LCK_UPPER)) + mdbx_panic("%s failed: errcode %u", __func__, GetLastError()); + } + mdbx_srwlock_ReleaseShared(&env->me_remap_guard); +} + +static int suspend_and_append(mdbx_handle_array_t **array, + const DWORD ThreadId) { + const unsigned limit = (*array)->limit; + if ((*array)->count == limit) { + void *ptr = mdbx_realloc( + (limit > ARRAY_LENGTH((*array)->handles)) + ? *array + : /* don't free initial array on the stack */ NULL, + sizeof(mdbx_handle_array_t) + + sizeof(HANDLE) * (limit * 2 - ARRAY_LENGTH((*array)->handles))); + if (!ptr) + return MDBX_ENOMEM; + if (limit == ARRAY_LENGTH((*array)->handles)) + memcpy(ptr, *array, sizeof(mdbx_handle_array_t)); + *array = (mdbx_handle_array_t *)ptr; + (*array)->limit = limit * 2; + } + + HANDLE hThread = OpenThread(THREAD_SUSPEND_RESUME | THREAD_QUERY_INFORMATION, + FALSE, ThreadId); + if (hThread == NULL) + return GetLastError(); + + if (SuspendThread(hThread) == -1) { + int err = GetLastError(); + DWORD ExitCode; + if (err == /* workaround for Win10 UCRT bug */ ERROR_ACCESS_DENIED || + !GetExitCodeThread(hThread, &ExitCode) || ExitCode != STILL_ACTIVE) + err = MDBX_SUCCESS; + CloseHandle(hThread); + return err; + } + + (*array)->handles[(*array)->count++] = hThread; + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int +mdbx_suspend_threads_before_remap(MDBX_env *env, mdbx_handle_array_t **array) { + const size_t CurrentTid = GetCurrentThreadId(); + int rc; + if (env->me_lck) { + /* Scan LCK for threads of the current process */ + const MDBX_reader *const begin = env->me_lck->mti_readers; + const MDBX_reader *const end = begin + env->me_lck->mti_numreaders; + const size_t WriteTxnOwner = env->me_txn0 ? env->me_txn0->mt_owner : 0; + for (const MDBX_reader *reader = begin; reader < end; ++reader) { + if (reader->mr_pid != env->me_pid || !reader->mr_tid) { + skip_lck: + continue; + } + if (reader->mr_tid == CurrentTid || reader->mr_tid == WriteTxnOwner) + goto skip_lck; + if (env->me_flags & MDBX_NOTLS) { + /* Skip duplicates in no-tls mode */ + for (const MDBX_reader *scan = reader; --scan >= begin;) + if (scan->mr_tid == reader->mr_tid) + goto skip_lck; + } + + rc = suspend_and_append(array, (mdbx_tid_t)reader->mr_tid); + if (rc != MDBX_SUCCESS) { + bailout_lck: + (void)mdbx_resume_threads_after_remap(*array); + return rc; + } + } + if (WriteTxnOwner && WriteTxnOwner != CurrentTid) { + rc = suspend_and_append(array, (mdbx_tid_t)WriteTxnOwner); + if (rc != MDBX_SUCCESS) + goto bailout_lck; + } + } else { + /* Without LCK (i.e. read-only mode). + * Walk thougth a snapshot of all running threads */ + mdbx_assert(env, + env->me_txn0 == NULL || (env->me_flags & MDBX_EXCLUSIVE) != 0); + const HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); + if (hSnapshot == INVALID_HANDLE_VALUE) + return GetLastError(); + + THREADENTRY32 entry; + entry.dwSize = sizeof(THREADENTRY32); + + if (!Thread32First(hSnapshot, &entry)) { + rc = GetLastError(); + bailout_toolhelp: + CloseHandle(hSnapshot); + (void)mdbx_resume_threads_after_remap(*array); + return rc; + } + + do { + if (entry.th32OwnerProcessID != env->me_pid || + entry.th32ThreadID == CurrentTid) + continue; + + rc = suspend_and_append(array, entry.th32ThreadID); + if (rc != MDBX_SUCCESS) + goto bailout_toolhelp; + + } while (Thread32Next(hSnapshot, &entry)); + + rc = GetLastError(); + if (rc != ERROR_NO_MORE_FILES) + goto bailout_toolhelp; + CloseHandle(hSnapshot); + } + + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int +mdbx_resume_threads_after_remap(mdbx_handle_array_t *array) { + int rc = MDBX_SUCCESS; + for (unsigned i = 0; i < array->count; ++i) { + const HANDLE hThread = array->handles[i]; + if (ResumeThread(hThread) == -1) { + const int err = GetLastError(); + DWORD ExitCode; + if (err != /* workaround for Win10 UCRT bug */ ERROR_ACCESS_DENIED && + GetExitCodeThread(hThread, &ExitCode) && ExitCode == STILL_ACTIVE) + rc = err; + } + CloseHandle(hThread); + } + return rc; +} + +/*----------------------------------------------------------------------------*/ +/* global `initial` lock for lockfile initialization, + * exclusive/shared locking first cacheline */ + +/* Briefly descritpion of locking schema/algorithm: + * - Windows does not support upgrading or downgrading for file locking. + * - Therefore upgrading/downgrading is emulated by shared and exclusive + * locking of upper and lower halves. + * - In other words, we have FSM with possible 9 states, + * i.e. free/shared/exclusive x free/shared/exclusive == 9. + * Only 6 states of FSM are used, which 2 of ones are transitive. + * + * The mdbx_lck_seize() moves the locking-FSM from the initial free/unlocked + * state to the "exclusive write" (and returns MDBX_RESULT_TRUE) if possible, + * or to the "used" (and returns MDBX_RESULT_FALSE). + * + * The mdbx_lck_downgrade() moves the locking-FSM from "exclusive write" + * state to the "used" (i.e. shared) state. + * + * States: + * ?-? = free, i.e. unlocked + * S-? = used, i.e. shared lock + * E-? = exclusive-read, i.e. operational exclusive + * ?-S + * ?-E = middle (transitive state) + * S-S + * S-E = locked (transitive state) + * E-S + * E-E = exclusive-write, i.e. exclusive due (re)initialization + */ + +static void lck_unlock(MDBX_env *env) { + int rc; + + if (env->me_lfd != INVALID_HANDLE_VALUE) { + /* double `unlock` for robustly remove overlapped shared/exclusive locks */ + while (funlock(env->me_lfd, LCK_LOWER)) + ; + rc = GetLastError(); + assert(rc == ERROR_NOT_LOCKED); + (void)rc; + SetLastError(ERROR_SUCCESS); + + while (funlock(env->me_lfd, LCK_UPPER)) + ; + rc = GetLastError(); + assert(rc == ERROR_NOT_LOCKED); + (void)rc; + SetLastError(ERROR_SUCCESS); + } + + if (env->me_fd != INVALID_HANDLE_VALUE) { + /* explicitly unlock to avoid latency for other processes (windows kernel + * releases such locks via deferred queues) */ + while (funlock(env->me_fd, LCK_BODY)) + ; + rc = GetLastError(); + assert(rc == ERROR_NOT_LOCKED); + (void)rc; + SetLastError(ERROR_SUCCESS); + + while (funlock(env->me_fd, LCK_META)) + ; + rc = GetLastError(); + assert(rc == ERROR_NOT_LOCKED); + (void)rc; + SetLastError(ERROR_SUCCESS); + + while (funlock(env->me_fd, LCK_WHOLE)) + ; + rc = GetLastError(); + assert(rc == ERROR_NOT_LOCKED); + (void)rc; + SetLastError(ERROR_SUCCESS); + } +} + +MDBX_INTERNAL_FUNC int mdbx_lck_init(MDBX_env *env, + MDBX_env *inprocess_neighbor, + int global_uniqueness_flag) { + (void)env; + (void)inprocess_neighbor; + (void)global_uniqueness_flag; + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_lck_destroy(MDBX_env *env, + MDBX_env *inprocess_neighbor) { + (void)inprocess_neighbor; + + /* LY: should unmap before releasing the locks to avoid race condition and + * STATUS_USER_MAPPED_FILE/ERROR_USER_MAPPED_FILE */ + if (env->me_map) + mdbx_munmap(&env->me_dxb_mmap); + if (env->me_lck) + mdbx_munmap(&env->me_lck_mmap); + + lck_unlock(env); + return MDBX_SUCCESS; +} + +/* Seize state as 'exclusive-write' (E-E and returns MDBX_RESULT_TRUE) + * or as 'used' (S-? and returns MDBX_RESULT_FALSE). + * Oherwise returns an error. */ +static int internal_seize_lck(HANDLE lfd) { + int rc; + assert(lfd != INVALID_HANDLE_VALUE); + + /* 1) now on ?-? (free), get ?-E (middle) */ + mdbx_jitter4testing(false); + if (!flock(lfd, LCK_EXCLUSIVE | LCK_WAITFOR, LCK_UPPER)) { + rc = GetLastError() /* 2) something went wrong, give up */; + mdbx_error("%s(%s) failed: errcode %u", __func__, + "?-?(free) >> ?-E(middle)", rc); + return rc; + } + + /* 3) now on ?-E (middle), try E-E (exclusive-write) */ + mdbx_jitter4testing(false); + if (flock(lfd, LCK_EXCLUSIVE | LCK_DONTWAIT, LCK_LOWER)) + return MDBX_RESULT_TRUE /* 4) got E-E (exclusive-write), done */; + + /* 5) still on ?-E (middle) */ + rc = GetLastError(); + mdbx_jitter4testing(false); + if (rc != ERROR_SHARING_VIOLATION && rc != ERROR_LOCK_VIOLATION) { + /* 6) something went wrong, give up */ + if (!funlock(lfd, LCK_UPPER)) + mdbx_panic("%s(%s) failed: errcode %u", __func__, + "?-E(middle) >> ?-?(free)", GetLastError()); + return rc; + } + + /* 7) still on ?-E (middle), try S-E (locked) */ + mdbx_jitter4testing(false); + rc = flock(lfd, LCK_SHARED | LCK_DONTWAIT, LCK_LOWER) ? MDBX_RESULT_FALSE + : GetLastError(); + + mdbx_jitter4testing(false); + if (rc != MDBX_RESULT_FALSE) + mdbx_error("%s(%s) failed: errcode %u", __func__, + "?-E(middle) >> S-E(locked)", rc); + + /* 8) now on S-E (locked) or still on ?-E (middle), + * transite to S-? (used) or ?-? (free) */ + if (!funlock(lfd, LCK_UPPER)) + mdbx_panic("%s(%s) failed: errcode %u", __func__, + "X-E(locked/middle) >> X-?(used/free)", GetLastError()); + + /* 9) now on S-? (used, DONE) or ?-? (free, FAILURE) */ + return rc; +} + +MDBX_INTERNAL_FUNC int mdbx_lck_seize(MDBX_env *env) { + int rc; + + assert(env->me_fd != INVALID_HANDLE_VALUE); + if (env->me_flags & MDBX_EXCLUSIVE) + return MDBX_RESULT_TRUE /* nope since files were must be opened + non-shareable */ + ; + + if (env->me_lfd == INVALID_HANDLE_VALUE) { + /* LY: without-lck mode (e.g. on read-only filesystem) */ + mdbx_jitter4testing(false); + if (!flock(env->me_fd, LCK_SHARED | LCK_DONTWAIT, LCK_WHOLE)) { + rc = GetLastError(); + mdbx_error("%s(%s) failed: errcode %u", __func__, "without-lck", rc); + return rc; + } + return MDBX_RESULT_FALSE; + } + + rc = internal_seize_lck(env->me_lfd); + mdbx_jitter4testing(false); + if (rc == MDBX_RESULT_TRUE && (env->me_flags & MDBX_RDONLY) == 0) { + /* Check that another process don't operates in without-lck mode. + * Doing such check by exclusive locking the body-part of db. Should be + * noted: + * - we need an exclusive lock for do so; + * - we can't lock meta-pages, otherwise other process could get an error + * while opening db in valid (non-conflict) mode. */ + if (!flock(env->me_fd, LCK_EXCLUSIVE | LCK_DONTWAIT, LCK_BODY)) { + rc = GetLastError(); + mdbx_error("%s(%s) failed: errcode %u", __func__, + "lock-against-without-lck", rc); + mdbx_jitter4testing(false); + lck_unlock(env); + } else { + mdbx_jitter4testing(false); + if (!funlock(env->me_fd, LCK_BODY)) + mdbx_panic("%s(%s) failed: errcode %u", __func__, + "unlock-against-without-lck", GetLastError()); + } + } + + return rc; +} + +MDBX_INTERNAL_FUNC int mdbx_lck_downgrade(MDBX_env *env) { + /* Transite from exclusive state (E-?) to used (S-?) */ + assert(env->me_fd != INVALID_HANDLE_VALUE); + assert(env->me_lfd != INVALID_HANDLE_VALUE); + +#if 1 + if (env->me_flags & MDBX_EXCLUSIVE) + return MDBX_SUCCESS /* nope since files were must be opened non-shareable */ + ; +#else + /* 1) must be at E-E (exclusive-write) */ + if (env->me_flags & MDBX_EXCLUSIVE) { + /* transite from E-E to E_? (exclusive-read) */ + if (!funlock(env->me_lfd, LCK_UPPER)) + mdbx_panic("%s(%s) failed: errcode %u", __func__, + "E-E(exclusive-write) >> E-?(exclusive-read)", GetLastError()); + return MDBX_SUCCESS /* 2) now at E-? (exclusive-read), done */; + } +#endif + + /* 3) now at E-E (exclusive-write), transite to ?_E (middle) */ + if (!funlock(env->me_lfd, LCK_LOWER)) + mdbx_panic("%s(%s) failed: errcode %u", __func__, + "E-E(exclusive-write) >> ?-E(middle)", GetLastError()); + + /* 4) now at ?-E (middle), transite to S-E (locked) */ + if (!flock(env->me_lfd, LCK_SHARED | LCK_DONTWAIT, LCK_LOWER)) { + int rc = GetLastError() /* 5) something went wrong, give up */; + mdbx_error("%s(%s) failed: errcode %u", __func__, + "?-E(middle) >> S-E(locked)", rc); + return rc; + } + + /* 6) got S-E (locked), continue transition to S-? (used) */ + if (!funlock(env->me_lfd, LCK_UPPER)) + mdbx_panic("%s(%s) failed: errcode %u", __func__, + "S-E(locked) >> S-?(used)", GetLastError()); + + return MDBX_SUCCESS /* 7) now at S-? (used), done */; +} + +/*----------------------------------------------------------------------------*/ +/* reader checking (by pid) */ + +MDBX_INTERNAL_FUNC int mdbx_rpid_set(MDBX_env *env) { + (void)env; + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_rpid_clear(MDBX_env *env) { + (void)env; + return MDBX_SUCCESS; +} + +/* Checks reader by pid. + * + * Returns: + * MDBX_RESULT_TRUE, if pid is live (unable to acquire lock) + * MDBX_RESULT_FALSE, if pid is dead (lock acquired) + * or otherwise the errcode. */ +MDBX_INTERNAL_FUNC int mdbx_rpid_check(MDBX_env *env, uint32_t pid) { + (void)env; + HANDLE hProcess = OpenProcess(SYNCHRONIZE, FALSE, pid); + int rc; + if (likely(hProcess)) { + rc = WaitForSingleObject(hProcess, 0); + if (unlikely(rc == WAIT_FAILED)) + rc = GetLastError(); + CloseHandle(hProcess); + } else { + rc = GetLastError(); + } + + switch (rc) { + case ERROR_INVALID_PARAMETER: + /* pid seems invalid */ + return MDBX_RESULT_FALSE; + case WAIT_OBJECT_0: + /* process just exited */ + return MDBX_RESULT_FALSE; + case WAIT_TIMEOUT: + /* pid running */ + return MDBX_RESULT_TRUE; + default: + /* failure */ + return rc; + } +} + +//---------------------------------------------------------------------------- +// Stub for slim read-write lock +// Copyright (C) 1995-2002 Brad Wilson + +static void WINAPI stub_srwlock_Init(MDBX_srwlock *srwl) { + srwl->readerCount = srwl->writerCount = 0; +} + +static void WINAPI stub_srwlock_AcquireShared(MDBX_srwlock *srwl) { + while (true) { + assert(srwl->writerCount >= 0 && srwl->readerCount >= 0); + + // If there's a writer already, spin without unnecessarily + // interlocking the CPUs + if (srwl->writerCount != 0) { + YieldProcessor(); + continue; + } + + // Add to the readers list + _InterlockedIncrement(&srwl->readerCount); + + // Check for writers again (we may have been pre-empted). If + // there are no writers writing or waiting, then we're done. + if (srwl->writerCount == 0) + break; + + // Remove from the readers list, spin, try again + _InterlockedDecrement(&srwl->readerCount); + YieldProcessor(); + } +} + +static void WINAPI stub_srwlock_ReleaseShared(MDBX_srwlock *srwl) { + assert(srwl->readerCount > 0); + _InterlockedDecrement(&srwl->readerCount); +} + +static void WINAPI stub_srwlock_AcquireExclusive(MDBX_srwlock *srwl) { + while (true) { + assert(srwl->writerCount >= 0 && srwl->readerCount >= 0); + + // If there's a writer already, spin without unnecessarily + // interlocking the CPUs + if (srwl->writerCount != 0) { + YieldProcessor(); + continue; + } + + // See if we can become the writer (expensive, because it inter- + // locks the CPUs, so writing should be an infrequent process) + if (_InterlockedExchange(&srwl->writerCount, 1) == 0) + break; + } + + // Now we're the writer, but there may be outstanding readers. + // Spin until there aren't any more; new readers will wait now + // that we're the writer. + while (srwl->readerCount != 0) { + assert(srwl->writerCount >= 0 && srwl->readerCount >= 0); + YieldProcessor(); + } +} + +static void WINAPI stub_srwlock_ReleaseExclusive(MDBX_srwlock *srwl) { + assert(srwl->writerCount == 1 && srwl->readerCount >= 0); + srwl->writerCount = 0; +} + +MDBX_srwlock_function mdbx_srwlock_Init, mdbx_srwlock_AcquireShared, + mdbx_srwlock_ReleaseShared, mdbx_srwlock_AcquireExclusive, + mdbx_srwlock_ReleaseExclusive; + +/*----------------------------------------------------------------------------*/ + +#if 0 /* LY: unused for now */ +static DWORD WINAPI stub_DiscardVirtualMemory(PVOID VirtualAddress, + SIZE_T Size) { + return VirtualAlloc(VirtualAddress, Size, MEM_RESET, PAGE_NOACCESS) + ? ERROR_SUCCESS + : GetLastError(); +} +#endif /* unused for now */ + +static uint64_t WINAPI stub_GetTickCount64(void) { + LARGE_INTEGER Counter, Frequency; + return (QueryPerformanceFrequency(&Frequency) && + QueryPerformanceCounter(&Counter)) + ? Counter.QuadPart * 1000ul / Frequency.QuadPart + : 0; +} + +/*----------------------------------------------------------------------------*/ +#ifndef MDBX_ALLOY +MDBX_GetFileInformationByHandleEx mdbx_GetFileInformationByHandleEx; +MDBX_GetVolumeInformationByHandleW mdbx_GetVolumeInformationByHandleW; +MDBX_GetFinalPathNameByHandleW mdbx_GetFinalPathNameByHandleW; +MDBX_SetFileInformationByHandle mdbx_SetFileInformationByHandle; +MDBX_NtFsControlFile mdbx_NtFsControlFile; +MDBX_PrefetchVirtualMemory mdbx_PrefetchVirtualMemory; +MDBX_GetTickCount64 mdbx_GetTickCount64; +#if 0 /* LY: unused for now */ +MDBX_DiscardVirtualMemory mdbx_DiscardVirtualMemory; +MDBX_OfferVirtualMemory mdbx_OfferVirtualMemory; +MDBX_ReclaimVirtualMemory mdbx_ReclaimVirtualMemory; +#endif /* unused for now */ +#endif /* MDBX_ALLOY */ + +static void mdbx_winnt_import(void) { + const HINSTANCE hKernel32dll = GetModuleHandleA("kernel32.dll"); + const MDBX_srwlock_function init = + (MDBX_srwlock_function)GetProcAddress(hKernel32dll, "InitializeSRWLock"); + if (init != NULL) { + mdbx_srwlock_Init = init; + mdbx_srwlock_AcquireShared = (MDBX_srwlock_function)GetProcAddress( + hKernel32dll, "AcquireSRWLockShared"); + mdbx_srwlock_ReleaseShared = (MDBX_srwlock_function)GetProcAddress( + hKernel32dll, "ReleaseSRWLockShared"); + mdbx_srwlock_AcquireExclusive = (MDBX_srwlock_function)GetProcAddress( + hKernel32dll, "AcquireSRWLockExclusive"); + mdbx_srwlock_ReleaseExclusive = (MDBX_srwlock_function)GetProcAddress( + hKernel32dll, "ReleaseSRWLockExclusive"); + } else { + mdbx_srwlock_Init = stub_srwlock_Init; + mdbx_srwlock_AcquireShared = stub_srwlock_AcquireShared; + mdbx_srwlock_ReleaseShared = stub_srwlock_ReleaseShared; + mdbx_srwlock_AcquireExclusive = stub_srwlock_AcquireExclusive; + mdbx_srwlock_ReleaseExclusive = stub_srwlock_ReleaseExclusive; + } + +#define GET_KERNEL32_PROC(ENTRY) \ + mdbx_##ENTRY = (MDBX_##ENTRY)GetProcAddress(hKernel32dll, #ENTRY) + GET_KERNEL32_PROC(GetFileInformationByHandleEx); + GET_KERNEL32_PROC(GetVolumeInformationByHandleW); + GET_KERNEL32_PROC(GetFinalPathNameByHandleW); + GET_KERNEL32_PROC(SetFileInformationByHandle); + GET_KERNEL32_PROC(PrefetchVirtualMemory); + GET_KERNEL32_PROC(GetTickCount64); + if (!mdbx_GetTickCount64) + mdbx_GetTickCount64 = stub_GetTickCount64; +#if 0 /* LY: unused for now */ + GET_KERNEL32_PROC(DiscardVirtualMemory); + if (!mdbx_DiscardVirtualMemory) + mdbx_DiscardVirtualMemory = stub_DiscardVirtualMemory; + GET_KERNEL32_PROC(OfferVirtualMemory); + GET_KERNEL32_PROC(ReclaimVirtualMemory); +#endif /* unused for now */ +#undef GET_KERNEL32_PROC + + const HINSTANCE hNtdll = GetModuleHandleA("ntdll.dll"); + mdbx_NtFsControlFile = + (MDBX_NtFsControlFile)GetProcAddress(hNtdll, "NtFsControlFile"); +} diff --git a/contrib/db/libmdbx/src/elements/ntdll.def b/contrib/db/libmdbx/src/elements/ntdll.def new file mode 100644 index 00000000..e3a6e33c --- /dev/null +++ b/contrib/db/libmdbx/src/elements/ntdll.def @@ -0,0 +1,1244 @@ +LIBRARY ntdll +EXPORTS +CsrAllocateCaptureBuffer +CsrAllocateMessagePointer +CsrCaptureMessageBuffer +CsrCaptureMessageMultiUnicodeStringsInPlace +CsrCaptureMessageString +CsrCaptureTimeout +CsrClientCallServer +CsrClientConnectToServer +CsrFreeCaptureBuffer +CsrGetProcessId +CsrIdentifyAlertableThread +CsrSetPriorityClass +DbgBreakPoint +DbgPrint +DbgPrintEx +DbgPrintReturnControlC +DbgPrompt +DbgQueryDebugFilterState +DbgSetDebugFilterState +DbgUiConnectToDbg +DbgUiContinue +DbgUiConvertStateChangeStructure +DbgUiDebugActiveProcess +DbgUiGetThreadDebugObject +DbgUiIssueRemoteBreakin +DbgUiRemoteBreakin +DbgUiSetThreadDebugObject +DbgUiStopDebugging +DbgUiWaitStateChange +DbgUserBreakPoint +KiRaiseUserExceptionDispatcher +KiUserApcDispatcher +KiUserCallbackDispatcher +KiUserExceptionDispatcher +LdrAccessResource +LdrAddRefDll +LdrDisableThreadCalloutsForDll +LdrEnumResources +LdrEnumerateLoadedModules +LdrFindEntryForAddress +LdrFindResourceDirectory_U +LdrFindResourceEx_U +LdrFindResource_U +LdrFlushAlternateResourceModules +LdrGetDllHandle +LdrGetDllHandleEx +LdrGetProcedureAddress +LdrInitShimEngineDynamic +LdrInitializeThunk +LdrLoadAlternateResourceModule +LdrLoadDll +LdrLockLoaderLock +LdrProcessRelocationBlock +LdrQueryImageFileExecutionOptions +LdrQueryProcessModuleInformation +LdrSetAppCompatDllRedirectionCallback +LdrSetDllManifestProber +LdrShutdownProcess +LdrShutdownThread +LdrUnloadAlternateResourceModule +LdrUnloadDll +LdrUnlockLoaderLock +LdrVerifyImageMatchesChecksum +NlsAnsiCodePage +NlsMbCodePageTag +NlsMbOemCodePageTag +NtAcceptConnectPort +NtAccessCheck +NtAccessCheckAndAuditAlarm +NtAccessCheckByType +NtAccessCheckByTypeAndAuditAlarm +NtAccessCheckByTypeResultList +NtAccessCheckByTypeResultListAndAuditAlarm +NtAccessCheckByTypeResultListAndAuditAlarmByHandle +NtAddAtom +NtAddBootEntry +NtAdjustGroupsToken +NtAdjustPrivilegesToken +NtAlertResumeThread +NtAlertThread +NtAllocateLocallyUniqueId +NtAllocateUserPhysicalPages +NtAllocateUuids +NtAllocateVirtualMemory +NtAreMappedFilesTheSame +NtAssignProcessToJobObject +NtCallbackReturn +NtCancelIoFile +NtCancelTimer +NtClearEvent +NtClose +NtCloseObjectAuditAlarm +NtCompactKeys +NtCompareTokens +NtCompleteConnectPort +NtCompressKey +NtConnectPort +NtContinue +NtCreateDebugObject +NtCreateDirectoryObject +NtCreateEvent +NtCreateEventPair +NtCreateFile +NtCreateIoCompletion +NtCreateJobObject +NtCreateJobSet +NtCreateKey +NtCreateKeyedEvent +NtCreateMailslotFile +NtCreateMutant +NtCreateNamedPipeFile +NtCreatePagingFile +NtCreatePort +NtCreateProcess +NtCreateProcessEx +NtCreateProfile +NtCreateSection +NtCreateSemaphore +NtCreateSymbolicLinkObject +NtCreateThread +NtCreateTimer +NtCreateToken +NtCreateWaitablePort +NtDebugActiveProcess +NtDebugContinue +NtDelayExecution +NtDeleteAtom +NtDeleteBootEntry +NtDeleteFile +NtDeleteKey +NtDeleteObjectAuditAlarm +NtDeleteValueKey +NtDeviceIoControlFile +NtDisplayString +NtDuplicateObject +NtDuplicateToken +NtEnumerateBootEntries +NtEnumerateKey +NtEnumerateSystemEnvironmentValuesEx +NtEnumerateValueKey +NtExtendSection +NtFilterToken +NtFindAtom +NtFlushBuffersFile +NtFlushInstructionCache +NtFlushKey +NtFlushVirtualMemory +NtFlushWriteBuffer +NtFreeUserPhysicalPages +NtFreeVirtualMemory +NtFsControlFile +NtGetContextThread +NtGetDevicePowerState +NtGetWriteWatch +NtImpersonateAnonymousToken +NtImpersonateClientOfPort +NtImpersonateThread +NtInitializeRegistry +NtInitiatePowerAction +NtIsProcessInJob +NtIsSystemResumeAutomatic +NtListenPort +NtLoadDriver +NtLoadKey +NtLoadKey2 +NtLockFile +NtLockProductActivationKeys +NtLockRegistryKey +NtLockVirtualMemory +NtMakePermanentObject +NtMakeTemporaryObject +NtMapUserPhysicalPages +NtMapUserPhysicalPagesScatter +NtMapViewOfSection +NtModifyBootEntry +NtNotifyChangeDirectoryFile +NtNotifyChangeKey +NtNotifyChangeMultipleKeys +NtOpenDirectoryObject +NtOpenEvent +NtOpenEventPair +NtOpenFile +NtOpenIoCompletion +NtOpenJobObject +NtOpenKey +NtOpenKeyedEvent +NtOpenMutant +NtOpenObjectAuditAlarm +NtOpenProcess +NtOpenProcessToken +NtOpenProcessTokenEx +NtOpenSection +NtOpenSemaphore +NtOpenSymbolicLinkObject +NtOpenThread +NtOpenThreadToken +NtOpenThreadTokenEx +NtOpenTimer +NtPlugPlayControl +NtPowerInformation +NtPrivilegeCheck +NtPrivilegeObjectAuditAlarm +NtPrivilegedServiceAuditAlarm +NtProtectVirtualMemory +NtPulseEvent +NtQueryAttributesFile +NtQueryBootEntryOrder +NtQueryBootOptions +NtQueryDebugFilterState +NtQueryDefaultLocale +NtQueryDefaultUILanguage +NtQueryDirectoryFile +NtQueryDirectoryObject +NtQueryEaFile +NtQueryEvent +NtQueryFullAttributesFile +NtQueryInformationAtom +NtQueryInformationFile +NtQueryInformationJobObject +NtQueryInformationPort +NtQueryInformationProcess +NtQueryInformationThread +NtQueryInformationToken +NtQueryInstallUILanguage +NtQueryIntervalProfile +NtQueryIoCompletion +NtQueryKey +NtQueryMultipleValueKey +NtQueryMutant +NtQueryObject +NtQueryOpenSubKeys +NtQueryPerformanceCounter +NtQueryPortInformationProcess +NtQueryQuotaInformationFile +NtQuerySection +NtQuerySecurityObject +NtQuerySemaphore +NtQuerySymbolicLinkObject +NtQuerySystemEnvironmentValue +NtQuerySystemEnvironmentValueEx +NtQuerySystemInformation +NtQuerySystemTime +NtQueryTimer +NtQueryTimerResolution +NtQueryValueKey +NtQueryVirtualMemory +NtQueryVolumeInformationFile +NtQueueApcThread +NtRaiseException +NtRaiseHardError +NtReadFile +NtReadFileScatter +NtReadRequestData +NtReadVirtualMemory +NtRegisterThreadTerminatePort +NtReleaseKeyedEvent +NtReleaseMutant +NtReleaseSemaphore +NtRemoveIoCompletion +NtRemoveProcessDebug +NtRenameKey +NtReplaceKey +NtReplyPort +NtReplyWaitReceivePort +NtReplyWaitReceivePortEx +NtReplyWaitReplyPort +NtRequestPort +NtRequestWaitReplyPort +NtResetEvent +NtResetWriteWatch +NtRestoreKey +NtResumeProcess +NtResumeThread +NtSaveKey +NtSaveKeyEx +NtSaveMergedKeys +NtSecureConnectPort +NtSetBootEntryOrder +NtSetBootOptions +NtSetContextThread +NtSetDebugFilterState +NtSetDefaultHardErrorPort +NtSetDefaultLocale +NtSetDefaultUILanguage +NtSetEaFile +NtSetEvent +NtSetEventBoostPriority +NtSetHighEventPair +NtSetHighWaitLowEventPair +NtSetInformationDebugObject +NtSetInformationFile +NtSetInformationJobObject +NtSetInformationKey +NtSetInformationObject +NtSetInformationProcess +NtSetInformationThread +NtSetInformationToken +NtSetIntervalProfile +NtSetIoCompletion +NtSetLdtEntries +NtSetLowEventPair +NtSetLowWaitHighEventPair +NtSetQuotaInformationFile +NtSetSecurityObject +NtSetSystemEnvironmentValue +NtSetSystemEnvironmentValueEx +NtSetSystemInformation +NtSetSystemPowerState +NtSetSystemTime +NtSetThreadExecutionState +NtSetTimer +NtSetTimerResolution +NtSetUuidSeed +NtSetValueKey +NtSetVolumeInformationFile +NtShutdownSystem +NtSignalAndWaitForSingleObject +NtStartProfile +NtStopProfile +NtSuspendProcess +NtSuspendThread +NtSystemDebugControl +NtTerminateJobObject +NtTerminateProcess +NtTerminateThread +NtTestAlert +NtTraceEvent +NtTranslateFilePath +NtUnloadDriver +NtUnloadKey +NtUnloadKeyEx +NtUnlockFile +NtUnlockVirtualMemory +NtUnmapViewOfSection +NtVdmControl +NtWaitForDebugEvent +NtWaitForKeyedEvent +NtWaitForMultipleObjects +NtWaitForSingleObject +NtWaitHighEventPair +NtWaitLowEventPair +NtWriteFile +NtWriteFileGather +NtWriteRequestData +NtWriteVirtualMemory +NtYieldExecution +PfxFindPrefix +PfxInitialize +PfxInsertPrefix +PfxRemovePrefix +RtlAbortRXact +RtlAbsoluteToSelfRelativeSD +RtlAcquirePebLock +RtlAcquireResourceExclusive +RtlAcquireResourceShared +RtlActivateActivationContext +RtlActivateActivationContextEx +RtlActivateActivationContextUnsafeFast +RtlAddAccessAllowedAce +RtlAddAccessAllowedAceEx +RtlAddAccessAllowedObjectAce +RtlAddAccessDeniedAce +RtlAddAccessDeniedAceEx +RtlAddAccessDeniedObjectAce +RtlAddAce +RtlAddActionToRXact +RtlAddAtomToAtomTable +RtlAddAttributeActionToRXact +RtlAddAuditAccessAce +RtlAddAuditAccessAceEx +RtlAddAuditAccessObjectAce +RtlAddCompoundAce +RtlAddRefActivationContext +RtlAddRefMemoryStream +RtlAddVectoredExceptionHandler +RtlAddressInSectionTable +RtlAdjustPrivilege +RtlAllocateAndInitializeSid +RtlAllocateHandle +RtlAllocateHeap +RtlAnsiCharToUnicodeChar +RtlAnsiStringToUnicodeSize +RtlAnsiStringToUnicodeString +RtlAppendAsciizToString +RtlAppendPathElement +RtlAppendStringToString +RtlAppendUnicodeStringToString +RtlAppendUnicodeToString +RtlApplicationVerifierStop +RtlApplyRXact +RtlApplyRXactNoFlush +RtlAreAllAccessesGranted +RtlAreAnyAccessesGranted +RtlAreBitsClear +RtlAreBitsSet +RtlAssert +RtlCancelTimer +RtlCaptureContext +RtlCaptureStackBackTrace +RtlCharToInteger +RtlCheckForOrphanedCriticalSections +RtlCheckRegistryKey +RtlClearAllBits +RtlClearBits +RtlCloneMemoryStream +RtlCommitMemoryStream +RtlCompactHeap +RtlCompareMemory +RtlCompareMemoryUlong +RtlCompareString +RtlCompareUnicodeString +RtlCompressBuffer +RtlComputeCrc32 +RtlComputeImportTableHash +RtlComputePrivatizedDllName_U +RtlConsoleMultiByteToUnicodeN +RtlConvertExclusiveToShared +RtlConvertSharedToExclusive +RtlConvertSidToUnicodeString +RtlConvertToAutoInheritSecurityObject +RtlCopyLuid +RtlCopyLuidAndAttributesArray +RtlCopyMemoryStreamTo +RtlCopyOutOfProcessMemoryStreamTo +RtlCopySecurityDescriptor +RtlCopySid +RtlCopySidAndAttributesArray +RtlCopyString +RtlCopyUnicodeString +RtlCreateAcl +RtlCreateActivationContext +RtlCreateAndSetSD +RtlCreateAtomTable +RtlCreateBootStatusDataFile +RtlCreateEnvironment +RtlCreateHeap +RtlCreateProcessParameters +RtlCreateQueryDebugBuffer +RtlCreateRegistryKey +RtlCreateSecurityDescriptor +RtlCreateServiceSid +RtlCreateSystemVolumeInformationFolder +RtlCreateTagHeap +RtlCreateTimer +RtlCreateTimerQueue +RtlCreateUnicodeString +RtlCreateUnicodeStringFromAsciiz +RtlCreateUserProcess +RtlCreateUserSecurityObject +RtlCreateUserThread +RtlCustomCPToUnicodeN +RtlCutoverTimeToSystemTime +RtlDeNormalizeProcessParams +RtlDeactivateActivationContext +RtlDeactivateActivationContextUnsafeFast +RtlDebugPrintTimes +RtlDecodePointer +RtlDecodeSystemPointer +RtlDecompressBuffer +RtlDecompressFragment +RtlDefaultNpAcl +RtlDelete +RtlDeleteAce +RtlDeleteAtomFromAtomTable +RtlDeleteCriticalSection +RtlDeleteElementGenericTable +RtlDeleteElementGenericTableAvl +RtlDeleteNoSplay +RtlDeleteRegistryValue +RtlDeleteResource +RtlDeleteSecurityObject +RtlDeleteTimer +RtlDeleteTimerQueue +RtlDeleteTimerQueueEx +RtlDeregisterWait +RtlDeregisterWaitEx +RtlDestroyAtomTable +RtlDestroyEnvironment +RtlDestroyHandleTable +RtlDestroyHeap +RtlDestroyProcessParameters +RtlDestroyQueryDebugBuffer +RtlDetermineDosPathNameType_U +RtlDllShutdownInProgress +RtlDnsHostNameToComputerName +RtlDoesFileExists_U +RtlDosApplyFileIsolationRedirection_Ustr +RtlDosPathNameToNtPathName_U +RtlDosSearchPath_U +RtlDosSearchPath_Ustr +RtlDowncaseUnicodeChar +RtlDowncaseUnicodeString +RtlDumpResource +RtlDuplicateUnicodeString +RtlEmptyAtomTable +RtlEnableEarlyCriticalSectionEventCreation +RtlEncodePointer +RtlEncodeSystemPointer +RtlEnterCriticalSection +RtlEnumProcessHeaps +RtlEnumerateGenericTable +RtlEnumerateGenericTableAvl +RtlEnumerateGenericTableLikeADirectory +RtlEnumerateGenericTableWithoutSplaying +RtlEnumerateGenericTableWithoutSplayingAvl +RtlEqualComputerName +RtlEqualDomainName +RtlEqualLuid +RtlEqualPrefixSid +RtlEqualSid +RtlEqualString +RtlEqualUnicodeString +RtlEraseUnicodeString +RtlExitUserThread +RtlExpandEnvironmentStrings_U +RtlFillMemory +RtlFinalReleaseOutOfProcessMemoryStream +RtlFindActivationContextSectionGuid +RtlFindActivationContextSectionString +RtlFindCharInUnicodeString +RtlFindClearBits +RtlFindClearBitsAndSet +RtlFindClearRuns +RtlFindLastBackwardRunClear +RtlFindLeastSignificantBit +RtlFindLongestRunClear +RtlFindMessage +RtlFindMostSignificantBit +RtlFindNextForwardRunClear +RtlFindSetBits +RtlFindSetBitsAndClear +RtlFirstEntrySList +RtlFirstFreeAce +RtlFlushSecureMemoryCache +RtlFormatCurrentUserKeyPath +RtlFormatMessage +RtlFreeAnsiString +RtlFreeHandle +RtlFreeHeap +RtlFreeOemString +RtlFreeSid +RtlFreeThreadActivationContextStack +RtlFreeUnicodeString +RtlGUIDFromString +RtlGenerate8dot3Name +RtlGetAce +RtlGetActiveActivationContext +RtlGetCallersAddress +RtlGetCompressionWorkSpaceSize +RtlGetControlSecurityDescriptor +RtlGetCurrentDirectory_U +RtlGetCurrentPeb +RtlGetDaclSecurityDescriptor +RtlGetElementGenericTable +RtlGetElementGenericTableAvl +RtlGetFrame +RtlGetFullPathName_U +RtlGetGroupSecurityDescriptor +RtlGetLastNtStatus +RtlGetLastWin32Error +RtlGetLengthWithoutLastFullDosOrNtPathElement +RtlGetLengthWithoutTrailingPathSeperators +RtlGetLongestNtPathLength +RtlGetNativeSystemInformation +RtlGetNtGlobalFlags +RtlGetNtProductType +RtlGetNtVersionNumbers +RtlGetOwnerSecurityDescriptor +RtlGetProcessHeaps +RtlGetSaclSecurityDescriptor +RtlGetSecurityDescriptorRMControl +RtlGetSetBootStatusData +RtlGetUnloadEventTrace +RtlGetUserInfoHeap +RtlGetVersion +RtlHashUnicodeString +RtlIdentifierAuthoritySid +RtlImageDirectoryEntryToData +RtlImageNtHeader +RtlImageRvaToSection +RtlImageRvaToVa +RtlImpersonateSelf +RtlInitAnsiString +RtlInitCodePageTable +RtlInitMemoryStream +RtlInitNlsTables +RtlInitOutOfProcessMemoryStream +RtlInitString +RtlInitUnicodeString +RtlInitUnicodeStringEx +RtlInitializeAtomPackage +RtlInitializeBitMap +RtlInitializeContext +RtlInitializeCriticalSection +RtlInitializeCriticalSectionAndSpinCount +RtlInitializeGenericTable +RtlInitializeGenericTableAvl +RtlInitializeHandleTable +RtlInitializeRXact +RtlInitializeResource +RtlInitializeSListHead +RtlInitializeSid +RtlInsertElementGenericTable +RtlInsertElementGenericTableAvl +RtlInt64ToUnicodeString +RtlIntegerToChar +RtlIntegerToUnicodeString +RtlInterlockedFlushSList +RtlInterlockedPopEntrySList +RtlInterlockedPushEntrySList +RtlInterlockedPushListSList +RtlIpv4AddressToStringA +RtlIpv4AddressToStringExA +RtlIpv4AddressToStringExW +RtlIpv4AddressToStringW +RtlIpv4StringToAddressA +RtlIpv4StringToAddressExA +RtlIpv4StringToAddressExW +RtlIpv4StringToAddressW +RtlIpv6AddressToStringA +RtlIpv6AddressToStringExA +RtlIpv6AddressToStringExW +RtlIpv6AddressToStringW +RtlIpv6StringToAddressA +RtlIpv6StringToAddressExA +RtlIpv6StringToAddressExW +RtlIpv6StringToAddressW +RtlIsActivationContextActive +RtlIsDosDeviceName_U +RtlIsGenericTableEmpty +RtlIsGenericTableEmptyAvl +RtlIsNameLegalDOS8Dot3 +RtlIsTextUnicode +RtlIsThreadWithinLoaderCallout +RtlIsValidHandle +RtlIsValidIndexHandle +RtlLargeIntegerToChar +RtlLeaveCriticalSection +RtlLengthRequiredSid +RtlLengthSecurityDescriptor +RtlLengthSid +RtlLocalTimeToSystemTime +RtlLockBootStatusData +RtlLockHeap +RtlLockMemoryStreamRegion +RtlLogStackBackTrace +RtlLookupAtomInAtomTable +RtlLookupElementGenericTable +RtlLookupElementGenericTableAvl +RtlMakeSelfRelativeSD +RtlMapGenericMask +RtlMapSecurityErrorToNtStatus +RtlMoveMemory +RtlMultiAppendUnicodeStringBuffer +RtlMultiByteToUnicodeN +RtlMultiByteToUnicodeSize +RtlNewInstanceSecurityObject +RtlNewSecurityGrantedAccess +RtlNewSecurityObject +RtlNewSecurityObjectEx +RtlNewSecurityObjectWithMultipleInheritance +RtlNormalizeProcessParams +RtlNtPathNameToDosPathName +RtlNtStatusToDosError +RtlNtStatusToDosErrorNoTeb +RtlNumberGenericTableElements +RtlNumberGenericTableElementsAvl +RtlNumberOfClearBits +RtlNumberOfSetBits +RtlOemStringToUnicodeSize +RtlOemStringToUnicodeString +RtlOemToUnicodeN +RtlOpenCurrentUser +RtlPcToFileHeader +RtlPinAtomInAtomTable +RtlPopFrame +RtlPrefixString +RtlPrefixUnicodeString +RtlProtectHeap +RtlPushFrame +RtlQueryAtomInAtomTable +RtlQueryDepthSList +RtlQueryEnvironmentVariable_U +RtlQueryHeapInformation +RtlQueryInformationAcl +RtlQueryInformationActivationContext +RtlQueryInformationActiveActivationContext +RtlQueryInterfaceMemoryStream +RtlQueryProcessBackTraceInformation +RtlQueryProcessDebugInformation +RtlQueryProcessHeapInformation +RtlQueryProcessLockInformation +RtlQueryRegistryValues +RtlQuerySecurityObject +RtlQueryTagHeap +RtlQueryTimeZoneInformation +RtlQueueApcWow64Thread +RtlQueueWorkItem +RtlRaiseException +RtlRaiseStatus +RtlRandom +RtlRandomEx +RtlReAllocateHeap +RtlReadMemoryStream +RtlReadOutOfProcessMemoryStream +RtlRealPredecessor +RtlRealSuccessor +RtlRegisterSecureMemoryCacheCallback +RtlRegisterWait +RtlReleaseActivationContext +RtlReleaseMemoryStream +RtlReleasePebLock +RtlReleaseResource +RtlRemoteCall +RtlRemoveVectoredExceptionHandler +RtlResetRtlTranslations +RtlRestoreLastWin32Error +RtlRevertMemoryStream +RtlRunDecodeUnicodeString +RtlRunEncodeUnicodeString +RtlSecondsSince1970ToTime +RtlSecondsSince1980ToTime +RtlSeekMemoryStream +RtlSelfRelativeToAbsoluteSD +RtlSelfRelativeToAbsoluteSD2 +RtlSetAllBits +RtlSetAttributesSecurityDescriptor +RtlSetBits +RtlSetControlSecurityDescriptor +RtlSetCriticalSectionSpinCount +RtlSetCurrentDirectory_U +RtlSetCurrentEnvironment +RtlSetDaclSecurityDescriptor +RtlSetEnvironmentVariable +RtlSetGroupSecurityDescriptor +RtlSetHeapInformation +RtlSetInformationAcl +RtlSetIoCompletionCallback +RtlSetLastWin32Error +RtlSetLastWin32ErrorAndNtStatusFromNtStatus +RtlSetMemoryStreamSize +RtlSetOwnerSecurityDescriptor +RtlSetProcessIsCritical +RtlSetSaclSecurityDescriptor +RtlSetSecurityDescriptorRMControl +RtlSetSecurityObject +RtlSetSecurityObjectEx +RtlSetThreadIsCritical +RtlSetThreadPoolStartFunc +RtlSetTimeZoneInformation +RtlSetTimer +RtlSetUserFlagsHeap +RtlSetUserValueHeap +RtlSizeHeap +RtlSplay +RtlStartRXact +RtlStatMemoryStream +RtlStringFromGUID +RtlSubAuthorityCountSid +RtlSubAuthoritySid +RtlSubtreePredecessor +RtlSubtreeSuccessor +RtlSystemTimeToLocalTime +RtlTimeFieldsToTime +RtlTimeToElapsedTimeFields +RtlTimeToSecondsSince1970 +RtlTimeToSecondsSince1980 +RtlTimeToTimeFields +RtlTraceDatabaseAdd +RtlTraceDatabaseCreate +RtlTraceDatabaseDestroy +RtlTraceDatabaseEnumerate +RtlTraceDatabaseFind +RtlTraceDatabaseLock +RtlTraceDatabaseUnlock +RtlTraceDatabaseValidate +RtlTryEnterCriticalSection +RtlUnhandledExceptionFilter +RtlUnhandledExceptionFilter2 +RtlUnicodeStringToAnsiSize +RtlUnicodeStringToAnsiString +RtlUnicodeStringToCountedOemString +RtlUnicodeStringToInteger +RtlUnicodeStringToOemSize +RtlUnicodeStringToOemString +RtlUnicodeToCustomCPN +RtlUnicodeToMultiByteN +RtlUnicodeToMultiByteSize +RtlUnicodeToOemN +RtlUniform +RtlUnlockBootStatusData +RtlUnlockHeap +RtlUnlockMemoryStreamRegion +RtlUnwind +RtlUpcaseUnicodeChar +RtlUpcaseUnicodeString +RtlUpcaseUnicodeStringToAnsiString +RtlUpcaseUnicodeStringToCountedOemString +RtlUpcaseUnicodeStringToOemString +RtlUpcaseUnicodeToCustomCPN +RtlUpcaseUnicodeToMultiByteN +RtlUpcaseUnicodeToOemN +RtlUpdateTimer +RtlUpperChar +RtlUpperString +RtlValidAcl +RtlValidRelativeSecurityDescriptor +RtlValidSecurityDescriptor +RtlValidSid +RtlValidateHeap +RtlValidateProcessHeaps +RtlValidateUnicodeString +RtlVerifyVersionInfo +RtlWalkFrameChain +RtlWalkHeap +RtlWriteMemoryStream +RtlWriteRegistryValue +RtlZeroHeap +RtlZeroMemory +RtlZombifyActivationContext +RtlpApplyLengthFunction +RtlpEnsureBufferSize +RtlpNotOwnerCriticalSection +RtlpNtCreateKey +RtlpNtEnumerateSubKey +RtlpNtMakeTemporaryKey +RtlpNtOpenKey +RtlpNtQueryValueKey +RtlpNtSetValueKey +RtlpUnWaitCriticalSection +RtlpWaitForCriticalSection +RtlxAnsiStringToUnicodeSize +RtlxOemStringToUnicodeSize +RtlxUnicodeStringToAnsiSize +RtlxUnicodeStringToOemSize +VerSetConditionMask +ZwAcceptConnectPort +ZwAccessCheck +ZwAccessCheckAndAuditAlarm +ZwAccessCheckByType +ZwAccessCheckByTypeAndAuditAlarm +ZwAccessCheckByTypeResultList +ZwAccessCheckByTypeResultListAndAuditAlarm +ZwAccessCheckByTypeResultListAndAuditAlarmByHandle +ZwAddAtom +ZwAddBootEntry +ZwAdjustGroupsToken +ZwAdjustPrivilegesToken +ZwAlertResumeThread +ZwAlertThread +ZwAllocateLocallyUniqueId +ZwAllocateUserPhysicalPages +ZwAllocateUuids +ZwAllocateVirtualMemory +ZwAreMappedFilesTheSame +ZwAssignProcessToJobObject +ZwCallbackReturn +ZwCancelIoFile +ZwCancelTimer +ZwClearEvent +ZwClose +ZwCloseObjectAuditAlarm +ZwCompactKeys +ZwCompareTokens +ZwCompleteConnectPort +ZwCompressKey +ZwConnectPort +ZwContinue +ZwCreateDebugObject +ZwCreateDirectoryObject +ZwCreateEvent +ZwCreateEventPair +ZwCreateFile +ZwCreateIoCompletion +ZwCreateJobObject +ZwCreateJobSet +ZwCreateKey +ZwCreateKeyedEvent +ZwCreateMailslotFile +ZwCreateMutant +ZwCreateNamedPipeFile +ZwCreatePagingFile +ZwCreatePort +ZwCreateProcess +ZwCreateProcessEx +ZwCreateProfile +ZwCreateSection +ZwCreateSemaphore +ZwCreateSymbolicLinkObject +ZwCreateThread +ZwCreateTimer +ZwCreateToken +ZwCreateWaitablePort +ZwDebugActiveProcess +ZwDebugContinue +ZwDelayExecution +ZwDeleteAtom +ZwDeleteBootEntry +ZwDeleteFile +ZwDeleteKey +ZwDeleteObjectAuditAlarm +ZwDeleteValueKey +ZwDeviceIoControlFile +ZwDisplayString +ZwDuplicateObject +ZwDuplicateToken +ZwEnumerateBootEntries +ZwEnumerateKey +ZwEnumerateSystemEnvironmentValuesEx +ZwEnumerateValueKey +ZwExtendSection +ZwFilterToken +ZwFindAtom +ZwFlushBuffersFile +ZwFlushInstructionCache +ZwFlushKey +ZwFlushVirtualMemory +ZwFlushWriteBuffer +ZwFreeUserPhysicalPages +ZwFreeVirtualMemory +ZwFsControlFile +ZwGetContextThread +ZwGetDevicePowerState +ZwGetWriteWatch +ZwImpersonateAnonymousToken +ZwImpersonateClientOfPort +ZwImpersonateThread +ZwInitializeRegistry +ZwInitiatePowerAction +ZwIsProcessInJob +ZwIsSystemResumeAutomatic +ZwListenPort +ZwLoadDriver +ZwLoadKey +ZwLoadKey2 +ZwLockFile +ZwLockProductActivationKeys +ZwLockRegistryKey +ZwLockVirtualMemory +ZwMakePermanentObject +ZwMakeTemporaryObject +ZwMapUserPhysicalPages +ZwMapUserPhysicalPagesScatter +ZwMapViewOfSection +ZwModifyBootEntry +ZwNotifyChangeDirectoryFile +ZwNotifyChangeKey +ZwNotifyChangeMultipleKeys +ZwOpenDirectoryObject +ZwOpenEvent +ZwOpenEventPair +ZwOpenFile +ZwOpenIoCompletion +ZwOpenJobObject +ZwOpenKey +ZwOpenKeyedEvent +ZwOpenMutant +ZwOpenObjectAuditAlarm +ZwOpenProcess +ZwOpenProcessToken +ZwOpenProcessTokenEx +ZwOpenSection +ZwOpenSemaphore +ZwOpenSymbolicLinkObject +ZwOpenThread +ZwOpenThreadToken +ZwOpenThreadTokenEx +ZwOpenTimer +ZwPlugPlayControl +ZwPowerInformation +ZwPrivilegeCheck +ZwPrivilegeObjectAuditAlarm +ZwPrivilegedServiceAuditAlarm +ZwProtectVirtualMemory +ZwPulseEvent +ZwQueryAttributesFile +ZwQueryBootEntryOrder +ZwQueryBootOptions +ZwQueryDebugFilterState +ZwQueryDefaultLocale +ZwQueryDefaultUILanguage +ZwQueryDirectoryFile +ZwQueryDirectoryObject +ZwQueryEaFile +ZwQueryEvent +ZwQueryFullAttributesFile +ZwQueryInformationAtom +ZwQueryInformationFile +ZwQueryInformationJobObject +ZwQueryInformationPort +ZwQueryInformationProcess +ZwQueryInformationThread +ZwQueryInformationToken +ZwQueryInstallUILanguage +ZwQueryIntervalProfile +ZwQueryIoCompletion +ZwQueryKey +ZwQueryMultipleValueKey +ZwQueryMutant +ZwQueryObject +ZwQueryOpenSubKeys +ZwQueryPerformanceCounter +ZwQueryPortInformationProcess +ZwQueryQuotaInformationFile +ZwQuerySection +ZwQuerySecurityObject +ZwQuerySemaphore +ZwQuerySymbolicLinkObject +ZwQuerySystemEnvironmentValue +ZwQuerySystemEnvironmentValueEx +ZwQuerySystemInformation +ZwQuerySystemTime +ZwQueryTimer +ZwQueryTimerResolution +ZwQueryValueKey +ZwQueryVirtualMemory +ZwQueryVolumeInformationFile +ZwQueueApcThread +ZwRaiseException +ZwRaiseHardError +ZwReadFile +ZwReadFileScatter +ZwReadRequestData +ZwReadVirtualMemory +ZwRegisterThreadTerminatePort +ZwReleaseKeyedEvent +ZwReleaseMutant +ZwReleaseSemaphore +ZwRemoveIoCompletion +ZwRemoveProcessDebug +ZwRenameKey +ZwReplaceKey +ZwReplyPort +ZwReplyWaitReceivePort +ZwReplyWaitReceivePortEx +ZwReplyWaitReplyPort +ZwRequestPort +ZwRequestWaitReplyPort +ZwResetEvent +ZwResetWriteWatch +ZwRestoreKey +ZwResumeProcess +ZwResumeThread +ZwSaveKey +ZwSaveKeyEx +ZwSaveMergedKeys +ZwSecureConnectPort +ZwSetBootEntryOrder +ZwSetBootOptions +ZwSetContextThread +ZwSetDebugFilterState +ZwSetDefaultHardErrorPort +ZwSetDefaultLocale +ZwSetDefaultUILanguage +ZwSetEaFile +ZwSetEvent +ZwSetEventBoostPriority +ZwSetHighEventPair +ZwSetHighWaitLowEventPair +ZwSetInformationDebugObject +ZwSetInformationFile +ZwSetInformationJobObject +ZwSetInformationKey +ZwSetInformationObject +ZwSetInformationProcess +ZwSetInformationThread +ZwSetInformationToken +ZwSetIntervalProfile +ZwSetIoCompletion +ZwSetLdtEntries +ZwSetLowEventPair +ZwSetLowWaitHighEventPair +ZwSetQuotaInformationFile +ZwSetSecurityObject +ZwSetSystemEnvironmentValue +ZwSetSystemEnvironmentValueEx +ZwSetSystemInformation +ZwSetSystemPowerState +ZwSetSystemTime +ZwSetThreadExecutionState +ZwSetTimer +ZwSetTimerResolution +ZwSetUuidSeed +ZwSetValueKey +ZwSetVolumeInformationFile +ZwShutdownSystem +ZwSignalAndWaitForSingleObject +ZwStartProfile +ZwStopProfile +ZwSuspendProcess +ZwSuspendThread +ZwSystemDebugControl +ZwTerminateJobObject +ZwTerminateProcess +ZwTerminateThread +ZwTestAlert +ZwTraceEvent +ZwTranslateFilePath +ZwUnloadDriver +ZwUnloadKey +ZwUnloadKeyEx +ZwUnlockFile +ZwUnlockVirtualMemory +ZwUnmapViewOfSection +ZwVdmControl +ZwWaitForDebugEvent +ZwWaitForKeyedEvent +ZwWaitForMultipleObjects +ZwWaitForSingleObject +ZwWaitHighEventPair +ZwWaitLowEventPair +ZwWriteFile +ZwWriteFileGather +ZwWriteRequestData +ZwWriteVirtualMemory +ZwYieldExecution +__isascii +__iscsym +__iscsymf +__toascii +_alldiv +_alldvrm +_allmul +_allrem +_allshl +_allshr +_atoi64 +_aulldiv +_aulldvrm +_aullrem +_aullshr +_fltused +_i64toa +_i64tow +_itoa +_itow +_lfind +_ltoa +_ltow +_memccpy +_memicmp +_snprintf +_snwprintf +_splitpath +_strcmpi +_stricmp +_strlwr +_strnicmp +_strupr +_ui64toa +_ui64tow +_ultoa +_ultow +_vsnprintf +_vsnwprintf +_wcsicmp +_wcslwr +_wcsnicmp +_wcsupr +_wtoi +_wtoi64 +_wtol +abs +atan +atoi +atol +bsearch +ceil +cos +fabs +floor +isalnum +isalpha +iscntrl +isdigit +isgraph +islower +isprint +ispunct +isspace +isupper +iswalpha +iswctype +iswdigit +iswlower +iswspace +iswxdigit +isxdigit +labs +log +mbstowcs +memchr +memcmp +memcpy +memmove +memset +pow +qsort +sin +sprintf +sqrt +sscanf +strcat +strchr +strcmp +strcpy +strcspn +strlen +strncat +strncmp +strncpy +strpbrk +strrchr +strspn +strstr +strtol +strtoul +swprintf +tan +tolower +toupper +towlower +towupper +vDbgPrintEx +vDbgPrintExWithPrefix +vsprintf +wcscat +wcschr +wcscmp +wcscpy +wcscspn +wcslen +wcsncat +wcsncmp +wcsncpy +wcspbrk +wcsrchr +wcsspn +wcsstr +wcstol +wcstombs +wcstoul diff --git a/contrib/db/libmdbx/src/elements/osal.c b/contrib/db/libmdbx/src/elements/osal.c new file mode 100644 index 00000000..9f13d510 --- /dev/null +++ b/contrib/db/libmdbx/src/elements/osal.c @@ -0,0 +1,1899 @@ +/* https://en.wikipedia.org/wiki/Operating_system_abstraction_layer */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "internals.h" + +#if defined(_WIN32) || defined(_WIN64) + +#include + +static int waitstatus2errcode(DWORD result) { + switch (result) { + case WAIT_OBJECT_0: + return MDBX_SUCCESS; + case WAIT_FAILED: + return GetLastError(); + case WAIT_ABANDONED: + return ERROR_ABANDONED_WAIT_0; + case WAIT_IO_COMPLETION: + return ERROR_USER_APC; + case WAIT_TIMEOUT: + return ERROR_TIMEOUT; + default: + return ERROR_UNHANDLED_ERROR; + } +} + +/* Map a result from an NTAPI call to WIN32 error code. */ +static int ntstatus2errcode(NTSTATUS status) { + DWORD dummy; + OVERLAPPED ov; + memset(&ov, 0, sizeof(ov)); + ov.Internal = status; + return GetOverlappedResult(NULL, &ov, &dummy, FALSE) ? MDBX_SUCCESS + : GetLastError(); +} + +/* We use native NT APIs to setup the memory map, so that we can + * let the DB file grow incrementally instead of always preallocating + * the full size. These APIs are defined in and + * but those headers are meant for driver-level development and + * conflict with the regular user-level headers, so we explicitly + * declare them here. Using these APIs also means we must link to + * ntdll.dll, which is not linked by default in user code. */ + +extern NTSTATUS NTAPI NtCreateSection( + OUT PHANDLE SectionHandle, IN ACCESS_MASK DesiredAccess, + IN OPTIONAL POBJECT_ATTRIBUTES ObjectAttributes, + IN OPTIONAL PLARGE_INTEGER MaximumSize, IN ULONG SectionPageProtection, + IN ULONG AllocationAttributes, IN OPTIONAL HANDLE FileHandle); + +typedef struct _SECTION_BASIC_INFORMATION { + ULONG Unknown; + ULONG SectionAttributes; + LARGE_INTEGER SectionSize; +} SECTION_BASIC_INFORMATION, *PSECTION_BASIC_INFORMATION; + +typedef enum _SECTION_INFORMATION_CLASS { + SectionBasicInformation, + SectionImageInformation, + SectionRelocationInformation, // name:wow64:whNtQuerySection_SectionRelocationInformation + MaxSectionInfoClass +} SECTION_INFORMATION_CLASS; + +extern NTSTATUS NTAPI NtQuerySection( + IN HANDLE SectionHandle, IN SECTION_INFORMATION_CLASS InformationClass, + OUT PVOID InformationBuffer, IN ULONG InformationBufferSize, + OUT PULONG ResultLength OPTIONAL); + +extern NTSTATUS NTAPI NtExtendSection(IN HANDLE SectionHandle, + IN PLARGE_INTEGER NewSectionSize); + +typedef enum _SECTION_INHERIT { ViewShare = 1, ViewUnmap = 2 } SECTION_INHERIT; + +extern NTSTATUS NTAPI NtMapViewOfSection( + IN HANDLE SectionHandle, IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, + IN ULONG_PTR ZeroBits, IN SIZE_T CommitSize, + IN OUT OPTIONAL PLARGE_INTEGER SectionOffset, IN OUT PSIZE_T ViewSize, + IN SECTION_INHERIT InheritDisposition, IN ULONG AllocationType, + IN ULONG Win32Protect); + +extern NTSTATUS NTAPI NtUnmapViewOfSection(IN HANDLE ProcessHandle, + IN OPTIONAL PVOID BaseAddress); + +extern NTSTATUS NTAPI NtClose(HANDLE Handle); + +extern NTSTATUS NTAPI NtAllocateVirtualMemory( + IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG_PTR ZeroBits, + IN OUT PSIZE_T RegionSize, IN ULONG AllocationType, IN ULONG Protect); + +extern NTSTATUS NTAPI NtFreeVirtualMemory(IN HANDLE ProcessHandle, + IN PVOID *BaseAddress, + IN OUT PSIZE_T RegionSize, + IN ULONG FreeType); + +#ifndef WOF_CURRENT_VERSION +typedef struct _WOF_EXTERNAL_INFO { + DWORD Version; + DWORD Provider; +} WOF_EXTERNAL_INFO, *PWOF_EXTERNAL_INFO; +#endif /* WOF_CURRENT_VERSION */ + +#ifndef WIM_PROVIDER_CURRENT_VERSION +#define WIM_PROVIDER_HASH_SIZE 20 + +typedef struct _WIM_PROVIDER_EXTERNAL_INFO { + DWORD Version; + DWORD Flags; + LARGE_INTEGER DataSourceId; + BYTE ResourceHash[WIM_PROVIDER_HASH_SIZE]; +} WIM_PROVIDER_EXTERNAL_INFO, *PWIM_PROVIDER_EXTERNAL_INFO; +#endif /* WIM_PROVIDER_CURRENT_VERSION */ + +#ifndef FILE_PROVIDER_CURRENT_VERSION +typedef struct _FILE_PROVIDER_EXTERNAL_INFO_V1 { + ULONG Version; + ULONG Algorithm; + ULONG Flags; +} FILE_PROVIDER_EXTERNAL_INFO_V1, *PFILE_PROVIDER_EXTERNAL_INFO_V1; +#endif /* FILE_PROVIDER_CURRENT_VERSION */ + +#ifndef STATUS_OBJECT_NOT_EXTERNALLY_BACKED +#define STATUS_OBJECT_NOT_EXTERNALLY_BACKED ((NTSTATUS)0xC000046DL) +#endif +#ifndef STATUS_INVALID_DEVICE_REQUEST +#define STATUS_INVALID_DEVICE_REQUEST ((NTSTATUS)0xC0000010L) +#endif + +#ifndef FILE_DEVICE_FILE_SYSTEM +#define FILE_DEVICE_FILE_SYSTEM 0x00000009 +#endif + +#ifndef FSCTL_GET_EXTERNAL_BACKING +#define FSCTL_GET_EXTERNAL_BACKING \ + CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 196, METHOD_BUFFERED, FILE_ANY_ACCESS) +#endif + +#endif /* _WIN32 || _WIN64 */ + +/*----------------------------------------------------------------------------*/ + +#if _POSIX_C_SOURCE > 200212 && \ + /* workaround for avoid musl libc wrong prototype */ ( \ + defined(__GLIBC__) || defined(__GNU_LIBRARY__)) +/* Prototype should match libc runtime. ISO POSIX (2003) & LSB 1.x-3.x */ +__extern_C void __assert_fail(const char *assertion, const char *file, + unsigned line, const char *function) +#ifdef __THROW + __THROW +#else + __nothrow +#endif /* __THROW */ + __noreturn; + +#elif defined(__APPLE__) || defined(__MACH__) +__extern_C void __assert_rtn(const char *function, const char *file, int line, + const char *assertion) /* __nothrow */ +#ifdef __dead2 + __dead2 +#else + __noreturn +#endif /* __dead2 */ +#ifdef __disable_tail_calls + __disable_tail_calls +#endif /* __disable_tail_calls */ + ; + +#define __assert_fail(assertion, file, line, function) \ + __assert_rtn(function, file, line, assertion) +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__BSD__) || defined(__NETBSD__) || defined(__bsdi__) || \ + defined(__DragonFly__) +__extern_C void __assert(const char *function, const char *file, int line, + const char *assertion) /* __nothrow */ +#ifdef __dead2 + __dead2 +#else + __noreturn +#endif /* __dead2 */ +#ifdef __disable_tail_calls + __disable_tail_calls +#endif /* __disable_tail_calls */ + ; +#define __assert_fail(assertion, file, line, function) \ + __assert(function, file, line, assertion) + +#endif /* __assert_fail */ + +MDBX_INTERNAL_FUNC void __cold mdbx_assert_fail(const MDBX_env *env, + const char *msg, + const char *func, int line) { +#if MDBX_DEBUG + if (env && env->me_assert_func) { + env->me_assert_func(env, msg, func, line); + return; + } +#else + (void)env; +#endif /* MDBX_DEBUG */ + + if (mdbx_debug_logger) + mdbx_debug_log(MDBX_LOG_FATAL, func, line, "assert: %s\n", msg); + else { +#if defined(_WIN32) || defined(_WIN64) + char *message = nullptr; + const int num = mdbx_asprintf(&message, "\r\nMDBX-ASSERTION: %s, %s:%u", + msg, func ? func : "unknown", line); + if (num < 1 || !message) + message = ""; + OutputDebugStringA(message); + if (IsDebuggerPresent()) + DebugBreak(); +#else + __assert_fail(msg, "mdbx", line, func); +#endif + } + +#if defined(_WIN32) || defined(_WIN64) + FatalExit(ERROR_UNHANDLED_ERROR); +#else + abort(); +#endif +} + +MDBX_INTERNAL_FUNC __cold void mdbx_panic(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + + char *message = nullptr; + const int num = mdbx_vasprintf(&message, fmt, ap); + va_end(ap); + const char *const const_message = + (num < 1 || !message) ? "" + : message; + +#if defined(_WIN32) || defined(_WIN64) + OutputDebugStringA("\r\nMDBX-PANIC: "); + OutputDebugStringA(const_message); + if (IsDebuggerPresent()) + DebugBreak(); + FatalExit(ERROR_UNHANDLED_ERROR); +#else + __assert_fail(const_message, "mdbx", 0, "panic"); + abort(); +#endif +} + +/*----------------------------------------------------------------------------*/ + +#ifndef mdbx_vasprintf +MDBX_INTERNAL_FUNC int mdbx_vasprintf(char **strp, const char *fmt, + va_list ap) { + va_list ones; + va_copy(ones, ap); + int needed = vsnprintf(nullptr, 0, fmt, ap); + + if (unlikely(needed < 0 || needed >= INT_MAX)) { + *strp = nullptr; + va_end(ones); + return needed; + } + + *strp = mdbx_malloc(needed + 1); + if (unlikely(*strp == nullptr)) { + va_end(ones); +#if defined(_WIN32) || defined(_WIN64) + SetLastError(MDBX_ENOMEM); +#else + errno = MDBX_ENOMEM; +#endif + return -1; + } + + int actual = vsnprintf(*strp, needed + 1, fmt, ones); + va_end(ones); + + assert(actual == needed); + if (unlikely(actual < 0)) { + mdbx_free(*strp); + *strp = nullptr; + } + return actual; +} +#endif /* mdbx_vasprintf */ + +#ifndef mdbx_asprintf +MDBX_INTERNAL_FUNC int mdbx_asprintf(char **strp, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + int rc = mdbx_vasprintf(strp, fmt, ap); + va_end(ap); + return rc; +} +#endif /* mdbx_asprintf */ + +#ifndef mdbx_memalign_alloc +MDBX_INTERNAL_FUNC int mdbx_memalign_alloc(size_t alignment, size_t bytes, + void **result) { +#if defined(_WIN32) || defined(_WIN64) + (void)alignment; + *result = VirtualAlloc(NULL, bytes, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + return *result ? MDBX_SUCCESS : MDBX_ENOMEM /* ERROR_OUTOFMEMORY */; +#elif defined(_ISOC11_SOURCE) + *result = aligned_alloc(alignment, bytes); + return *result ? MDBX_SUCCESS : errno; +#elif _POSIX_VERSION >= 200112L + *result = nullptr; + return posix_memalign(result, alignment, bytes); +#elif __GLIBC_PREREQ(2, 16) || __STDC_VERSION__ >= 201112L + *result = memalign(alignment, bytes); + return *result ? MDBX_SUCCESS : errno; +#else +#error FIXME +#endif +} +#endif /* mdbx_memalign_alloc */ + +#ifndef mdbx_memalign_free +MDBX_INTERNAL_FUNC void mdbx_memalign_free(void *ptr) { +#if defined(_WIN32) || defined(_WIN64) + VirtualFree(ptr, 0, MEM_RELEASE); +#else + mdbx_free(ptr); +#endif +} +#endif /* mdbx_memalign_free */ + +#ifndef mdbx_strdup +char *mdbx_strdup(const char *str) { + if (!str) + return NULL; + size_t bytes = strlen(str) + 1; + char *dup = mdbx_malloc(bytes); + if (dup) + memcpy(dup, str, bytes); + return dup; +} +#endif /* mdbx_strdup */ + +/*----------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC int mdbx_condmutex_init(mdbx_condmutex_t *condmutex) { +#if defined(_WIN32) || defined(_WIN64) + int rc = MDBX_SUCCESS; + condmutex->event = NULL; + condmutex->mutex = CreateMutex(NULL, FALSE, NULL); + if (!condmutex->mutex) + return GetLastError(); + + condmutex->event = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!condmutex->event) { + rc = GetLastError(); + (void)CloseHandle(condmutex->mutex); + condmutex->mutex = NULL; + } + return rc; +#else + memset(condmutex, 0, sizeof(mdbx_condmutex_t)); + int rc = pthread_mutex_init(&condmutex->mutex, NULL); + if (rc == 0) { + rc = pthread_cond_init(&condmutex->cond, NULL); + if (rc != 0) + (void)pthread_mutex_destroy(&condmutex->mutex); + } + return rc; +#endif +} + +static bool is_allzeros(const void *ptr, size_t bytes) { + const uint8_t *u8 = ptr; + for (size_t i = 0; i < bytes; ++i) + if (u8[i] != 0) + return false; + return true; +} + +MDBX_INTERNAL_FUNC int mdbx_condmutex_destroy(mdbx_condmutex_t *condmutex) { + int rc = MDBX_EINVAL; +#if defined(_WIN32) || defined(_WIN64) + if (condmutex->event) { + rc = CloseHandle(condmutex->event) ? MDBX_SUCCESS : GetLastError(); + if (rc == MDBX_SUCCESS) + condmutex->event = NULL; + } + if (condmutex->mutex) { + rc = CloseHandle(condmutex->mutex) ? MDBX_SUCCESS : GetLastError(); + if (rc == MDBX_SUCCESS) + condmutex->mutex = NULL; + } +#else + if (!is_allzeros(&condmutex->cond, sizeof(condmutex->cond))) { + rc = pthread_cond_destroy(&condmutex->cond); + if (rc == 0) + memset(&condmutex->cond, 0, sizeof(condmutex->cond)); + } + if (!is_allzeros(&condmutex->mutex, sizeof(condmutex->mutex))) { + rc = pthread_mutex_destroy(&condmutex->mutex); + if (rc == 0) + memset(&condmutex->mutex, 0, sizeof(condmutex->mutex)); + } +#endif + return rc; +} + +MDBX_INTERNAL_FUNC int mdbx_condmutex_lock(mdbx_condmutex_t *condmutex) { +#if defined(_WIN32) || defined(_WIN64) + DWORD code = WaitForSingleObject(condmutex->mutex, INFINITE); + return waitstatus2errcode(code); +#else + return pthread_mutex_lock(&condmutex->mutex); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_condmutex_unlock(mdbx_condmutex_t *condmutex) { +#if defined(_WIN32) || defined(_WIN64) + return ReleaseMutex(condmutex->mutex) ? MDBX_SUCCESS : GetLastError(); +#else + return pthread_mutex_unlock(&condmutex->mutex); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_condmutex_signal(mdbx_condmutex_t *condmutex) { +#if defined(_WIN32) || defined(_WIN64) + return SetEvent(condmutex->event) ? MDBX_SUCCESS : GetLastError(); +#else + return pthread_cond_signal(&condmutex->cond); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_condmutex_wait(mdbx_condmutex_t *condmutex) { +#if defined(_WIN32) || defined(_WIN64) + DWORD code = + SignalObjectAndWait(condmutex->mutex, condmutex->event, INFINITE, FALSE); + if (code == WAIT_OBJECT_0) + code = WaitForSingleObject(condmutex->mutex, INFINITE); + return waitstatus2errcode(code); +#else + return pthread_cond_wait(&condmutex->cond, &condmutex->mutex); +#endif +} + +/*----------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC int mdbx_fastmutex_init(mdbx_fastmutex_t *fastmutex) { +#if defined(_WIN32) || defined(_WIN64) + InitializeCriticalSection(fastmutex); + return MDBX_SUCCESS; +#else + return pthread_mutex_init(fastmutex, NULL); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_fastmutex_destroy(mdbx_fastmutex_t *fastmutex) { +#if defined(_WIN32) || defined(_WIN64) + DeleteCriticalSection(fastmutex); + return MDBX_SUCCESS; +#else + return pthread_mutex_destroy(fastmutex); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_fastmutex_acquire(mdbx_fastmutex_t *fastmutex) { +#if defined(_WIN32) || defined(_WIN64) + EnterCriticalSection(fastmutex); + return MDBX_SUCCESS; +#else + return pthread_mutex_lock(fastmutex); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_fastmutex_release(mdbx_fastmutex_t *fastmutex) { +#if defined(_WIN32) || defined(_WIN64) + LeaveCriticalSection(fastmutex); + return MDBX_SUCCESS; +#else + return pthread_mutex_unlock(fastmutex); +#endif +} + +/*----------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC int mdbx_removefile(const char *pathname) { +#if defined(_WIN32) || defined(_WIN64) + return DeleteFileA(pathname) ? MDBX_SUCCESS : GetLastError(); +#else + return unlink(pathname) ? errno : MDBX_SUCCESS; +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_openfile(const char *pathname, int flags, + mode_t mode, mdbx_filehandle_t *fd, + bool exclusive) { + *fd = INVALID_HANDLE_VALUE; +#if defined(_WIN32) || defined(_WIN64) + (void)mode; + size_t wlen = mbstowcs(nullptr, pathname, INT_MAX); + if (wlen < 1 || wlen > /* MAX_PATH */ INT16_MAX) + return ERROR_INVALID_NAME; + wchar_t *const pathnameW = _alloca((wlen + 1) * sizeof(wchar_t)); + if (wlen != mbstowcs(pathnameW, pathname, wlen + 1)) + return ERROR_INVALID_NAME; + + DWORD DesiredAccess, ShareMode; + DWORD FlagsAndAttributes = FILE_ATTRIBUTE_NORMAL; + switch (flags & (O_RDONLY | O_WRONLY | O_RDWR)) { + default: + return ERROR_INVALID_PARAMETER; + case O_RDONLY: + DesiredAccess = GENERIC_READ; + ShareMode = + exclusive ? FILE_SHARE_READ : (FILE_SHARE_READ | FILE_SHARE_WRITE); + break; + case O_WRONLY: /* assume for MDBX_env_copy() and friends output */ + DesiredAccess = GENERIC_WRITE; + ShareMode = 0; + FlagsAndAttributes |= FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH; + break; + case O_RDWR: + DesiredAccess = GENERIC_READ | GENERIC_WRITE; + ShareMode = exclusive ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE); + break; + } + + DWORD CreationDisposition; + switch (flags & (O_EXCL | O_CREAT)) { + default: + return ERROR_INVALID_PARAMETER; + case 0: + CreationDisposition = OPEN_EXISTING; + break; + case O_EXCL | O_CREAT: + CreationDisposition = CREATE_NEW; + FlagsAndAttributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + break; + case O_CREAT: + CreationDisposition = OPEN_ALWAYS; + FlagsAndAttributes |= FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; + break; + } + + *fd = CreateFileW(pathnameW, DesiredAccess, ShareMode, NULL, + CreationDisposition, FlagsAndAttributes, NULL); + + if (*fd == INVALID_HANDLE_VALUE) + return GetLastError(); + if ((flags & O_CREAT) && GetLastError() != ERROR_ALREADY_EXISTS) { + /* set FILE_ATTRIBUTE_NOT_CONTENT_INDEXED for new file */ + DWORD FileAttributes = GetFileAttributesA(pathname); + if (FileAttributes == INVALID_FILE_ATTRIBUTES || + !SetFileAttributesA(pathname, FileAttributes | + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED)) { + int rc = GetLastError(); + CloseHandle(*fd); + *fd = INVALID_HANDLE_VALUE; + return rc; + } + } +#else + (void)exclusive; +#ifdef O_CLOEXEC + flags |= O_CLOEXEC; +#endif /* O_CLOEXEC */ + *fd = open(pathname, flags, mode); + if (*fd < 0) + return errno; + +#if defined(FD_CLOEXEC) && !defined(O_CLOEXEC) + int fd_flags = fcntl(*fd, F_GETFD); + if (fd_flags != -1) + (void)fcntl(*fd, F_SETFD, fd_flags | FD_CLOEXEC); +#endif /* FD_CLOEXEC && !O_CLOEXEC */ + + if ((flags & (O_RDONLY | O_WRONLY | O_RDWR)) == O_WRONLY) { + /* assume for MDBX_env_copy() and friends output */ +#if defined(O_DIRECT) + int fd_flags = fcntl(*fd, F_GETFD); + if (fd_flags != -1) + (void)fcntl(*fd, F_SETFL, fd_flags | O_DIRECT); +#endif /* O_DIRECT */ +#if defined(F_NOCACHE) + (void)fcntl(*fd, F_NOCACHE, 1); +#endif /* F_NOCACHE */ + } +#endif + + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_closefile(mdbx_filehandle_t fd) { +#if defined(_WIN32) || defined(_WIN64) + return CloseHandle(fd) ? MDBX_SUCCESS : GetLastError(); +#else + return (close(fd) == 0) ? MDBX_SUCCESS : errno; +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_pread(mdbx_filehandle_t fd, void *buf, size_t bytes, + uint64_t offset) { + if (bytes > MAX_WRITE) + return MDBX_EINVAL; +#if defined(_WIN32) || defined(_WIN64) + OVERLAPPED ov; + ov.hEvent = 0; + ov.Offset = (DWORD)offset; + ov.OffsetHigh = HIGH_DWORD(offset); + + DWORD read = 0; + if (unlikely(!ReadFile(fd, buf, (DWORD)bytes, &read, &ov))) { + int rc = GetLastError(); + return (rc == MDBX_SUCCESS) ? /* paranoia */ ERROR_READ_FAULT : rc; + } +#else + STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + intptr_t read = pread(fd, buf, bytes, offset); + if (read < 0) { + int rc = errno; + return (rc == MDBX_SUCCESS) ? /* paranoia */ MDBX_EIO : rc; + } +#endif + return (bytes == (size_t)read) ? MDBX_SUCCESS : MDBX_ENODATA; +} + +MDBX_INTERNAL_FUNC int mdbx_pwrite(mdbx_filehandle_t fd, const void *buf, + size_t bytes, uint64_t offset) { + while (true) { +#if defined(_WIN32) || defined(_WIN64) + OVERLAPPED ov; + ov.hEvent = 0; + ov.Offset = (DWORD)offset; + ov.OffsetHigh = HIGH_DWORD(offset); + + DWORD written; + if (unlikely(!WriteFile( + fd, buf, likely(bytes <= MAX_WRITE) ? (DWORD)bytes : MAX_WRITE, + &written, &ov))) + return GetLastError(); + if (likely(bytes == written)) + return MDBX_SUCCESS; +#else + STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + const intptr_t written = + pwrite(fd, buf, likely(bytes <= MAX_WRITE) ? bytes : MAX_WRITE, offset); + if (likely(bytes == (size_t)written)) + return MDBX_SUCCESS; + if (written < 0) { + const int rc = errno; + if (rc != EINTR) + return rc; + continue; + } +#endif + bytes -= written; + offset += written; + buf = (char *)buf + written; + } +} + +MDBX_INTERNAL_FUNC int mdbx_write(mdbx_filehandle_t fd, const void *buf, + size_t bytes) { + while (true) { +#if defined(_WIN32) || defined(_WIN64) + DWORD written; + if (unlikely(!WriteFile( + fd, buf, likely(bytes <= MAX_WRITE) ? (DWORD)bytes : MAX_WRITE, + &written, nullptr))) + return GetLastError(); + if (likely(bytes == written)) + return MDBX_SUCCESS; +#else + STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + const intptr_t written = + write(fd, buf, likely(bytes <= MAX_WRITE) ? bytes : MAX_WRITE); + if (likely(bytes == (size_t)written)) + return MDBX_SUCCESS; + if (written < 0) { + const int rc = errno; + if (rc != EINTR) + return rc; + continue; + } +#endif + bytes -= written; + buf = (char *)buf + written; + } +} + +int mdbx_pwritev(mdbx_filehandle_t fd, struct iovec *iov, int iovcnt, + uint64_t offset, size_t expected_written) { +#if defined(_WIN32) || defined(_WIN64) || defined(__APPLE__) + size_t written = 0; + for (int i = 0; i < iovcnt; ++i) { + int rc = mdbx_pwrite(fd, iov[i].iov_base, iov[i].iov_len, offset); + if (unlikely(rc != MDBX_SUCCESS)) + return rc; + written += iov[i].iov_len; + offset += iov[i].iov_len; + } + return (expected_written == written) ? MDBX_SUCCESS + : MDBX_EIO /* ERROR_WRITE_FAULT */; +#else + int rc; + intptr_t written; + do { + STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + written = pwritev(fd, iov, iovcnt, offset); + if (likely(expected_written == (size_t)written)) + return MDBX_SUCCESS; + rc = errno; + } while (rc == EINTR); + return (written < 0) ? rc : MDBX_EIO /* Use which error code? */; +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_filesync(mdbx_filehandle_t fd, + enum mdbx_syncmode_bits mode_bits) { +#if defined(_WIN32) || defined(_WIN64) + return ((mode_bits & (MDBX_SYNC_DATA | MDBX_SYNC_IODQ)) == 0 || + FlushFileBuffers(fd)) + ? MDBX_SUCCESS + : GetLastError(); +#else + +#if defined(__APPLE__) && \ + MDBX_OSX_SPEED_INSTEADOF_DURABILITY == MDBX_OSX_WANNA_DURABILITY + if (mode_bits & MDBX_SYNC_IODQ) + return likely(fcntl(fd, F_FULLFSYNC) != -1) ? MDBX_SUCCESS : errno; +#endif /* MacOS */ +#if defined(__linux__) || defined(__gnu_linux__) + if (mode_bits == MDBX_SYNC_SIZE && mdbx_linux_kernel_version >= 0x03060000) + return MDBX_SUCCESS; +#endif /* Linux */ + int rc; + do { +#if defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0 + /* LY: This code is always safe and without appreciable performance + * degradation, even on a kernel with fdatasync's bug. + * + * For more info about of a corresponding fdatasync() bug + * see http://www.spinics.net/lists/linux-ext4/msg33714.html */ + if ((mode_bits & MDBX_SYNC_SIZE) == 0) { + if (fdatasync(fd) == 0) + return MDBX_SUCCESS; + } else +#else + (void)mode_bits; +#endif + if (fsync(fd) == 0) + return MDBX_SUCCESS; + rc = errno; + } while (rc == EINTR); + return rc; +#endif +} + +int mdbx_filesize(mdbx_filehandle_t fd, uint64_t *length) { +#if defined(_WIN32) || defined(_WIN64) + BY_HANDLE_FILE_INFORMATION info; + if (!GetFileInformationByHandle(fd, &info)) + return GetLastError(); + *length = info.nFileSizeLow | (uint64_t)info.nFileSizeHigh << 32; +#else + struct stat st; + + STATIC_ASSERT_MSG(sizeof(off_t) <= sizeof(uint64_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + if (fstat(fd, &st)) + return errno; + + *length = st.st_size; +#endif + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_is_pipe(mdbx_filehandle_t fd) { +#if defined(_WIN32) || defined(_WIN64) + switch (GetFileType(fd)) { + case FILE_TYPE_DISK: + return MDBX_RESULT_FALSE; + case FILE_TYPE_CHAR: + case FILE_TYPE_PIPE: + return MDBX_RESULT_TRUE; + default: + return GetLastError(); + } +#else + struct stat info; + if (fstat(fd, &info)) + return errno; + switch (info.st_mode & S_IFMT) { + case S_IFBLK: + case S_IFREG: + return MDBX_RESULT_FALSE; + case S_IFCHR: + case S_IFIFO: + case S_IFSOCK: + return MDBX_RESULT_TRUE; + case S_IFDIR: + case S_IFLNK: + default: + return MDBX_INCOMPATIBLE; + } +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_ftruncate(mdbx_filehandle_t fd, uint64_t length) { +#if defined(_WIN32) || defined(_WIN64) + if (mdbx_SetFileInformationByHandle) { + FILE_END_OF_FILE_INFO EndOfFileInfo; + EndOfFileInfo.EndOfFile.QuadPart = length; + return mdbx_SetFileInformationByHandle(fd, FileEndOfFileInfo, + &EndOfFileInfo, + sizeof(FILE_END_OF_FILE_INFO)) + ? MDBX_SUCCESS + : GetLastError(); + } else { + LARGE_INTEGER li; + li.QuadPart = length; + return (SetFilePointerEx(fd, li, NULL, FILE_BEGIN) && SetEndOfFile(fd)) + ? MDBX_SUCCESS + : GetLastError(); + } +#else + STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + return ftruncate(fd, length) == 0 ? MDBX_SUCCESS : errno; +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_fseek(mdbx_filehandle_t fd, uint64_t pos) { +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER li; + li.QuadPart = pos; + return SetFilePointerEx(fd, li, NULL, FILE_BEGIN) ? MDBX_SUCCESS + : GetLastError(); +#else + STATIC_ASSERT_MSG(sizeof(off_t) >= sizeof(size_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + return (lseek(fd, pos, SEEK_SET) < 0) ? errno : MDBX_SUCCESS; +#endif +} + +/*----------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC int +mdbx_thread_create(mdbx_thread_t *thread, + THREAD_RESULT(THREAD_CALL *start_routine)(void *), + void *arg) { +#if defined(_WIN32) || defined(_WIN64) + *thread = CreateThread(NULL, 0, start_routine, arg, 0, NULL); + return *thread ? MDBX_SUCCESS : GetLastError(); +#else + return pthread_create(thread, NULL, start_routine, arg); +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_thread_join(mdbx_thread_t thread) { +#if defined(_WIN32) || defined(_WIN64) + DWORD code = WaitForSingleObject(thread, INFINITE); + return waitstatus2errcode(code); +#else + void *unused_retval = &unused_retval; + return pthread_join(thread, &unused_retval); +#endif +} + +/*----------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC int mdbx_msync(mdbx_mmap_t *map, size_t offset, + size_t length, int async) { + uint8_t *ptr = (uint8_t *)map->address + offset; +#if defined(_WIN32) || defined(_WIN64) + if (FlushViewOfFile(ptr, length) && (async || FlushFileBuffers(map->fd))) + return MDBX_SUCCESS; + return GetLastError(); +#else +#if defined(__linux__) || defined(__gnu_linux__) + if (async && mdbx_linux_kernel_version > 0x02061300) + /* Since Linux 2.6.19, MS_ASYNC is in fact a no-op, + since the kernel properly tracks dirty pages and flushes them to storage + as necessary. */ + return MDBX_SUCCESS; +#endif /* Linux */ + const int mode = async ? MS_ASYNC : MS_SYNC; + int rc = (msync(ptr, length, mode) == 0) ? MDBX_SUCCESS : errno; +#if defined(__APPLE__) && \ + MDBX_OSX_SPEED_INSTEADOF_DURABILITY == MDBX_OSX_WANNA_DURABILITY + if (rc == MDBX_SUCCESS && mode == MS_SYNC) + rc = likely(fcntl(map->fd, F_FULLFSYNC) != -1) ? MDBX_SUCCESS : errno; +#endif /* MacOS */ + return rc; +#endif +} + +MDBX_INTERNAL_FUNC int mdbx_check4nonlocal(mdbx_filehandle_t handle, + int flags) { +#if defined(_WIN32) || defined(_WIN64) + if (GetFileType(handle) != FILE_TYPE_DISK) + return ERROR_FILE_OFFLINE; + + if (mdbx_GetFileInformationByHandleEx) { + FILE_REMOTE_PROTOCOL_INFO RemoteProtocolInfo; + if (mdbx_GetFileInformationByHandleEx(handle, FileRemoteProtocolInfo, + &RemoteProtocolInfo, + sizeof(RemoteProtocolInfo))) { + + if ((RemoteProtocolInfo.Flags & REMOTE_PROTOCOL_INFO_FLAG_OFFLINE) && + !(flags & MDBX_RDONLY)) + return ERROR_FILE_OFFLINE; + if (!(RemoteProtocolInfo.Flags & REMOTE_PROTOCOL_INFO_FLAG_LOOPBACK) && + !(flags & MDBX_EXCLUSIVE)) + return ERROR_REMOTE_STORAGE_MEDIA_ERROR; + } + } + + if (mdbx_NtFsControlFile) { + NTSTATUS rc; + struct { + WOF_EXTERNAL_INFO wof_info; + union { + WIM_PROVIDER_EXTERNAL_INFO wim_info; + FILE_PROVIDER_EXTERNAL_INFO_V1 file_info; + }; + size_t reserved_for_microsoft_madness[42]; + } GetExternalBacking_OutputBuffer; + IO_STATUS_BLOCK StatusBlock; + rc = mdbx_NtFsControlFile(handle, NULL, NULL, NULL, &StatusBlock, + FSCTL_GET_EXTERNAL_BACKING, NULL, 0, + &GetExternalBacking_OutputBuffer, + sizeof(GetExternalBacking_OutputBuffer)); + if (NT_SUCCESS(rc)) { + if (!(flags & MDBX_EXCLUSIVE)) + return ERROR_REMOTE_STORAGE_MEDIA_ERROR; + } else if (rc != STATUS_OBJECT_NOT_EXTERNALLY_BACKED && + rc != STATUS_INVALID_DEVICE_REQUEST) + return ntstatus2errcode(rc); + } + + if (mdbx_GetVolumeInformationByHandleW && mdbx_GetFinalPathNameByHandleW) { + WCHAR *PathBuffer = mdbx_malloc(sizeof(WCHAR) * INT16_MAX); + if (!PathBuffer) + return MDBX_ENOMEM; + + int rc = MDBX_SUCCESS; + DWORD VolumeSerialNumber, FileSystemFlags; + if (!mdbx_GetVolumeInformationByHandleW(handle, PathBuffer, INT16_MAX, + &VolumeSerialNumber, NULL, + &FileSystemFlags, NULL, 0)) { + rc = GetLastError(); + goto bailout; + } + + if ((flags & MDBX_RDONLY) == 0) { + if (FileSystemFlags & + (FILE_SEQUENTIAL_WRITE_ONCE | FILE_READ_ONLY_VOLUME | + FILE_VOLUME_IS_COMPRESSED)) { + rc = ERROR_REMOTE_STORAGE_MEDIA_ERROR; + goto bailout; + } + } + + if (!mdbx_GetFinalPathNameByHandleW(handle, PathBuffer, INT16_MAX, + FILE_NAME_NORMALIZED | + VOLUME_NAME_NT)) { + rc = GetLastError(); + goto bailout; + } + + if (_wcsnicmp(PathBuffer, L"\\Device\\Mup\\", 12) == 0) { + if (!(flags & MDBX_EXCLUSIVE)) { + rc = ERROR_REMOTE_STORAGE_MEDIA_ERROR; + goto bailout; + } + } else if (mdbx_GetFinalPathNameByHandleW(handle, PathBuffer, INT16_MAX, + FILE_NAME_NORMALIZED | + VOLUME_NAME_DOS)) { + UINT DriveType = GetDriveTypeW(PathBuffer); + if (DriveType == DRIVE_NO_ROOT_DIR && + _wcsnicmp(PathBuffer, L"\\\\?\\", 4) == 0 && + _wcsnicmp(PathBuffer + 5, L":\\", 2) == 0) { + PathBuffer[7] = 0; + DriveType = GetDriveTypeW(PathBuffer + 4); + } + switch (DriveType) { + case DRIVE_CDROM: + if (flags & MDBX_RDONLY) + break; + // fall through + case DRIVE_UNKNOWN: + case DRIVE_NO_ROOT_DIR: + case DRIVE_REMOTE: + default: + if (!(flags & MDBX_EXCLUSIVE)) + rc = ERROR_REMOTE_STORAGE_MEDIA_ERROR; + // fall through + case DRIVE_REMOVABLE: + case DRIVE_FIXED: + case DRIVE_RAMDISK: + break; + } + } + bailout: + mdbx_free(PathBuffer); + return rc; + } +#else + (void)handle; + /* TODO: check for NFS handle ? */ + (void)flags; +#endif + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_mmap(const int flags, mdbx_mmap_t *map, + const size_t size, const size_t limit, + const bool truncate) { + assert(size <= limit); + map->limit = 0; + map->current = 0; + map->address = nullptr; +#if defined(_WIN32) || defined(_WIN64) + map->section = NULL; + map->filesize = 0; +#endif /* Windows */ + + int err = mdbx_check4nonlocal(map->fd, flags); + if (unlikely(err != MDBX_SUCCESS)) + return err; + + if ((flags & MDBX_RDONLY) == 0 && truncate) { + err = mdbx_ftruncate(map->fd, size); + if (err != MDBX_SUCCESS) + return err; +#if defined(_WIN32) || defined(_WIN64) + map->filesize = size; +#else + map->current = size; +#endif + } else { + uint64_t filesize; + err = mdbx_filesize(map->fd, &filesize); + if (err != MDBX_SUCCESS) + return err; +#if defined(_WIN32) || defined(_WIN64) + map->filesize = filesize; +#else + map->current = (filesize > limit) ? limit : (size_t)filesize; +#endif + } + +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER SectionSize; + SectionSize.QuadPart = size; + err = NtCreateSection( + &map->section, + /* DesiredAccess */ + (flags & MDBX_WRITEMAP) + ? SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE | + SECTION_MAP_WRITE + : SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE, + /* ObjectAttributes */ NULL, /* MaximumSize (InitialSize) */ &SectionSize, + /* SectionPageProtection */ + (flags & MDBX_RDONLY) ? PAGE_READONLY : PAGE_READWRITE, + /* AllocationAttributes */ SEC_RESERVE, map->fd); + if (!NT_SUCCESS(err)) + return ntstatus2errcode(err); + + SIZE_T ViewSize = (flags & MDBX_RDONLY) ? 0 : limit; + err = NtMapViewOfSection( + map->section, GetCurrentProcess(), &map->address, + /* ZeroBits */ 0, + /* CommitSize */ 0, + /* SectionOffset */ NULL, &ViewSize, + /* InheritDisposition */ ViewUnmap, + /* AllocationType */ (flags & MDBX_RDONLY) ? 0 : MEM_RESERVE, + /* Win32Protect */ + (flags & MDBX_WRITEMAP) ? PAGE_READWRITE : PAGE_READONLY); + if (!NT_SUCCESS(err)) { + NtClose(map->section); + map->section = 0; + map->address = nullptr; + return ntstatus2errcode(err); + } + assert(map->address != MAP_FAILED); + + map->current = (size_t)SectionSize.QuadPart; + map->limit = ViewSize; + +#else + + map->address = mmap( + NULL, limit, (flags & MDBX_WRITEMAP) ? PROT_READ | PROT_WRITE : PROT_READ, + MAP_SHARED, map->fd, 0); + + if (unlikely(map->address == MAP_FAILED)) { + map->limit = 0; + map->current = 0; + map->address = nullptr; + return errno; + } + map->limit = limit; + +#ifdef MADV_DONTFORK + if (unlikely(madvise(map->address, map->limit, MADV_DONTFORK) != 0)) + return errno; +#endif +#ifdef MADV_NOHUGEPAGE + (void)madvise(map->address, map->limit, MADV_NOHUGEPAGE); +#endif + +#endif + + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_munmap(mdbx_mmap_t *map) { +#if defined(_WIN32) || defined(_WIN64) + if (map->section) + NtClose(map->section); + NTSTATUS rc = NtUnmapViewOfSection(GetCurrentProcess(), map->address); + if (!NT_SUCCESS(rc)) + ntstatus2errcode(rc); +#else + if (unlikely(munmap(map->address, map->limit))) + return errno; +#endif + + map->limit = 0; + map->current = 0; + map->address = nullptr; + return MDBX_SUCCESS; +} + +MDBX_INTERNAL_FUNC int mdbx_mresize(int flags, mdbx_mmap_t *map, size_t size, + size_t limit) { + assert(size <= limit); +#if defined(_WIN32) || defined(_WIN64) + assert(size != map->current || limit != map->limit || size < map->filesize); + + NTSTATUS status; + LARGE_INTEGER SectionSize; + int err, rc = MDBX_SUCCESS; + + if (!(flags & MDBX_RDONLY) && limit == map->limit && size > map->current) { + /* growth rw-section */ + SectionSize.QuadPart = size; + status = NtExtendSection(map->section, &SectionSize); + if (NT_SUCCESS(status)) { + map->current = size; + if (map->filesize < size) + map->filesize = size; + } + return ntstatus2errcode(status); + } + + if (limit > map->limit) { + /* check ability of address space for growth before umnap */ + PVOID BaseAddress = (PBYTE)map->address + map->limit; + SIZE_T RegionSize = limit - map->limit; + status = NtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress, 0, + &RegionSize, MEM_RESERVE, PAGE_NOACCESS); + if (!NT_SUCCESS(status)) + return ntstatus2errcode(status); + + status = NtFreeVirtualMemory(GetCurrentProcess(), &BaseAddress, &RegionSize, + MEM_RELEASE); + if (!NT_SUCCESS(status)) + return ntstatus2errcode(status); + } + + /* Windows unable: + * - shrink a mapped file; + * - change size of mapped view; + * - extend read-only mapping; + * Therefore we should unmap/map entire section. */ + status = NtUnmapViewOfSection(GetCurrentProcess(), map->address); + if (!NT_SUCCESS(status)) + return ntstatus2errcode(status); + status = NtClose(map->section); + map->section = NULL; + PVOID ReservedAddress = NULL; + SIZE_T ReservedSize = limit; + + if (!NT_SUCCESS(status)) { + bailout_ntstatus: + err = ntstatus2errcode(status); + bailout: + map->address = NULL; + map->current = map->limit = 0; + if (ReservedAddress) + (void)NtFreeVirtualMemory(GetCurrentProcess(), &ReservedAddress, + &ReservedSize, MEM_RELEASE); + return err; + } + + /* resizing of the file may take a while, + * therefore we reserve address space to avoid occupy it by other threads */ + ReservedAddress = map->address; + status = NtAllocateVirtualMemory(GetCurrentProcess(), &ReservedAddress, 0, + &ReservedSize, MEM_RESERVE, PAGE_NOACCESS); + if (!NT_SUCCESS(status)) { + ReservedAddress = NULL; + if (status != /* STATUS_CONFLICTING_ADDRESSES */ 0xC0000018) + goto bailout_ntstatus /* no way to recovery */; + + /* assume we can change base address if mapping size changed or prev address + * couldn't be used */ + map->address = NULL; + } + +retry_file_and_section: + err = mdbx_filesize(map->fd, &map->filesize); + if (err != MDBX_SUCCESS) + goto bailout; + + if ((flags & MDBX_RDONLY) == 0 && map->filesize != size) { + err = mdbx_ftruncate(map->fd, size); + if (err == MDBX_SUCCESS) + map->filesize = size; + /* ignore error, because Windows unable shrink file + * that already mapped (by another process) */ + } + + SectionSize.QuadPart = size; + status = NtCreateSection( + &map->section, + /* DesiredAccess */ + (flags & MDBX_WRITEMAP) + ? SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE | + SECTION_MAP_WRITE + : SECTION_QUERY | SECTION_MAP_READ | SECTION_EXTEND_SIZE, + /* ObjectAttributes */ NULL, + /* MaximumSize (InitialSize) */ &SectionSize, + /* SectionPageProtection */ + (flags & MDBX_RDONLY) ? PAGE_READONLY : PAGE_READWRITE, + /* AllocationAttributes */ SEC_RESERVE, map->fd); + + if (!NT_SUCCESS(status)) + goto bailout_ntstatus; + + if (ReservedAddress) { + /* release reserved address space */ + status = NtFreeVirtualMemory(GetCurrentProcess(), &ReservedAddress, + &ReservedSize, MEM_RELEASE); + ReservedAddress = NULL; + if (!NT_SUCCESS(status)) + goto bailout_ntstatus; + } + +retry_mapview:; + SIZE_T ViewSize = (flags & MDBX_RDONLY) ? size : limit; + status = NtMapViewOfSection( + map->section, GetCurrentProcess(), &map->address, + /* ZeroBits */ 0, + /* CommitSize */ 0, + /* SectionOffset */ NULL, &ViewSize, + /* InheritDisposition */ ViewUnmap, + /* AllocationType */ (flags & MDBX_RDONLY) ? 0 : MEM_RESERVE, + /* Win32Protect */ + (flags & MDBX_WRITEMAP) ? PAGE_READWRITE : PAGE_READONLY); + + if (!NT_SUCCESS(status)) { + if (status == /* STATUS_CONFLICTING_ADDRESSES */ 0xC0000018 && + map->address) { + /* try remap at another base address */ + map->address = NULL; + goto retry_mapview; + } + NtClose(map->section); + map->section = NULL; + + if (map->address && (size != map->current || limit != map->limit)) { + /* try remap with previously size and limit, + * but will return MDBX_RESULT_TRUE on success */ + rc = MDBX_RESULT_TRUE; + size = map->current; + limit = map->limit; + goto retry_file_and_section; + } + + /* no way to recovery */ + goto bailout_ntstatus; + } + assert(map->address != MAP_FAILED); + + map->current = (size_t)SectionSize.QuadPart; + map->limit = ViewSize; +#else + + uint64_t filesize; + int rc = mdbx_filesize(map->fd, &filesize); + if (rc != MDBX_SUCCESS) + return rc; + + if (flags & MDBX_RDONLY) { + map->current = (filesize > limit) ? limit : (size_t)filesize; + if (map->current != size) + rc = MDBX_RESULT_TRUE; + } else if (filesize != size) { + rc = mdbx_ftruncate(map->fd, size); + if (rc != MDBX_SUCCESS) + return rc; + map->current = size; + } + + if (limit != map->limit) { +#if defined(_GNU_SOURCE) && (defined(__linux__) || defined(__gnu_linux__)) + void *ptr = mremap(map->address, map->limit, limit, + /* LY: in case changing the mapping size calling code + must guarantees the absence of competing threads, + and a willingness to another base address */ + MREMAP_MAYMOVE); + if (ptr == MAP_FAILED) { + rc = errno; + return (rc == EAGAIN || rc == ENOMEM) ? MDBX_RESULT_TRUE : rc; + } + map->address = ptr; + map->limit = limit; + +#ifdef MADV_DONTFORK + if (unlikely(madvise(map->address, map->limit, MADV_DONTFORK) != 0)) + return errno; +#endif + +#ifdef MADV_NOHUGEPAGE + (void)madvise(map->address, map->limit, MADV_NOHUGEPAGE); +#endif + +#else + rc = MDBX_RESULT_TRUE; +#endif /* _GNU_SOURCE && __linux__ */ + } +#endif + return rc; +} + +/*----------------------------------------------------------------------------*/ + +MDBX_INTERNAL_FUNC __cold void mdbx_osal_jitter(bool tiny) { + for (;;) { +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__x86_64__) + const unsigned salt = 277u * (unsigned)__rdtsc(); +#else + const unsigned salt = rand(); +#endif + + const unsigned coin = salt % (tiny ? 29u : 43u); + if (coin < 43 / 3) + break; +#if defined(_WIN32) || defined(_WIN64) + SwitchToThread(); + if (coin > 43 * 2 / 3) + Sleep(1); +#else + sched_yield(); + if (coin > 43 * 2 / 3) + usleep(coin); +#endif + } +} + +#if defined(_WIN32) || defined(_WIN64) +#elif defined(__APPLE__) || defined(__MACH__) +#include +#elif defined(__linux__) || defined(__gnu_linux__) +static __cold clockid_t choice_monoclock(void) { + struct timespec probe; +#if defined(CLOCK_BOOTTIME) + if (clock_gettime(CLOCK_BOOTTIME, &probe) == 0) + return CLOCK_BOOTTIME; +#elif defined(CLOCK_MONOTONIC_RAW) + if (clock_gettime(CLOCK_MONOTONIC_RAW, &probe) == 0) + return CLOCK_MONOTONIC_RAW; +#elif defined(CLOCK_MONOTONIC_COARSE) + if (clock_gettime(CLOCK_MONOTONIC_COARSE, &probe) == 0) + return CLOCK_MONOTONIC_COARSE; +#endif + return CLOCK_MONOTONIC; +} +#endif + +/*----------------------------------------------------------------------------*/ + +#if defined(_WIN32) || defined(_WIN64) +static LARGE_INTEGER performance_frequency; +#elif defined(__APPLE__) || defined(__MACH__) +static uint64_t ratio_16dot16_to_monotine; +#endif + +MDBX_INTERNAL_FUNC uint64_t +mdbx_osal_16dot16_to_monotime(uint32_t seconds_16dot16) { +#if defined(_WIN32) || defined(_WIN64) + if (unlikely(performance_frequency.QuadPart == 0)) + QueryPerformanceFrequency(&performance_frequency); + const uint64_t ratio = performance_frequency.QuadPart; +#elif defined(__APPLE__) || defined(__MACH__) + if (unlikely(ratio_16dot16_to_monotine == 0)) { + mach_timebase_info_data_t ti; + mach_timebase_info(&ti); + ratio_16dot16_to_monotine = UINT64_C(1000000000) * ti.denom / ti.numer; + } + const uint64_t ratio = ratio_16dot16_to_monotine; +#else + const uint64_t ratio = UINT64_C(1000000000); +#endif + return (ratio * seconds_16dot16 + 32768) >> 16; +} + +MDBX_INTERNAL_FUNC uint32_t mdbx_osal_monotime_to_16dot16(uint64_t monotime) { + static uint64_t limit; + if (unlikely(monotime > limit)) { + if (limit != 0) + return UINT32_MAX; + limit = mdbx_osal_16dot16_to_monotime(UINT32_MAX - 1); + if (monotime > limit) + return UINT32_MAX; + } +#if defined(_WIN32) || defined(_WIN64) + return (uint32_t)((monotime << 16) / performance_frequency.QuadPart); +#elif defined(__APPLE__) || defined(__MACH__) + return (uint32_t)((monotime << 16) / ratio_16dot16_to_monotine); +#else + return (uint32_t)(monotime * 128 / 1953125); +#endif +} + +MDBX_INTERNAL_FUNC uint64_t mdbx_osal_monotime(void) { +#if defined(_WIN32) || defined(_WIN64) + LARGE_INTEGER counter; + counter.QuadPart = 0; + QueryPerformanceCounter(&counter); + return counter.QuadPart; +#elif defined(__APPLE__) || defined(__MACH__) + return mach_absolute_time(); +#else + +#if defined(__linux__) || defined(__gnu_linux__) + static clockid_t posix_clockid = -1; + if (unlikely(posix_clockid < 0)) + posix_clockid = choice_monoclock(); +#elif defined(CLOCK_MONOTONIC) +#define posix_clockid CLOCK_MONOTONIC +#else +#define posix_clockid CLOCK_REALTIME +#endif + + struct timespec ts; + if (unlikely(clock_gettime(posix_clockid, &ts) != 0)) { + ts.tv_nsec = 0; + ts.tv_sec = 0; + } + return ts.tv_sec * UINT64_C(1000000000) + ts.tv_nsec; +#endif +} + +/*----------------------------------------------------------------------------*/ + +static void bootid_shake(bin128_t *p) { + /* Bob Jenkins's PRNG: https://burtleburtle.net/bob/rand/smallprng.html */ + const uint32_t e = p->a - (p->b << 23 | p->b >> 9); + p->a = p->b ^ (p->c << 16 | p->c >> 16); + p->b = p->c + (p->d << 11 | p->d >> 21); + p->c = p->d + e; + p->d = e + p->a; +} + +static void bootid_collect(bin128_t *p, const void *s, size_t n) { + p->y += UINT64_C(64526882297375213); + bootid_shake(p); + for (size_t i = 0; i < n; ++i) { + bootid_shake(p); + p->y ^= UINT64_C(48797879452804441) * ((const uint8_t *)s)[i]; + bootid_shake(p); + p->y += 14621231; + } + bootid_shake(p); + + /* minor non-linear tomfoolery */ + const unsigned z = p->x % 61; + p->y = p->y << z | p->y >> (64 - z); + bootid_shake(p); + bootid_shake(p); + const unsigned q = p->x % 59; + p->y = p->y << q | p->y >> (64 - q); + bootid_shake(p); + bootid_shake(p); + bootid_shake(p); +} + +#if defined(_WIN32) || defined(_WIN64) + +static uint64_t windows_systemtime_ms() { + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + return ((uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime) / 10000ul; +} + +static uint64_t windows_bootime(void) { + unsigned confirmed = 0; + uint64_t boottime = 0; + uint64_t up0 = mdbx_GetTickCount64(); + uint64_t st0 = windows_systemtime_ms(); + for (uint64_t fuse = st0; up0 && st0 < fuse + 1000 * 1000u / 42;) { + YieldProcessor(); + const uint64_t up1 = mdbx_GetTickCount64(); + const uint64_t st1 = windows_systemtime_ms(); + if (st1 > fuse && st1 == st0 && up1 == up0) { + uint64_t diff = st1 - up1; + if (boottime == diff) { + if (++confirmed > 4) + return boottime; + } else { + confirmed = 0; + boottime = diff; + } + fuse = st1; + Sleep(1); + } + st0 = st1; + up0 = up1; + } + return 0; +} + +static LSTATUS mdbx_RegGetValue(HKEY hkey, LPCWSTR lpSubKey, LPCWSTR lpValue, + DWORD dwFlags, LPDWORD pdwType, PVOID pvData, + LPDWORD pcbData) { + LSTATUS rc = + RegGetValueW(hkey, lpSubKey, lpValue, dwFlags, pdwType, pvData, pcbData); + if (rc != ERROR_FILE_NOT_FOUND) + return rc; + + rc = RegGetValueW(hkey, lpSubKey, lpValue, + dwFlags | 0x00010000 /* RRF_SUBKEY_WOW6464KEY */, pdwType, + pvData, pcbData); + if (rc != ERROR_FILE_NOT_FOUND) + return rc; + return RegGetValueW(hkey, lpSubKey, lpValue, + dwFlags | 0x00020000 /* RRF_SUBKEY_WOW6432KEY */, pdwType, + pvData, pcbData); +} +#endif + +static __cold __maybe_unused bool bootid_parse_uuid(bin128_t *s, const void *p, + const size_t n) { + if (n > 31) { + unsigned bits = 0; + for (unsigned i = 0; i < n; ++i) /* try parse an UUID in text form */ { + uint8_t c = ((const uint8_t *)p)[i]; + if (c >= '0' && c <= '9') + c -= '0'; + else if (c >= 'a' && c <= 'f') + c -= 'a' - 10; + else if (c >= 'A' && c <= 'F') + c -= 'A' - 10; + else + continue; + assert(c <= 15); + c ^= s->y >> 60; + s->y = s->y << 4 | s->x >> 60; + s->x = s->x << 4 | c; + bits += 4; + } + if (bits > 42 * 3) + /* UUID parsed successfully */ + return true; + } + + if (n > 15) /* is enough handle it as a binary? */ { + if (n == sizeof(bin128_t)) { + bin128_t aligned; + memcpy(&aligned, p, sizeof(bin128_t)); + s->x += aligned.x; + s->y += aligned.y; + } else + bootid_collect(s, p, n); + return true; + } + + if (n) + bootid_collect(s, p, n); + return false; +} + +__cold MDBX_INTERNAL_FUNC bin128_t mdbx_osal_bootid(void) { + bin128_t bin = {{0, 0}}; + bool got_machineid = false, got_bootime = false, got_bootseq = false; + +#if defined(__linux__) || defined(__gnu_linux__) + { + const int fd = + open("/proc/sys/kernel/random/boot_id", O_RDONLY | O_NOFOLLOW); + if (fd != -1) { + struct statvfs fs; + char buf[42]; + const ssize_t len = + (fstatvfs(fd, &fs) == 0 && fs.f_fsid == /* procfs */ 0x00009FA0) + ? read(fd, buf, sizeof(buf)) + : -1; + close(fd); + if (len > 0 && bootid_parse_uuid(&bin, buf, len)) + return bin; + } + } +#endif /* Linux */ + +#if defined(__APPLE__) || defined(__MACH__) + { + char buf[42]; + size_t len = sizeof(buf); + if (!sysctlbyname("kern.bootsessionuuid", buf, &len, nullptr, 0) && + bootid_parse_uuid(&bin, buf, len)) + return bin; + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \ + __MAC_OS_X_VERSION_MIN_REQUIRED > 1050 + uuid_t uuid; + struct timespec wait = {0, 1000000000u / 42}; + if (!gethostuuid(uuid, &wait) && + bootid_parse_uuid(&bin, uuid, sizeof(uuid))) + got_machineid = true; +#endif /* > 10.5 */ + + struct timeval boottime; + len = sizeof(boottime); + if (!sysctlbyname("kern.boottime", &boottime, &len, nullptr, 0) && + len == sizeof(boottime) && boottime.tv_sec) + got_bootime = true; + } +#endif /* Apple/Darwin */ + +#if defined(_WIN32) || defined(_WIN64) + { + union buf { + DWORD BootId; + DWORD BaseTime; + SYSTEM_TIMEOFDAY_INFORMATION SysTimeOfDayInfo; + struct { + LARGE_INTEGER BootTime; + LARGE_INTEGER CurrentTime; + LARGE_INTEGER TimeZoneBias; + ULONG TimeZoneId; + ULONG Reserved; + ULONGLONG BootTimeBias; + ULONGLONG SleepTimeBias; + } SysTimeOfDayInfoHacked; + wchar_t MachineGuid[42]; + char DigitalProductId[248]; + } buf; + + static const wchar_t HKLM_MicrosoftCryptography[] = + L"SOFTWARE\\Microsoft\\Cryptography"; + DWORD len = sizeof(buf); + /* Windows is madness and must die */ + if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_MicrosoftCryptography, + L"MachineGuid", RRF_RT_ANY, NULL, &buf.MachineGuid, + &len) == ERROR_SUCCESS && + len > 42 && len < sizeof(buf)) + got_machineid = bootid_parse_uuid(&bin, &buf.MachineGuid, len); + + if (!got_machineid) { + /* again, Windows is madness */ + static const wchar_t HKLM_WindowsNT[] = + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; + static const wchar_t HKLM_WindowsNT_DPK[] = + L"SOFTWARE\\Microsoft\\Windows " + L"NT\\CurrentVersion\\DefaultProductKey"; + static const wchar_t HKLM_WindowsNT_DPK2[] = + L"SOFTWARE\\Microsoft\\Windows " + L"NT\\CurrentVersion\\DefaultProductKey2"; + + len = sizeof(buf); + if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_WindowsNT, + L"DigitalProductId", RRF_RT_ANY, NULL, + &buf.DigitalProductId, &len) == ERROR_SUCCESS && + len > 42 && len < sizeof(buf)) { + bootid_collect(&bin, &buf.DigitalProductId, len); + got_machineid = true; + } + len = sizeof(buf); + if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_WindowsNT_DPK, + L"DigitalProductId", RRF_RT_ANY, NULL, + &buf.DigitalProductId, &len) == ERROR_SUCCESS && + len > 42 && len < sizeof(buf)) { + bootid_collect(&bin, &buf.DigitalProductId, len); + got_machineid = true; + } + len = sizeof(buf); + if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_WindowsNT_DPK2, + L"DigitalProductId", RRF_RT_ANY, NULL, + &buf.DigitalProductId, &len) == ERROR_SUCCESS && + len > 42 && len < sizeof(buf)) { + bootid_collect(&bin, &buf.DigitalProductId, len); + got_machineid = true; + } + } + + static const wchar_t HKLM_PrefetcherParams[] = + L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory " + L"Management\\PrefetchParameters"; + len = sizeof(buf); + if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_PrefetcherParams, L"BootId", + RRF_RT_DWORD, NULL, &buf.BootId, + &len) == ERROR_SUCCESS && + len > 1 && len < sizeof(buf)) { + bootid_collect(&bin, &buf.BootId, len); + got_bootseq = true; + } + + len = sizeof(buf); + if (mdbx_RegGetValue(HKEY_LOCAL_MACHINE, HKLM_PrefetcherParams, L"BaseTime", + RRF_RT_DWORD, NULL, &buf.BaseTime, + &len) == ERROR_SUCCESS && + len >= sizeof(buf.BaseTime) && buf.BaseTime) { + bootid_collect(&bin, &buf.BaseTime, len); + got_bootime = true; + } + + /* BootTime from SYSTEM_TIMEOFDAY_INFORMATION */ + NTSTATUS status = NtQuerySystemInformation( + 0x03 /* SystemTmeOfDayInformation */, &buf.SysTimeOfDayInfo, + sizeof(buf.SysTimeOfDayInfo), &len); + if (NT_SUCCESS(status) && + len >= offsetof(union buf, SysTimeOfDayInfoHacked.BootTime) + + sizeof(buf.SysTimeOfDayInfoHacked.BootTime) && + buf.SysTimeOfDayInfoHacked.BootTime.QuadPart) { + bootid_collect(&bin, &buf.SysTimeOfDayInfoHacked.BootTime, + sizeof(buf.SysTimeOfDayInfoHacked.BootTime)); + got_bootime = true; + } + + if (!got_bootime) { + uint64_t boottime = windows_bootime(); + if (boottime) { + bootid_collect(&bin, &boottime, sizeof(boottime)); + got_bootime = true; + } + } + } +#endif /* Windows */ + +#if defined(CTL_HW) && defined(HW_UUID) + if (!got_machineid) { + static const int mib[] = {CTL_HW, HW_UUID}; + char buf[42]; + size_t len = sizeof(buf); + if (sysctl( +#ifdef SYSCTL_LEGACY_NONCONST_MIB + (int *) +#endif + mib, + ARRAY_LENGTH(mib), &buf, &len, NULL, 0) == 0) + got_machineid = bootid_parse_uuid(&bin, buf, len); + } +#endif /* CTL_HW && HW_UUID */ + +#if defined(CTL_KERN) && defined(KERN_HOSTUUID) + if (!got_machineid) { + static const int mib[] = {CTL_KERN, KERN_HOSTUUID}; + char buf[42]; + size_t len = sizeof(buf); + if (sysctl( +#ifdef SYSCTL_LEGACY_NONCONST_MIB + (int *) +#endif + mib, + ARRAY_LENGTH(mib), &buf, &len, NULL, 0) == 0) + got_machineid = bootid_parse_uuid(&bin, buf, len); + } +#endif /* CTL_KERN && KERN_HOSTUUID */ + +#if defined(__NetBSD__) + if (!got_machineid) { + char buf[42]; + size_t len = sizeof(buf); + if (sysctlbyname("machdep.dmi.system-uuid", buf, &len, NULL, 0) == 0) + got_machineid = bootid_parse_uuid(&bin, buf, len); + } +#endif /* __NetBSD__ */ + +#if _XOPEN_SOURCE_EXTENDED + if (!got_machineid) { + const int hostid = gethostid(); + if (hostid > 0) { + bootid_collect(&bin, &hostid, sizeof(hostid)); + got_machineid = true; + } + } +#endif /* _XOPEN_SOURCE_EXTENDED */ + + if (!got_machineid) { + lack: + bin.x = bin.y = 0; + return bin; + } + + /*--------------------------------------------------------------------------*/ + +#if defined(CTL_KERN) && defined(KERN_BOOTTIME) + if (!got_bootime) { + static const int mib[] = {CTL_KERN, KERN_BOOTTIME}; + struct timeval boottime; + size_t len = sizeof(boottime); + if (sysctl( +#ifdef SYSCTL_LEGACY_NONCONST_MIB + (int *) +#endif + mib, + ARRAY_LENGTH(mib), &boottime, &len, NULL, 0) == 0 && + len == sizeof(boottime) && boottime.tv_sec) { + bootid_collect(&bin, &boottime, len); + got_bootime = true; + } + } +#endif /* CTL_KERN && KERN_BOOTTIME */ + +#if defined(__sun) || defined(__SVR4) || defined(__svr4__) + if (!got_bootime) { + kstat_ctl_t *kc = kstat_open(); + if (kc) { + kstat_t *kp = kstat_lookup(kc, "unix", 0, "system_misc"); + if (kp && kstat_read(kc, kp, 0) != -1) { + kstat_named_t *kn = (kstat_named_t *)kstat_data_lookup(kp, "boot_time"); + if (kn) { + switch (kn->data_type) { + case KSTAT_DATA_INT32: + case KSTAT_DATA_UINT32: + bootid_collect(&bin, &kn->value, sizeof(int32_t)); + got_boottime = true; + case KSTAT_DATA_INT64: + case KSTAT_DATA_UINT64: + bootid_collect(&bin, &kn->value, sizeof(int64_t)); + got_boottime = true; + } + } + } + kstat_close(kc); + } + } +#endif /* SunOS / Solaris */ + +#if _XOPEN_SOURCE_EXTENDED && defined(BOOT_TIME) + if (!got_bootime) { + setutxent(); + const struct utmpx id = {.ut_type = BOOT_TIME}; + const struct utmpx *entry = getutxid(&id); + if (entry) { + bootid_collect(&bin, entry, sizeof(*entry)); + got_bootime = true; + while (unlikely((entry = getutxid(&id)) != nullptr)) { + /* have multiple reboot records, assuming we can distinguish next + * bootsession even if RTC is wrong or absent */ + bootid_collect(&bin, entry, sizeof(*entry)); + got_bootseq = true; + } + } + endutxent(); + } +#endif /* _XOPEN_SOURCE_EXTENDED && BOOT_TIME */ + + if (!got_bootseq) { + if (!got_bootime || !MDBX_TRUST_RTC) + goto lack; + +#if defined(_WIN32) || defined(_WIN64) + FILETIME now; + GetSystemTimeAsFileTime(&now); + if (0x1CCCCCC > now.dwHighDateTime) +#else + struct timespec mono, real; + if (clock_gettime(CLOCK_MONOTONIC, &mono) || + clock_gettime(CLOCK_REALTIME, &real) || + /* wrong time, RTC is mad or absent */ + 1555555555l > real.tv_sec || + /* seems no adjustment by RTC/NTP, i.e. a fake time */ + real.tv_sec < mono.tv_sec || 1234567890l > real.tv_sec - mono.tv_sec || + (real.tv_sec - mono.tv_sec) % 900u == 0) +#endif + goto lack; + } + + return bin; +} diff --git a/contrib/db/libmdbx/src/elements/osal.h b/contrib/db/libmdbx/src/elements/osal.h new file mode 100644 index 00000000..c1988ad7 --- /dev/null +++ b/contrib/db/libmdbx/src/elements/osal.h @@ -0,0 +1,959 @@ +/* https://en.wikipedia.org/wiki/Operating_system_abstraction_layer */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +/*----------------------------------------------------------------------------*/ +/* Microsoft compiler generates a lot of warning for self includes... */ + +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable : 4548) /* expression before comma has no effect; \ + expected expression with side - effect */ +#pragma warning(disable : 4530) /* C++ exception handler used, but unwind \ + * semantics are not enabled. Specify /EHsc */ +#pragma warning(disable : 4577) /* 'noexcept' used with no exception handling \ + * mode specified; termination on exception is \ + * not guaranteed. Specify /EHsc */ +#endif /* _MSC_VER (warnings) */ + +#if defined(_WIN32) || defined(_WIN64) +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif +#if !defined(_NO_CRT_STDIO_INLINE) && MDBX_BUILD_SHARED_LIBRARY && \ + !defined(MDBX_TOOLS) +#define _NO_CRT_STDIO_INLINE +#endif +#endif /* Windows */ + +/*----------------------------------------------------------------------------*/ +/* C99 includes */ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* C11 stdalign.h */ +#if __has_include() +#include +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L +#define alignas(N) _Alignas(N) +#elif defined(_MSC_VER) +#define alignas(N) __declspec(align(N)) +#elif __has_attribute(__aligned__) || defined(__GNUC__) +#define alignas(N) __attribute__((__aligned__(N))) +#else +#error "FIXME: Required _alignas() or equivalent." +#endif + +/*----------------------------------------------------------------------------*/ +/* Systems includes */ + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + defined(__BSD__) || defined(__NETBSD__) || defined(__bsdi__) || \ + defined(__DragonFly__) || defined(__APPLE__) || defined(__MACH__) +#include +#include +#include +#if defined(__FreeBSD__) || defined(__DragonFly__) +#include +#elif defined(__OpenBSD__) || defined(__NetBSD__) +#include +#else +#define SYSCTL_LEGACY_NONCONST_MIB +#endif +#include +#else +#include +#ifndef _POSIX_C_SOURCE +#ifdef _POSIX_SOURCE +#define _POSIX_C_SOURCE 1 +#else +#define _POSIX_C_SOURCE 0 +#endif +#endif +#endif /* !xBSD */ + +#if defined(__FreeBSD__) || defined(__OpenBSD__) || __has_include() +#include +#endif + +#if defined(__APPLE__) || defined(__MACH__) || __has_include() +#include +#endif /* MacOS */ + +#if defined(__MACH__) +#include +#include +#include +#include +#undef P_DIRTY +#endif + +#if defined(__linux__) || defined(__gnu_linux__) +#include +#include +#include +#endif /* Linux */ + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 0 +#endif + +#ifndef _XOPEN_SOURCE_EXTENDED +#define _XOPEN_SOURCE_EXTENDED 0 +#else +#include +#endif /* _XOPEN_SOURCE_EXTENDED */ + +#if defined(__sun) || defined(__SVR4) || defined(__svr4__) +#include +#endif /* SunOS/Solaris */ + +#if defined(_WIN32) || defined(_WIN64) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include +#define HAVE_SYS_STAT_H +#define HAVE_SYS_TYPES_H +typedef HANDLE mdbx_thread_t; +typedef unsigned mdbx_thread_key_t; +#define MDBX_OSAL_SECTION HANDLE +#define MAP_FAILED NULL +#define HIGH_DWORD(v) ((DWORD)((sizeof(v) > 4) ? ((uint64_t)(v) >> 32) : 0)) +#define THREAD_CALL WINAPI +#define THREAD_RESULT DWORD +typedef struct { + HANDLE mutex; + HANDLE event; +} mdbx_condmutex_t; +typedef CRITICAL_SECTION mdbx_fastmutex_t; + +#if MDBX_AVOID_CRT +#ifndef mdbx_malloc +static inline void *mdbx_malloc(size_t bytes) { + return LocalAlloc(LMEM_FIXED, bytes); +} +#endif /* mdbx_malloc */ + +#ifndef mdbx_calloc +static inline void *mdbx_calloc(size_t nelem, size_t size) { + return LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, nelem * size); +} +#endif /* mdbx_calloc */ + +#ifndef mdbx_realloc +static inline void *mdbx_realloc(void *ptr, size_t bytes) { + return LocalReAlloc(ptr, bytes, LMEM_MOVEABLE); +} +#endif /* mdbx_realloc */ + +#ifndef mdbx_free +#define mdbx_free LocalFree +#endif /* mdbx_free */ +#else +#define mdbx_malloc malloc +#define mdbx_calloc calloc +#define mdbx_realloc realloc +#define mdbx_free free +#define mdbx_strdup _strdup +#endif /* MDBX_AVOID_CRT */ + +#ifndef snprintf +#define snprintf _snprintf /* ntdll */ +#endif + +#ifndef vsnprintf +#define vsnprintf _vsnprintf /* ntdll */ +#endif + +#else /*----------------------------------------------------------------------*/ + +#include +#include +#include +#include +#include +#include +#include +#include +typedef pthread_t mdbx_thread_t; +typedef pthread_key_t mdbx_thread_key_t; +#define INVALID_HANDLE_VALUE (-1) +#define THREAD_CALL +#define THREAD_RESULT void * +typedef struct { + pthread_mutex_t mutex; + pthread_cond_t cond; +} mdbx_condmutex_t; +typedef pthread_mutex_t mdbx_fastmutex_t; +#define mdbx_malloc malloc +#define mdbx_calloc calloc +#define mdbx_realloc realloc +#define mdbx_free free +#define mdbx_strdup strdup +#endif /* Platform */ + +#if __GLIBC_PREREQ(2, 12) || defined(__FreeBSD__) || defined(malloc_usable_size) +/* malloc_usable_size() already provided */ +#elif defined(__APPLE__) +#define malloc_usable_size(ptr) malloc_size(ptr) +#elif defined(_MSC_VER) && !MDBX_AVOID_CRT +#define malloc_usable_size(ptr) _msize(ptr) +#endif /* malloc_usable_size */ + +/* *INDENT-OFF* */ +/* clang-format off */ +#if defined(HAVE_SYS_STAT_H) || __has_include() +#include +#endif +#if defined(HAVE_SYS_TYPES_H) || __has_include() +#include +#endif +#if defined(HAVE_SYS_FILE_H) || __has_include() +#include +#endif +/* *INDENT-ON* */ +/* clang-format on */ + +#ifndef SSIZE_MAX +#define SSIZE_MAX INTPTR_MAX +#endif + +#if !defined(MADV_DODUMP) && defined(MADV_CORE) +#define MADV_DODUMP MADV_CORE +#endif /* MADV_CORE -> MADV_DODUMP */ + +#if !defined(MADV_DONTDUMP) && defined(MADV_NOCORE) +#define MADV_DONTDUMP MADV_NOCORE +#endif /* MADV_NOCORE -> MADV_DONTDUMP */ + +#if defined(i386) || defined(__386) || defined(__i386) || defined(__i386__) || \ + defined(i486) || defined(__i486) || defined(__i486__) || \ + defined(i586) | defined(__i586) || defined(__i586__) || defined(i686) || \ + defined(__i686) || defined(__i686__) || defined(_M_IX86) || \ + defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || \ + defined(__INTEL__) || defined(__x86_64) || defined(__x86_64__) || \ + defined(__amd64__) || defined(__amd64) || defined(_M_X64) || \ + defined(_M_AMD64) || defined(__IA32__) || defined(__INTEL__) +#ifndef __ia32__ +/* LY: define neutral __ia32__ for x86 and x86-64 archs */ +#define __ia32__ 1 +#endif /* __ia32__ */ +#if !defined(__amd64__) && (defined(__x86_64) || defined(__x86_64__) || \ + defined(__amd64) || defined(_M_X64)) +/* LY: define trusty __amd64__ for all AMD64/x86-64 arch */ +#define __amd64__ 1 +#endif /* __amd64__ */ +#endif /* all x86 */ + +#if !defined(MDBX_UNALIGNED_OK) +#if defined(_MSC_VER) +#define MDBX_UNALIGNED_OK 1 /* avoid MSVC misoptimization */ +#elif __CLANG_PREREQ(5, 0) || __GNUC_PREREQ(5, 0) +#define MDBX_UNALIGNED_OK 0 /* expecting optimization is well done */ +#elif (defined(__ia32__) || defined(__ARM_FEATURE_UNALIGNED)) && \ + !defined(__ALIGNED__) +#define MDBX_UNALIGNED_OK 1 +#else +#define MDBX_UNALIGNED_OK 0 +#endif +#endif /* MDBX_UNALIGNED_OK */ + +#if (-6 & 5) || CHAR_BIT != 8 || UINT_MAX < 0xffffffff || ULONG_MAX % 0xFFFF +#error \ + "Sanity checking failed: Two's complement, reasonably sized integer types" +#endif + +/*----------------------------------------------------------------------------*/ +/* Compiler's includes for builtins/intrinsics */ + +#if defined(_MSC_VER) || defined(__INTEL_COMPILER) +#include +#elif __GNUC_PREREQ(4, 4) || defined(__clang__) +#if defined(__ia32__) || defined(__e2k__) +#include +#endif /* __ia32__ */ +#if defined(__ia32__) +#include +#endif /* __ia32__ */ +#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun) +#include +#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) && \ + (defined(HP_IA64) || defined(__ia64)) +#include +#elif defined(__IBMC__) && defined(__powerpc) +#include +#elif defined(_AIX) +#include +#include +#elif (defined(__osf__) && defined(__DECC)) || defined(__alpha) +#include +#include +#elif defined(__MWERKS__) +/* CodeWarrior - troubles ? */ +#pragma gcc_extensions +#elif defined(__SNC__) +/* Sony PS3 - troubles ? */ +#elif defined(__hppa__) || defined(__hppa) +#include +#else +#error Unsupported C compiler, please use GNU C 4.4 or newer +#endif /* Compiler */ + +/*----------------------------------------------------------------------------*/ +/* Byteorder */ + +#if !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) || \ + !defined(__ORDER_BIG_ENDIAN__) + +/* *INDENT-OFF* */ +/* clang-format off */ +#if defined(__GLIBC__) || defined(__GNU_LIBRARY__) || defined(__ANDROID__) || \ + defined(HAVE_ENDIAN_H) || __has_include() +#include +#elif defined(__APPLE__) || defined(__MACH__) || defined(__OpenBSD__) || \ + defined(HAVE_MACHINE_ENDIAN_H) || __has_include() +#include +#elif defined(HAVE_SYS_ISA_DEFS_H) || __has_include() +#include +#elif (defined(HAVE_SYS_TYPES_H) && defined(HAVE_SYS_ENDIAN_H)) || \ + (__has_include() && __has_include()) +#include +#include +#elif defined(__bsdi__) || defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__NETBSD__) || defined(__NetBSD__) || \ + defined(HAVE_SYS_PARAM_H) || __has_include() +#include +#endif /* OS */ +/* *INDENT-ON* */ +/* clang-format on */ + +#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && defined(__BIG_ENDIAN) +#define __ORDER_LITTLE_ENDIAN__ __LITTLE_ENDIAN +#define __ORDER_BIG_ENDIAN__ __BIG_ENDIAN +#define __BYTE_ORDER__ __BYTE_ORDER +#elif defined(_BYTE_ORDER) && defined(_LITTLE_ENDIAN) && defined(_BIG_ENDIAN) +#define __ORDER_LITTLE_ENDIAN__ _LITTLE_ENDIAN +#define __ORDER_BIG_ENDIAN__ _BIG_ENDIAN +#define __BYTE_ORDER__ _BYTE_ORDER +#else +#define __ORDER_LITTLE_ENDIAN__ 1234 +#define __ORDER_BIG_ENDIAN__ 4321 + +#if defined(__LITTLE_ENDIAN__) || \ + (defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN)) || \ + defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || \ + defined(__MIPSEL__) || defined(_MIPSEL) || defined(__MIPSEL) || \ + defined(_M_ARM) || defined(_M_ARM64) || defined(__e2k__) || \ + defined(__elbrus_4c__) || defined(__elbrus_8c__) || defined(__bfin__) || \ + defined(__BFIN__) || defined(__ia64__) || defined(_IA64) || \ + defined(__IA64__) || defined(__ia64) || defined(_M_IA64) || \ + defined(__itanium__) || defined(__ia32__) || defined(__CYGWIN__) || \ + defined(_WIN64) || defined(_WIN32) || defined(__TOS_WIN__) || \ + defined(__WINDOWS__) +#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__ + +#elif defined(__BIG_ENDIAN__) || \ + (defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN)) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(__MIPSEB__) || defined(_MIPSEB) || defined(__MIPSEB) || \ + defined(__m68k__) || defined(M68000) || defined(__hppa__) || \ + defined(__hppa) || defined(__HPPA__) || defined(__sparc__) || \ + defined(__sparc) || defined(__370__) || defined(__THW_370__) || \ + defined(__s390__) || defined(__s390x__) || defined(__SYSC_ZARCH__) +#define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__ + +#else +#error __BYTE_ORDER__ should be defined. +#endif /* Arch */ + +#endif +#endif /* __BYTE_ORDER__ || __ORDER_LITTLE_ENDIAN__ || __ORDER_BIG_ENDIAN__ */ + +/*----------------------------------------------------------------------------*/ +/* Memory/Compiler barriers, cache coherence */ + +static __maybe_unused __inline void mdbx_compiler_barrier(void) { +#if defined(__clang__) || defined(__GNUC__) + __asm__ __volatile__("" ::: "memory"); +#elif defined(_MSC_VER) + _ReadWriteBarrier(); +#elif defined(__INTEL_COMPILER) /* LY: Intel Compiler may mimic GCC and MSC */ + __memory_barrier(); + if (type > MDBX_BARRIER_COMPILER) +#if defined(__ia64__) || defined(__ia64) || defined(_M_IA64) + __mf(); +#elif defined(__i386__) || defined(__x86_64__) + _mm_mfence(); +#else +#error "Unknown target for Intel Compiler, please report to us." +#endif +#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun) + __compiler_barrier(); +#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) && \ + (defined(HP_IA64) || defined(__ia64)) + _Asm_sched_fence(/* LY: no-arg meaning 'all expect ALU', e.g. 0x3D3D */); +#elif defined(_AIX) || defined(__ppc__) || defined(__powerpc__) || \ + defined(__ppc64__) || defined(__powerpc64__) + __fence(); +#else +#error "Could not guess the kind of compiler, please report to us." +#endif +} + +static __maybe_unused __inline void mdbx_memory_barrier(void) { +#if __has_extension(c_atomic) || __has_extension(cxx_atomic) + __c11_atomic_thread_fence(__ATOMIC_SEQ_CST); +#elif defined(__ATOMIC_SEQ_CST) + __atomic_thread_fence(__ATOMIC_SEQ_CST); +#elif defined(__clang__) || defined(__GNUC__) + __sync_synchronize(); +#elif defined(_MSC_VER) + MemoryBarrier(); +#elif defined(__INTEL_COMPILER) /* LY: Intel Compiler may mimic GCC and MSC */ +#if defined(__ia64__) || defined(__ia64) || defined(_M_IA64) + __mf(); +#elif defined(__i386__) || defined(__x86_64__) + _mm_mfence(); +#else +#error "Unknown target for Intel Compiler, please report to us." +#endif +#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun) + __machine_rw_barrier(); +#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) && \ + (defined(HP_IA64) || defined(__ia64)) + _Asm_mf(); +#elif defined(_AIX) || defined(__ppc__) || defined(__powerpc__) || \ + defined(__ppc64__) || defined(__powerpc64__) + __lwsync(); +#else +#error "Could not guess the kind of compiler, please report to us." +#endif +} + +/*----------------------------------------------------------------------------*/ +/* Cache coherence and invalidation */ + +#ifndef MDBX_CPU_WRITEBACK_IS_COHERENT +#if defined(__ia32__) || defined(__e2k__) || defined(__hppa) || \ + defined(__hppa__) +#define MDBX_CPU_WRITEBACK_IS_COHERENT 1 +#else +#define MDBX_CPU_WRITEBACK_IS_COHERENT 0 +#endif +#endif /* MDBX_CPU_WRITEBACK_IS_COHERENT */ + +#ifndef MDBX_CACHELINE_SIZE +#if defined(SYSTEM_CACHE_ALIGNMENT_SIZE) +#define MDBX_CACHELINE_SIZE SYSTEM_CACHE_ALIGNMENT_SIZE +#elif defined(__ia64__) || defined(__ia64) || defined(_M_IA64) +#define MDBX_CACHELINE_SIZE 128 +#else +#define MDBX_CACHELINE_SIZE 64 +#endif +#endif /* MDBX_CACHELINE_SIZE */ + +#if MDBX_CPU_WRITEBACK_IS_COHERENT +#define mdbx_flush_noncoherent_cpu_writeback() mdbx_compiler_barrier() +#else +#define mdbx_flush_noncoherent_cpu_writeback() mdbx_memory_barrier() +#endif + +#if __has_include() +#include +#elif defined(__mips) || defined(__mips__) || defined(__mips64) || \ + defined(__mips64__) || defined(_M_MRX000) || defined(_MIPS_) || \ + defined(__MWERKS__) || defined(__sgi) +/* MIPS should have explicit cache control */ +#include +#endif + +#ifndef MDBX_CPU_CACHE_MMAP_NONCOHERENT +#if defined(__mips) || defined(__mips__) || defined(__mips64) || \ + defined(__mips64__) || defined(_M_MRX000) || defined(_MIPS_) || \ + defined(__MWERKS__) || defined(__sgi) +/* MIPS has cache coherency issues. */ +#define MDBX_CPU_CACHE_MMAP_NONCOHERENT 1 +#else +/* LY: assume no relevant mmap/dcache issues. */ +#define MDBX_CPU_CACHE_MMAP_NONCOHERENT 0 +#endif +#endif /* ndef MDBX_CPU_CACHE_MMAP_NONCOHERENT */ + +static __maybe_unused __inline void +mdbx_invalidate_mmap_noncoherent_cache(void *addr, size_t nbytes) { +#if MDBX_CPU_CACHE_MMAP_NONCOHERENT +#ifdef DCACHE + /* MIPS has cache coherency issues. + * Note: for any nbytes >= on-chip cache size, entire is flushed. */ + cacheflush(addr, nbytes, DCACHE); +#else +#error "Oops, cacheflush() not available" +#endif /* DCACHE */ +#else /* MDBX_CPU_CACHE_MMAP_NONCOHERENT */ + (void)addr; + (void)nbytes; +#endif /* MDBX_CPU_CACHE_MMAP_NONCOHERENT */ +} + +/*----------------------------------------------------------------------------*/ +/* libc compatibility stuff */ + +#if (!defined(__GLIBC__) && __GLIBC_PREREQ(2, 1)) && \ + (defined(_GNU_SOURCE) || defined(_BSD_SOURCE)) +#define mdbx_asprintf asprintf +#define mdbx_vasprintf vasprintf +#else +MDBX_INTERNAL_FUNC __printf_args(2, 3) int __maybe_unused + mdbx_asprintf(char **strp, const char *fmt, ...); +MDBX_INTERNAL_FUNC int mdbx_vasprintf(char **strp, const char *fmt, va_list ap); +#endif + +/*----------------------------------------------------------------------------*/ +/* OS abstraction layer stuff */ + +/* max bytes to write in one call */ +#if defined(_WIN32) || defined(_WIN64) +#define MAX_WRITE UINT32_C(0x01000000) +#else +#define MAX_WRITE UINT32_C(0x3fff0000) +#endif + +#if defined(__linux__) || defined(__gnu_linux__) +MDBX_INTERNAL_VAR uint32_t mdbx_linux_kernel_version; +#endif /* Linux */ + +/* Get the size of a memory page for the system. + * This is the basic size that the platform's memory manager uses, and is + * fundamental to the use of memory-mapped files. */ +static __maybe_unused __inline size_t mdbx_syspagesize(void) { +#if defined(_WIN32) || defined(_WIN64) + SYSTEM_INFO si; + GetSystemInfo(&si); + return si.dwPageSize; +#else + return sysconf(_SC_PAGE_SIZE); +#endif +} + +#ifndef mdbx_strdup +LIBMDBX_API char *mdbx_strdup(const char *str); +#endif + +static __maybe_unused __inline int mdbx_get_errno(void) { +#if defined(_WIN32) || defined(_WIN64) + DWORD rc = GetLastError(); +#else + int rc = errno; +#endif + return rc; +} + +#ifndef mdbx_memalign_alloc +MDBX_INTERNAL_FUNC int mdbx_memalign_alloc(size_t alignment, size_t bytes, + void **result); +#endif +#ifndef mdbx_memalign_free +MDBX_INTERNAL_FUNC void mdbx_memalign_free(void *ptr); +#endif + +MDBX_INTERNAL_FUNC int mdbx_condmutex_init(mdbx_condmutex_t *condmutex); +MDBX_INTERNAL_FUNC int mdbx_condmutex_lock(mdbx_condmutex_t *condmutex); +MDBX_INTERNAL_FUNC int mdbx_condmutex_unlock(mdbx_condmutex_t *condmutex); +MDBX_INTERNAL_FUNC int mdbx_condmutex_signal(mdbx_condmutex_t *condmutex); +MDBX_INTERNAL_FUNC int mdbx_condmutex_wait(mdbx_condmutex_t *condmutex); +MDBX_INTERNAL_FUNC int mdbx_condmutex_destroy(mdbx_condmutex_t *condmutex); + +MDBX_INTERNAL_FUNC int mdbx_fastmutex_init(mdbx_fastmutex_t *fastmutex); +MDBX_INTERNAL_FUNC int mdbx_fastmutex_acquire(mdbx_fastmutex_t *fastmutex); +MDBX_INTERNAL_FUNC int mdbx_fastmutex_release(mdbx_fastmutex_t *fastmutex); +MDBX_INTERNAL_FUNC int mdbx_fastmutex_destroy(mdbx_fastmutex_t *fastmutex); + +MDBX_INTERNAL_FUNC int mdbx_pwritev(mdbx_filehandle_t fd, struct iovec *iov, + int iovcnt, uint64_t offset, + size_t expected_written); +MDBX_INTERNAL_FUNC int mdbx_pread(mdbx_filehandle_t fd, void *buf, size_t count, + uint64_t offset); +MDBX_INTERNAL_FUNC int mdbx_pwrite(mdbx_filehandle_t fd, const void *buf, + size_t count, uint64_t offset); +MDBX_INTERNAL_FUNC int mdbx_write(mdbx_filehandle_t fd, const void *buf, + size_t count); + +MDBX_INTERNAL_FUNC int +mdbx_thread_create(mdbx_thread_t *thread, + THREAD_RESULT(THREAD_CALL *start_routine)(void *), + void *arg); +MDBX_INTERNAL_FUNC int mdbx_thread_join(mdbx_thread_t thread); + +enum mdbx_syncmode_bits { + MDBX_SYNC_DATA = 1, + MDBX_SYNC_SIZE = 2, + MDBX_SYNC_IODQ = 4 +}; + +MDBX_INTERNAL_FUNC int mdbx_filesync(mdbx_filehandle_t fd, + enum mdbx_syncmode_bits mode_bits); +MDBX_INTERNAL_FUNC int mdbx_ftruncate(mdbx_filehandle_t fd, uint64_t length); +MDBX_INTERNAL_FUNC int mdbx_fseek(mdbx_filehandle_t fd, uint64_t pos); +MDBX_INTERNAL_FUNC int mdbx_filesize(mdbx_filehandle_t fd, uint64_t *length); +MDBX_INTERNAL_FUNC int mdbx_openfile(const char *pathname, int flags, + mode_t mode, mdbx_filehandle_t *fd, + bool exclusive); +MDBX_INTERNAL_FUNC int mdbx_closefile(mdbx_filehandle_t fd); +MDBX_INTERNAL_FUNC int mdbx_removefile(const char *pathname); +MDBX_INTERNAL_FUNC int mdbx_is_pipe(mdbx_filehandle_t fd); + +typedef struct mdbx_mmap_param { + union { + void *address; + uint8_t *dxb; + struct MDBX_lockinfo *lck; + }; + mdbx_filehandle_t fd; + size_t limit; /* mapping length, but NOT a size of file nor DB */ + size_t current; /* mapped region size, i.e. the size of file and DB */ +#if defined(_WIN32) || defined(_WIN64) + uint64_t filesize /* in-process cache of a file size. */; +#endif +#ifdef MDBX_OSAL_SECTION + MDBX_OSAL_SECTION section; +#endif +} mdbx_mmap_t; + +MDBX_INTERNAL_FUNC int mdbx_mmap(const int flags, mdbx_mmap_t *map, + const size_t must, const size_t limit, + const bool truncate); +MDBX_INTERNAL_FUNC int mdbx_munmap(mdbx_mmap_t *map); +MDBX_INTERNAL_FUNC int mdbx_mresize(int flags, mdbx_mmap_t *map, size_t current, + size_t wanna); +#if defined(_WIN32) || defined(_WIN64) +typedef struct { + unsigned limit, count; + HANDLE handles[31]; +} mdbx_handle_array_t; +MDBX_INTERNAL_FUNC int +mdbx_suspend_threads_before_remap(MDBX_env *env, mdbx_handle_array_t **array); +MDBX_INTERNAL_FUNC int +mdbx_resume_threads_after_remap(mdbx_handle_array_t *array); +#endif /* Windows */ +MDBX_INTERNAL_FUNC int mdbx_msync(mdbx_mmap_t *map, size_t offset, + size_t length, int async); +MDBX_INTERNAL_FUNC int mdbx_check4nonlocal(mdbx_filehandle_t handle, int flags); + +static __maybe_unused __inline uint32_t mdbx_getpid(void) { + STATIC_ASSERT(sizeof(mdbx_pid_t) <= sizeof(uint32_t)); +#if defined(_WIN32) || defined(_WIN64) + return GetCurrentProcessId(); +#else + return getpid(); +#endif +} + +static __maybe_unused __inline size_t mdbx_thread_self(void) { + STATIC_ASSERT(sizeof(mdbx_tid_t) <= sizeof(size_t)); +#if defined(_WIN32) || defined(_WIN64) + return GetCurrentThreadId(); +#else + return (size_t)pthread_self(); +#endif +} + +MDBX_INTERNAL_FUNC void __maybe_unused mdbx_osal_jitter(bool tiny); +MDBX_INTERNAL_FUNC uint64_t mdbx_osal_monotime(void); +MDBX_INTERNAL_FUNC uint64_t +mdbx_osal_16dot16_to_monotime(uint32_t seconds_16dot16); +MDBX_INTERNAL_FUNC uint32_t mdbx_osal_monotime_to_16dot16(uint64_t monotime); + +typedef union bin128 { + __anonymous_struct_extension__ struct { uint64_t x, y; }; + __anonymous_struct_extension__ struct { uint32_t a, b, c, d; }; +} bin128_t; + +MDBX_INTERNAL_FUNC bin128_t mdbx_osal_bootid(void); +/*----------------------------------------------------------------------------*/ +/* lck stuff */ + +#if defined(_WIN32) || defined(_WIN64) +#undef MDBX_OSAL_LOCK +#define MDBX_OSAL_LOCK_SIGN UINT32_C(0xF10C) +#else +#define MDBX_OSAL_LOCK pthread_mutex_t +#define MDBX_OSAL_LOCK_SIGN UINT32_C(0x8017) +#endif /* MDBX_OSAL_LOCK */ + +/// \brief Initialization of synchronization primitives linked with MDBX_env +/// instance both in LCK-file and within the current process. +/// \param +/// global_uniqueness_flag = true - denotes that there are no other processes +/// working with DB and LCK-file. Thus the function MUST initialize +/// shared synchronization objects in memory-mapped LCK-file. +/// global_uniqueness_flag = false - denotes that at least one process is +/// already working with DB and LCK-file, including the case when DB +/// has already been opened in the current process. Thus the function +/// MUST NOT initialize shared synchronization objects in memory-mapped +/// LCK-file that are already in use. +/// \return Error code or zero on success. +MDBX_INTERNAL_FUNC int mdbx_lck_init(MDBX_env *env, + MDBX_env *inprocess_neighbor, + int global_uniqueness_flag); + +/// \brief Disconnects from shared interprocess objects and destructs +/// synchronization objects linked with MDBX_env instance +/// within the current process. +/// \param +/// inprocess_neighbor = NULL - if the current process does not have other +/// instances of MDBX_env linked with the DB being closed. +/// Thus the function MUST check for other processes working with DB or +/// LCK-file, and keep or destroy shared synchronization objects in +/// memory-mapped LCK-file depending on the result. +/// inprocess_neighbor = not-NULL - pointer to another instance of MDBX_env +/// (anyone of there is several) working with DB or LCK-file within the +/// current process. Thus the function MUST NOT try to acquire exclusive +/// lock and/or try to destruct shared synchronization objects linked with +/// DB or LCK-file. Moreover, the implementation MUST ensure correct work +/// of other instances of MDBX_env within the current process, e.g. +/// restore POSIX-fcntl locks after the closing of file descriptors. +/// \return Error code (MDBX_PANIC) or zero on success. +MDBX_INTERNAL_FUNC int mdbx_lck_destroy(MDBX_env *env, + MDBX_env *inprocess_neighbor); + +/// \brief Connects to shared interprocess locking objects and tries to acquire +/// the maximum lock level (shared if exclusive is not available) +/// Depending on implementation or/and platform (Windows) this function may +/// acquire the non-OS super-level lock (e.g. for shared synchronization +/// objects initialization), which will be downgraded to OS-exclusive or +/// shared via explicit calling of mdbx_lck_downgrade(). +/// \return +/// MDBX_RESULT_TRUE (-1) - if an exclusive lock was acquired and thus +/// the current process is the first and only after the last use of DB. +/// MDBX_RESULT_FALSE (0) - if a shared lock was acquired and thus +/// DB has already been opened and now is used by other processes. +/// Otherwise (not 0 and not -1) - error code. +MDBX_INTERNAL_FUNC int mdbx_lck_seize(MDBX_env *env); + +/// \brief Downgrades the level of initially acquired lock to +/// operational level specified by agrument. The reson for such downgrade: +/// - unblocking of other processes that are waiting for access, i.e. +/// if (env->me_flags & MDBX_EXCLUSIVE) != 0, then other processes +/// should be made aware that access is unavailable rather than +/// wait for it. +/// - freeing locks that interfere file operation (expecially for Windows) +/// (env->me_flags & MDBX_EXCLUSIVE) == 0 - downgrade to shared lock. +/// (env->me_flags & MDBX_EXCLUSIVE) != 0 - downgrade to exclusive +/// operational lock. +/// \return Error code or zero on success +MDBX_INTERNAL_FUNC int mdbx_lck_downgrade(MDBX_env *env); + +/// \brief Locks LCK-file or/and table of readers for (de)registering. +/// \return Error code or zero on success +MDBX_INTERNAL_FUNC int mdbx_rdt_lock(MDBX_env *env); + +/// \brief Unlocks LCK-file or/and table of readers after (de)registering. +MDBX_INTERNAL_FUNC void mdbx_rdt_unlock(MDBX_env *env); + +/// \brief Acquires lock for DB change (on writing transaction start) +/// Reading transactions will not be blocked. +/// Declared as LIBMDBX_API because it is used in mdbx_chk. +/// \return Error code or zero on success +LIBMDBX_API int mdbx_txn_lock(MDBX_env *env, bool dontwait); + +/// \brief Releases lock once DB changes is made (after writing transaction +/// has finished). +/// Declared as LIBMDBX_API because it is used in mdbx_chk. +LIBMDBX_API void mdbx_txn_unlock(MDBX_env *env); + +/// \brief Sets alive-flag of reader presence (indicative lock) for PID of +/// the current process. The function does no more than needed for +/// the correct working of mdbx_rpid_check() in other processes. +/// \return Error code or zero on success +MDBX_INTERNAL_FUNC int mdbx_rpid_set(MDBX_env *env); + +/// \brief Resets alive-flag of reader presence (indicative lock) +/// for PID of the current process. The function does no more than needed +/// for the correct working of mdbx_rpid_check() in other processes. +/// \return Error code or zero on success +MDBX_INTERNAL_FUNC int mdbx_rpid_clear(MDBX_env *env); + +/// \brief Checks for reading process status with the given pid with help of +/// alive-flag of presence (indicative lock) or using another way. +/// \return +/// MDBX_RESULT_TRUE (-1) - if the reader process with the given PID is alive +/// and working with DB (indicative lock is present). +/// MDBX_RESULT_FALSE (0) - if the reader process with the given PID is absent +/// or not working with DB (indicative lock is not present). +/// Otherwise (not 0 and not -1) - error code. +MDBX_INTERNAL_FUNC int mdbx_rpid_check(MDBX_env *env, uint32_t pid); + +#if defined(_WIN32) || defined(_WIN64) +typedef union MDBX_srwlock { + struct { + long volatile readerCount; + long volatile writerCount; + }; + RTL_SRWLOCK native; +} MDBX_srwlock; + +typedef void(WINAPI *MDBX_srwlock_function)(MDBX_srwlock *); +MDBX_INTERNAL_VAR MDBX_srwlock_function mdbx_srwlock_Init, + mdbx_srwlock_AcquireShared, mdbx_srwlock_ReleaseShared, + mdbx_srwlock_AcquireExclusive, mdbx_srwlock_ReleaseExclusive; + +typedef BOOL(WINAPI *MDBX_GetFileInformationByHandleEx)( + _In_ HANDLE hFile, _In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + _Out_ LPVOID lpFileInformation, _In_ DWORD dwBufferSize); +MDBX_INTERNAL_VAR MDBX_GetFileInformationByHandleEx + mdbx_GetFileInformationByHandleEx; + +typedef BOOL(WINAPI *MDBX_GetVolumeInformationByHandleW)( + _In_ HANDLE hFile, _Out_opt_ LPWSTR lpVolumeNameBuffer, + _In_ DWORD nVolumeNameSize, _Out_opt_ LPDWORD lpVolumeSerialNumber, + _Out_opt_ LPDWORD lpMaximumComponentLength, + _Out_opt_ LPDWORD lpFileSystemFlags, + _Out_opt_ LPWSTR lpFileSystemNameBuffer, _In_ DWORD nFileSystemNameSize); +MDBX_INTERNAL_VAR MDBX_GetVolumeInformationByHandleW + mdbx_GetVolumeInformationByHandleW; + +typedef DWORD(WINAPI *MDBX_GetFinalPathNameByHandleW)(_In_ HANDLE hFile, + _Out_ LPWSTR lpszFilePath, + _In_ DWORD cchFilePath, + _In_ DWORD dwFlags); +MDBX_INTERNAL_VAR MDBX_GetFinalPathNameByHandleW mdbx_GetFinalPathNameByHandleW; + +typedef BOOL(WINAPI *MDBX_SetFileInformationByHandle)( + _In_ HANDLE hFile, _In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + _Out_ LPVOID lpFileInformation, _In_ DWORD dwBufferSize); +MDBX_INTERNAL_VAR MDBX_SetFileInformationByHandle + mdbx_SetFileInformationByHandle; + +typedef NTSTATUS(NTAPI *MDBX_NtFsControlFile)( + IN HANDLE FileHandle, IN OUT HANDLE Event, + IN OUT PVOID /* PIO_APC_ROUTINE */ ApcRoutine, IN OUT PVOID ApcContext, + OUT PIO_STATUS_BLOCK IoStatusBlock, IN ULONG FsControlCode, + IN OUT PVOID InputBuffer, IN ULONG InputBufferLength, + OUT OPTIONAL PVOID OutputBuffer, IN ULONG OutputBufferLength); +MDBX_INTERNAL_VAR MDBX_NtFsControlFile mdbx_NtFsControlFile; + +typedef uint64_t(WINAPI *MDBX_GetTickCount64)(void); +MDBX_INTERNAL_VAR MDBX_GetTickCount64 mdbx_GetTickCount64; + +#if !defined(_WIN32_WINNT_WIN8) || _WIN32_WINNT < _WIN32_WINNT_WIN8 +typedef struct _WIN32_MEMORY_RANGE_ENTRY { + PVOID VirtualAddress; + SIZE_T NumberOfBytes; +} WIN32_MEMORY_RANGE_ENTRY, *PWIN32_MEMORY_RANGE_ENTRY; +#endif /* Windows 8.x */ + +typedef BOOL(WINAPI *MDBX_PrefetchVirtualMemory)( + HANDLE hProcess, ULONG_PTR NumberOfEntries, + PWIN32_MEMORY_RANGE_ENTRY VirtualAddresses, ULONG Flags); +MDBX_INTERNAL_VAR MDBX_PrefetchVirtualMemory mdbx_PrefetchVirtualMemory; + +#if 0 /* LY: unused for now */ +#if !defined(_WIN32_WINNT_WIN81) || _WIN32_WINNT < _WIN32_WINNT_WIN81 +typedef enum OFFER_PRIORITY { + VmOfferPriorityVeryLow = 1, + VmOfferPriorityLow, + VmOfferPriorityBelowNormal, + VmOfferPriorityNormal +} OFFER_PRIORITY; +#endif /* Windows 8.1 */ + +typedef DWORD(WINAPI *MDBX_DiscardVirtualMemory)(PVOID VirtualAddress, + SIZE_T Size); +MDBX_INTERNAL_VAR MDBX_DiscardVirtualMemory mdbx_DiscardVirtualMemory; + +typedef DWORD(WINAPI *MDBX_ReclaimVirtualMemory)(PVOID VirtualAddress, + SIZE_T Size); +MDBX_INTERNAL_VAR MDBX_ReclaimVirtualMemory mdbx_ReclaimVirtualMemory; + +typedef DWORD(WINAPI *MDBX_OfferVirtualMemory( + PVOID VirtualAddress, + SIZE_T Size, + OFFER_PRIORITY Priority +); +MDBX_INTERNAL_VAR MDBX_OfferVirtualMemory mdbx_OfferVirtualMemory; +#endif /* unused for now */ + +#endif /* Windows */ + +/*----------------------------------------------------------------------------*/ +/* Atomics */ + +#if !defined(__cplusplus) && (__STDC_VERSION__ >= 201112L) && \ + !defined(__STDC_NO_ATOMICS__) && \ + (__GNUC_PREREQ(4, 9) || __CLANG_PREREQ(3, 8) || \ + !(defined(__GNUC__) || defined(__clang__))) +#include +#elif defined(__GNUC__) || defined(__clang__) +/* LY: nothing required */ +#elif defined(_MSC_VER) +#pragma warning(disable : 4163) /* 'xyz': not available as an intrinsic */ +#pragma warning(disable : 4133) /* 'function': incompatible types - from \ + 'size_t' to 'LONGLONG' */ +#pragma warning(disable : 4244) /* 'return': conversion from 'LONGLONG' to \ + 'std::size_t', possible loss of data */ +#pragma warning(disable : 4267) /* 'function': conversion from 'size_t' to \ + 'long', possible loss of data */ +#pragma intrinsic(_InterlockedExchangeAdd, _InterlockedCompareExchange) +#pragma intrinsic(_InterlockedExchangeAdd64, _InterlockedCompareExchange64) +#elif defined(__APPLE__) +#include +#else +#error FIXME atomic-ops +#endif + +/*----------------------------------------------------------------------------*/ + +#if defined(_MSC_VER) && _MSC_VER >= 1900 +/* LY: MSVC 2015/2017/2019 has buggy/inconsistent PRIuPTR/PRIxPTR macros + * for internal format-args checker. */ +#undef PRIuPTR +#undef PRIiPTR +#undef PRIdPTR +#undef PRIxPTR +#define PRIuPTR "Iu" +#define PRIiPTR "Ii" +#define PRIdPTR "Id" +#define PRIxPTR "Ix" +#define PRIuSIZE "zu" +#define PRIiSIZE "zi" +#define PRIdSIZE "zd" +#define PRIxSIZE "zx" +#endif /* fix PRI*PTR for _MSC_VER */ + +#ifndef PRIuSIZE +#define PRIuSIZE PRIuPTR +#define PRIiSIZE PRIiPTR +#define PRIdSIZE PRIdPTR +#define PRIxSIZE PRIxPTR +#endif /* PRI*SIZE macros for MSVC */ + +#ifdef _MSC_VER +#pragma warning(pop) +#endif diff --git a/contrib/db/libmdbx/src/elements/version.c.in b/contrib/db/libmdbx/src/elements/version.c.in new file mode 100644 index 00000000..2854bd5d --- /dev/null +++ b/contrib/db/libmdbx/src/elements/version.c.in @@ -0,0 +1,46 @@ +/* This is CMake-template for libmdbx's version.c + ******************************************************************************/ + +#include "internals.h" + +#if MDBX_VERSION_MAJOR != ${MDBX_VERSION_MAJOR} || \ + MDBX_VERSION_MINOR != ${MDBX_VERSION_MINOR} +#error "API version mismatch! Had `git fetch --tags` done?" +#endif + +static const char sourcery[] = STRINGIFY(MDBX_BUILD_SOURCERY); + +__dll_export +#ifdef __attribute_used__ + __attribute_used__ +#elif defined(__GNUC__) || __has_attribute(__used__) + __attribute__((__used__)) +#endif +#ifdef __attribute_externally_visible__ + __attribute_externally_visible__ +#elif (defined(__GNUC__) && !defined(__clang__)) || \ + __has_attribute(__externally_visible__) + __attribute__((__externally_visible__)) +#endif + const mdbx_version_info mdbx_version = { + ${MDBX_VERSION_MAJOR}, + ${MDBX_VERSION_MINOR}, + ${MDBX_VERSION_RELEASE}, + ${MDBX_VERSION_REVISION}, + {"@MDBX_GIT_TIMESTAMP@", "@MDBX_GIT_TREE@", "@MDBX_GIT_COMMIT@", + "@MDBX_GIT_DESCRIBE@"}, + sourcery}; + +__dll_export +#ifdef __attribute_used__ + __attribute_used__ +#elif defined(__GNUC__) || __has_attribute(__used__) + __attribute__((__used__)) +#endif +#ifdef __attribute_externally_visible__ + __attribute_externally_visible__ +#elif (defined(__GNUC__) && !defined(__clang__)) || \ + __has_attribute(__externally_visible__) + __attribute__((__externally_visible__)) +#endif + const char *const mdbx_sourcery_anchor = sourcery; diff --git a/contrib/db/libmdbx/src/man1/mdbx_chk.1 b/contrib/db/libmdbx/src/man1/mdbx_chk.1 new file mode 100644 index 00000000..84b10a1f --- /dev/null +++ b/contrib/db/libmdbx/src/man1/mdbx_chk.1 @@ -0,0 +1,87 @@ +.\" Copyright 2015-2019 Leonid Yuriev . +.\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.TH MDBX_CHK 1 "2019-09-10" "MDBX 0.x" +.SH NAME +mdbx_chk \- MDBX checking tool +.SH SYNOPSIS +.B mdbx_chk +[\c +.BR \-V ] +[\c +.BR \-v [ v [ v ]]] +[\c +.BR \-n ] +[\c +.BR \-q ] +[\c +.BR \-w ] +[\c +.BR \-d ] +[\c +.BI \-s \ subdb\fR] +[\c +.BR \-c ] +[\c +.BR \-i ] +.BR \ envpath +.SH DESCRIPTION +The +.B mdbx_chk +utility intended to check an MDBX database file. +.SH OPTIONS +.TP +.BR \-V +Write the library version number to the standard output, and exit. +.TP +.BR \-v +Produce verbose output, including summarize space and page usage statistics. +If \fB\-vv\fP is given, be more verbose, show summarized B-tree info +and space allocation. +If \fB\-vvv\fP is given, be more verbose, include summarized statistics +of leaf B-tree pages. +If \fB\-vvvv\fP is given, be even more verbose, show info of each page +during B-tree traversal and basic info of each GC record. +If \fB\-vvvvv\fP is given, turn maximal verbosity, display the full list +of page IDs in the GC records and size of each key-value pair of database(s). +.TP +.BR \-n +Open MDBX environment(s) which do not use subdirectories. +.TP +.BR \-q +Be quiet; do not output anything even if an error was detected. +.TP +.BR \-w +Open environment in read-write mode and lock for writing while checking. +This could be impossible if environment already used by another process(s) +in an incompatible read-write mode. This allow rollback to last steady commit +(in case environment was not closed properly) and then check transaction IDs +of meta-pages. Otherwise, without \fB\-w\fP option environment will be +opened in read-only mode. +.TP +.BR \-d +Disable page-by-page traversal of B-tree. In this case, without B-tree +traversal, it is unable to check for lost-unused pages nor for double-used +pages. +.TP +.BR \-s \ subdb +Verify and show info only for a specific subdatabase. +.TP +.BR \-c +Force using cooperative mode while opening environment, i.e. don't try to open +in exclusive/monopolistic mode. Only exclusive/monopolistic mode allow complete +check, including full check of all meta-pages and actual size of database file. +.TP +.BR \-i +Ignore wrong order errors, which will likely false-positive if custom +comparator(s) was used. +.SH DIAGNOSTICS +Exit status is zero if no errors occur. Errors result in a non-zero exit status +and a diagnostic message being written to standard error +if no quiet mode was requested. +.SH "SEE ALSO" +.BR mdbx_stat (1), +.BR mdbx_copy (1), +.BR mdbx_dump (1), +.BR mdbx_load (1) +.SH AUTHOR +Leonid Yuriev diff --git a/contrib/db/libmdbx/src/man1/mdbx_copy.1 b/contrib/db/libmdbx/src/man1/mdbx_copy.1 new file mode 100644 index 00000000..08717bca --- /dev/null +++ b/contrib/db/libmdbx/src/man1/mdbx_copy.1 @@ -0,0 +1,60 @@ +.\" Copyright 2015-2019 Leonid Yuriev . +.\" Copyright 2012-2015 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . +.\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.TH MDBX_COPY 1 "2019-09-10" "MDBX 0.x" +.SH NAME +mdbx_copy \- MDBX environment copy tool +.SH SYNOPSIS +.B mdbx_copy +[\c +.BR \-V ] +[\c +.BR \-c ] +[\c +.BR \-n ] +.B srcpath +[\c +.BR dstpath ] +.SH DESCRIPTION +The +.B mdbx_copy +utility copies an MDBX environment. The environment can +be copied regardless of whether it is currently in use. +No lockfile is created, since it gets recreated at need. + +If +.I dstpath +is specified it must be the path of an empty directory +for storing the backup. Otherwise, the backup will be +written to stdout. + +.SH OPTIONS +.TP +.BR \-V +Write the library version number to the standard output, and exit. +.TP +.BR \-c +Compact while copying. Only current data pages will be copied; freed +or unused pages will be omitted from the copy. This option will +slow down the backup process as it is more CPU-intensive. +Currently it fails if the environment has suffered a page leak. +.TP +.BR \-n +Open MDBX environment(s) which do not use subdirectories. + +.SH DIAGNOSTICS +Exit status is zero if no errors occur. +Errors result in a non-zero exit status and +a diagnostic message being written to standard error. +.SH CAVEATS +This utility can trigger significant file size growth if run +in parallel with write transactions, because pages which they +free during copying cannot be reused until the copy is done. +.SH "SEE ALSO" +.BR mdbx_dump (1), +.BR mdbx_chk (1), +.BR mdbx_stat (1), +.BR mdbx_load (1) +.SH AUTHOR +Howard Chu of Symas Corporation diff --git a/contrib/db/libmdbx/src/man1/mdbx_dump.1 b/contrib/db/libmdbx/src/man1/mdbx_dump.1 new file mode 100644 index 00000000..0e5ecf66 --- /dev/null +++ b/contrib/db/libmdbx/src/man1/mdbx_dump.1 @@ -0,0 +1,80 @@ +.\" Copyright 2015-2019 Leonid Yuriev . +.\" Copyright 2014-2015 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . +.\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.TH MDBX_DUMP 1 "2019-09-10" "MDBX 0.x" +.SH NAME +mdbx_dump \- MDBX environment export tool +.SH SYNOPSIS +.B mdbx_dump +[\c +.BR \-V ] +[\c +.BI \-f \ file\fR] +[\c +.BR \-l ] +[\c +.BR \-n ] +[\c +.BR \-p ] +[\c +.BR \-a \ | +.BI \-s \ subdb\fR] +.BR \ envpath +.SH DESCRIPTION +The +.B mdbx_dump +utility reads a database and writes its contents to the +standard output using a portable flat-text format +understood by the +.BR mdbx_load (1) +utility. +.SH OPTIONS +.TP +.BR \-V +Write the library version number to the standard output, and exit. +.TP +.BR \-f \ file +Write to the specified file instead of to the standard output. +.TP +.BR \-l +List the databases stored in the environment. Just the +names will be listed, no data will be output. +.TP +.BR \-n +Dump an MDBX database which does not use subdirectories. +.TP +.BR \-p +If characters in either the key or data items are printing characters (as +defined by isprint(3)), output them directly. This option permits users to +use standard text editors and tools to modify the contents of databases. + +Note: different systems may have different notions about what characters +are considered printing characters, and databases dumped in this manner may +be less portable to external systems. +.TP +.BR \-a +Dump all of the subdatabases in the environment. +.TP +.BR \-s \ subdb +Dump a specific subdatabase. If no database is specified, only the main database is dumped. +.SH DIAGNOSTICS +Exit status is zero if no errors occur. +Errors result in a non-zero exit status and +a diagnostic message being written to standard error. + +Dumping and reloading databases that use user-defined comparison functions +will result in new databases that use the default comparison functions. +\fBIn this case it is quite likely that the reloaded database will be +damaged beyond repair permitting neither record storage nor retrieval.\fP + +The only available workaround is to modify the source for the +.BR mdbx_load (1) +utility to load the database using the correct comparison functions. +.SH "SEE ALSO" +.BR mdbx_load (1), +.BR mdbx_copy (1), +.BR mdbx_chk (1), +.BR mdbx_stat (1) +.SH AUTHOR +Howard Chu of Symas Corporation diff --git a/contrib/db/libmdbx/src/man1/mdbx_load.1 b/contrib/db/libmdbx/src/man1/mdbx_load.1 new file mode 100644 index 00000000..01b03eff --- /dev/null +++ b/contrib/db/libmdbx/src/man1/mdbx_load.1 @@ -0,0 +1,89 @@ +.\" Copyright 2015-2019 Leonid Yuriev . +.\" Copyright 2014-2015 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . +.\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.TH MDBX_LOAD 1 "2019-09-10" "MDBX 0.x" +.SH NAME +mdbx_load \- MDBX environment import tool +.SH SYNOPSIS +.B mdbx_load +[\c +.BR \-V ] +[\c +.BI \-f \ file\fR] +[\c +.BR \-n ] +[\c +.BI \-s \ subdb\fR] +[\c +.BR \-N ] +[\c +.BR \-T ] +.BR \ envpath +.SH DESCRIPTION +The +.B mdbx_load +utility reads from the standard input and loads it into the +MDBX environment +.BR envpath . + +The input to +.B mdbx_load +must be in the output format specified by the +.BR mdbx_dump (1) +utility or as specified by the +.B -T +option below. +.SH OPTIONS +.TP +.BR \-V +Write the library version number to the standard output, and exit. +.TP +.BR \-a +Append all records in the order they appear in the input. The input is assumed to already be +in correctly sorted order and no sorting or checking for redundant values will be performed. +This option must be used to reload data that was produced by running +.B mdbx_dump +on a database that uses custom compare functions. +.TP +.BR \-f \ file +Read from the specified file instead of from the standard input. +.TP +.BR \-n +Load an MDBX database which does not use subdirectories. +.TP +.BR \-s \ subdb +Load a specific subdatabase. If no database is specified, data is loaded into the main database. +.TP +.BR \-N +Don't overwrite existing records when loading into an already existing database; just skip them. +.TP +.BR \-T +Load data from simple text files. The input must be paired lines of text, where the first +line of the pair is the key item, and the second line of the pair is its corresponding +data item. + +A simple escape mechanism, where newline and backslash (\\) characters are special, is +applied to the text input. Newline characters are interpreted as record separators. +Backslash characters in the text will be interpreted in one of two ways: If the backslash +character precedes another backslash character, the pair will be interpreted as a literal +backslash. If the backslash character precedes any other character, the two characters +following the backslash will be interpreted as a hexadecimal specification of a single +character; for example, \\0a is a newline character in the ASCII character set. + +For this reason, any backslash or newline characters that naturally occur in the text +input must be escaped to avoid misinterpretation by +.BR mdbx_load . + +.SH DIAGNOSTICS +Exit status is zero if no errors occur. +Errors result in a non-zero exit status and +a diagnostic message being written to standard error. + +.SH "SEE ALSO" +.BR mdbx_dump (1), +.BR mdbx_chk (1), +.BR mdbx_stat (1), +.BR mdbx_copy (1) +.SH AUTHOR +Howard Chu of Symas Corporation diff --git a/contrib/db/libmdbx/src/man1/mdbx_stat.1 b/contrib/db/libmdbx/src/man1/mdbx_stat.1 new file mode 100644 index 00000000..424e76c0 --- /dev/null +++ b/contrib/db/libmdbx/src/man1/mdbx_stat.1 @@ -0,0 +1,69 @@ +.\" Copyright 2015-2019 Leonid Yuriev . +.\" Copyright 2012-2015 Howard Chu, Symas Corp. All Rights Reserved. +.\" Copyright 2015,2016 Peter-Service R&D LLC . +.\" Copying restrictions apply. See COPYRIGHT/LICENSE. +.TH MDBX_STAT 1 "2019-09-10" "MDBX 0.x" +.SH NAME +mdbx_stat \- MDBX environment status tool +.SH SYNOPSIS +.B mdbx_stat +[\c +.BR \-V ] +[\c +.BR \-e ] +[\c +.BR \-f [ f [ f ]]] +[\c +.BR \-n ] +[\c +.BR \-r [ r ]] +[\c +.BR \-a \ | +.BI \-s \ subdb\fR] +.BR \ envpath +.SH DESCRIPTION +The +.B mdbx_stat +utility displays the status of an MDBX environment. +.SH OPTIONS +.TP +.BR \-V +Write the library version number to the standard output, and exit. +.TP +.BR \-e +Display information about the database environment. +.TP +.BR \-f +Display information about the environment freelist. +If \fB\-ff\fP is given, summarize each freelist entry. +If \fB\-fff\fP is given, display the full list of page IDs in the freelist. +.TP +.BR \-n +Display the status of an MDBX database which does not use subdirectories. +.TP +.BR \-r +Display information about the environment reader table. +Shows the process ID, thread ID, and transaction ID for each active +reader slot. The process ID and transaction ID are in decimal, the +thread ID is in hexadecimal. The transaction ID is displayed as "-" +if the reader does not currently have a read transaction open. +If \fB\-rr\fP is given, check for stale entries in the reader +table and clear them. The reader table will be printed again +after the check is performed. +.TP +.BR \-a +Display the status of all of the subdatabases in the environment. +.TP +.BR \-s \ subdb +Display the status of a specific subdatabase. +.SH DIAGNOSTICS +Exit status is zero if no errors occur. +Errors result in a non-zero exit status and +a diagnostic message being written to standard error. +.SH "SEE ALSO" +.BR mdbx_chk (1), +.BR mdbx_copy (1), +.BR mdbx_dump (1), +.BR mdbx_load (1) +.SH AUTHOR +Howard Chu of Symas Corporation diff --git a/contrib/db/libmdbx/src/tools/CMakeLists.txt b/contrib/db/libmdbx/src/tools/CMakeLists.txt new file mode 100644 index 00000000..01ecd7dd --- /dev/null +++ b/contrib/db/libmdbx/src/tools/CMakeLists.txt @@ -0,0 +1,42 @@ +set(MDBX_TOOLS mdbx_chk mdbx_copy mdbx_dump mdbx_load mdbx_stat) + +# use, i.e. don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH already (but later on when installing) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +# add the automatically determined parts of the RPATH +# which point to directories outside the build tree to the install RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# the RPATH to be used when installing, but only if it's not a system directory +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) +if(isSystemDir EQUAL -1) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CMAKE_INSTALL_RPATH "@executable_path/../lib") + else() + set(CMAKE_INSTALL_RPATH "\$ORIGIN/../lib") + endif() +endif() + +foreach(TOOL ${MDBX_TOOLS}) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + add_executable(${TOOL} ${TOOL}.c wingetopt.c wingetopt.h) + else() + add_executable(${TOOL} ${TOOL}.c) + endif() + + target_link_libraries(${TOOL} mdbx ${CMAKE_THREAD_LIBS_INIT}) + set_target_properties(${TOOL} PROPERTIES + C_STANDARD ${MDBX_C_STANDARD} C_STANDARD_REQUIRED ON + INTERPROCEDURAL_OPTIMIZATION $) + + install(TARGETS ${TOOL} DESTINATION ${CMAKE_INSTALL_PREFIX}/bin COMPONENT mdbx) + install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../man1/${TOOL}.1 DESTINATION ${CMAKE_INSTALL_PREFIX}/man/man1 COMPONENT mdbx) +endforeach() + +if(LIB_MATH) + target_link_libraries(mdbx_chk ${LIB_MATH}) + target_link_libraries(mdbx_stat ${LIB_MATH}) +endif() diff --git a/contrib/db/libmdbx/src/tools/mdbx_chk.c b/contrib/db/libmdbx/src/tools/mdbx_chk.c new file mode 100644 index 00000000..cd61e44e --- /dev/null +++ b/contrib/db/libmdbx/src/tools/mdbx_chk.c @@ -0,0 +1,1430 @@ +/* mdbx_chk.c - memory-mapped database check tool */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +#ifdef _MSC_VER +#if _MSC_VER > 1800 +#pragma warning(disable : 4464) /* relative include path contains '..' */ +#endif +#pragma warning(disable : 4996) /* The POSIX name is deprecated... */ +#endif /* _MSC_VER (warnings) */ + +#define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#include "../elements/internals.h" + +typedef struct flagbit { + int bit; + char *name; +} flagbit; + +flagbit dbflags[] = {{MDBX_DUPSORT, "dupsort"}, + {MDBX_INTEGERKEY, "integerkey"}, + {MDBX_REVERSEKEY, "reversekey"}, + {MDBX_DUPFIXED, "dupfixed"}, + {MDBX_REVERSEDUP, "reversedup"}, + {MDBX_INTEGERDUP, "integerdup"}, + {0, NULL}}; + +#if defined(_WIN32) || defined(_WIN64) +#include "wingetopt.h" + +static volatile BOOL user_break; +static BOOL WINAPI ConsoleBreakHandlerRoutine(DWORD dwCtrlType) { + (void)dwCtrlType; + user_break = true; + return true; +} + +#else /* WINDOWS */ + +static volatile sig_atomic_t user_break; +static void signal_handler(int sig) { + (void)sig; + user_break = 1; +} + +#endif /* !WINDOWS */ + +#define EXIT_INTERRUPTED (EXIT_FAILURE + 4) +#define EXIT_FAILURE_SYS (EXIT_FAILURE + 3) +#define EXIT_FAILURE_MDB (EXIT_FAILURE + 2) +#define EXIT_FAILURE_CHECK_MAJOR (EXIT_FAILURE + 1) +#define EXIT_FAILURE_CHECK_MINOR EXIT_FAILURE + +typedef struct { + const char *name; + struct { + uint64_t branch, large_count, large_volume, leaf; + uint64_t subleaf_dupsort, leaf_dupfixed, subleaf_dupfixed; + uint64_t total, empty, other; + } pages; + uint64_t payload_bytes; + uint64_t lost_bytes; +} walk_dbi_t; + +struct { + short *pagemap; + uint64_t total_payload_bytes; + uint64_t pgcount; + walk_dbi_t dbi[MAX_DBI + CORE_DBS + /* account pseudo-entry for meta */ 1]; +} walk; + +#define dbi_free walk.dbi[FREE_DBI] +#define dbi_main walk.dbi[MAIN_DBI] +#define dbi_meta walk.dbi[CORE_DBS] + +uint64_t total_unused_bytes; +int envflags = MDBX_RDONLY | MDBX_EXCLUSIVE; + +MDBX_env *env; +MDBX_txn *txn; +MDBX_envinfo envinfo; +MDBX_stat envstat; +size_t maxkeysize, userdb_count, skipped_subdb; +uint64_t reclaimable_pages, gc_pages, alloc_pages, unused_pages, backed_pages; +unsigned verbose; +bool ignore_wrong_order, quiet; +const char *only_subdb; + +struct problem { + struct problem *pr_next; + size_t count; + const char *caption; +}; + +struct problem *problems_list; +uint64_t total_problems; + +static void __printf_args(1, 2) print(const char *msg, ...) { + if (!quiet) { + va_list args; + + fflush(stderr); + va_start(args, msg); + vfprintf(stdout, msg, args); + va_end(args); + } +} + +static void __printf_args(1, 2) error(const char *msg, ...) { + total_problems++; + + if (!quiet) { + va_list args; + + fflush(NULL); + va_start(args, msg); + fputs(" ! ", stderr); + vfprintf(stderr, msg, args); + va_end(args); + fflush(NULL); + } +} + +static void pagemap_cleanup(void) { + for (size_t i = CORE_DBS + /* account pseudo-entry for meta */ 1; + i < ARRAY_LENGTH(walk.dbi); ++i) { + if (walk.dbi[i].name) { + mdbx_free((void *)walk.dbi[i].name); + walk.dbi[i].name = NULL; + } + } + + mdbx_free(walk.pagemap); + walk.pagemap = NULL; +} + +static walk_dbi_t *pagemap_lookup_dbi(const char *dbi_name, bool silent) { + static walk_dbi_t *last; + + if (dbi_name == MDBX_PGWALK_MAIN) + return &dbi_main; + if (dbi_name == MDBX_PGWALK_GC) + return &dbi_free; + if (dbi_name == MDBX_PGWALK_META) + return &dbi_meta; + + if (last && strcmp(last->name, dbi_name) == 0) + return last; + + walk_dbi_t *dbi = walk.dbi + CORE_DBS + /* account pseudo-entry for meta */ 1; + for (; dbi < ARRAY_END(walk.dbi) && dbi->name; ++dbi) { + if (strcmp(dbi->name, dbi_name) == 0) + return last = dbi; + } + + if (verbose > 0 && !silent) { + print(" - found '%s' area\n", dbi_name); + fflush(NULL); + } + + if (dbi == ARRAY_END(walk.dbi)) + return NULL; + + dbi->name = mdbx_strdup(dbi_name); + return last = dbi; +} + +static void __printf_args(4, 5) + + problem_add(const char *object, uint64_t entry_number, const char *msg, + const char *extra, ...) { + total_problems++; + + if (!quiet) { + int need_fflush = 0; + struct problem *p; + + for (p = problems_list; p; p = p->pr_next) + if (p->caption == msg) + break; + + if (!p) { + p = mdbx_calloc(1, sizeof(*p)); + p->caption = msg; + p->pr_next = problems_list; + problems_list = p; + need_fflush = 1; + } + + p->count++; + if (verbose > 1) { + print(" %s #%" PRIu64 ": %s", object, entry_number, msg); + if (extra) { + va_list args; + printf(" ("); + va_start(args, extra); + vfprintf(stdout, extra, args); + va_end(args); + printf(")"); + } + printf("\n"); + if (need_fflush) + fflush(NULL); + } + } +} + +static struct problem *problems_push(void) { + struct problem *p = problems_list; + problems_list = NULL; + return p; +} + +static size_t problems_pop(struct problem *list) { + size_t count = 0; + + if (problems_list) { + int i; + + print(" - problems: "); + for (i = 0; problems_list; ++i) { + struct problem *p = problems_list->pr_next; + count += problems_list->count; + print("%s%s (%" PRIuPTR ")", i ? ", " : "", problems_list->caption, + problems_list->count); + mdbx_free(problems_list); + problems_list = p; + } + print("\n"); + fflush(NULL); + } + + problems_list = list; + return count; +} + +static int pgvisitor(const uint64_t pgno, const unsigned pgnumber, + void *const ctx, const int deep, + const char *const dbi_name_or_tag, const size_t page_size, + const MDBX_page_type_t pagetype, const size_t nentries, + const size_t payload_bytes, const size_t header_bytes, + const size_t unused_bytes) { + (void)ctx; + if (deep > 42) { + problem_add("deep", deep, "too large", nullptr); + return MDBX_CORRUPTED /* avoid infinite loop/recursion */; + } + + if (pagetype == MDBX_page_void) + return MDBX_SUCCESS; + + walk_dbi_t *dbi = pagemap_lookup_dbi(dbi_name_or_tag, false); + if (!dbi) + return MDBX_ENOMEM; + + const size_t page_bytes = payload_bytes + header_bytes + unused_bytes; + walk.pgcount += pgnumber; + + const char *pagetype_caption; + bool branch = false; + switch (pagetype) { + default: + problem_add("page", pgno, "unknown page-type", "type %u, deep %i", + (unsigned)pagetype, deep); + pagetype_caption = "unknown"; + dbi->pages.other += pgnumber; + break; + case MDBX_page_meta: + pagetype_caption = "meta"; + dbi->pages.other += pgnumber; + break; + case MDBX_page_large: + pagetype_caption = "large"; + dbi->pages.large_volume += pgnumber; + dbi->pages.large_count += 1; + break; + case MDBX_page_branch: + pagetype_caption = "branch"; + dbi->pages.branch += pgnumber; + branch = true; + break; + case MDBX_page_leaf: + pagetype_caption = "leaf"; + dbi->pages.leaf += pgnumber; + break; + case MDBX_page_dupfixed_leaf: + pagetype_caption = "leaf-dupfixed"; + dbi->pages.leaf_dupfixed += pgnumber; + break; + case MDBX_subpage_leaf: + pagetype_caption = "subleaf-dupsort"; + dbi->pages.subleaf_dupsort += 1; + break; + case MDBX_subpage_dupfixed_leaf: + pagetype_caption = "subleaf-dupfixed"; + dbi->pages.subleaf_dupfixed += 1; + break; + } + + if (pgnumber) { + if (verbose > 3 && (!only_subdb || strcmp(only_subdb, dbi->name) == 0)) { + if (pgnumber == 1) + print(" %s-page %" PRIu64, pagetype_caption, pgno); + else + print(" %s-span %" PRIu64 "[%u]", pagetype_caption, pgno, pgnumber); + print(" of %s: header %" PRIiPTR ", payload %" PRIiPTR + ", unused %" PRIiPTR ", deep %i\n", + dbi->name, header_bytes, payload_bytes, unused_bytes, deep); + } + + bool already_used = false; + for (unsigned n = 0; n < pgnumber; ++n) { + uint64_t spanpgno = pgno + n; + if (spanpgno >= alloc_pages) + problem_add("page", spanpgno, "wrong page-no", + "%s-page: %" PRIu64 " > %" PRIu64 ", deep %i", + pagetype_caption, spanpgno, alloc_pages, deep); + else if (walk.pagemap[spanpgno]) { + walk_dbi_t *coll_dbi = &walk.dbi[walk.pagemap[spanpgno] - 1]; + problem_add("page", spanpgno, + (branch && coll_dbi == dbi) ? "loop" : "already used", + "%s-page: by %s, deep %i", pagetype_caption, coll_dbi->name, + deep); + already_used = true; + } else { + walk.pagemap[spanpgno] = (short)(dbi - walk.dbi + 1); + dbi->pages.total += 1; + } + } + + if (already_used) + return branch ? MDBX_RESULT_TRUE /* avoid infinite loop/recursion */ + : MDBX_SUCCESS; + } + + if (unused_bytes > page_size) + problem_add("page", pgno, "illegal unused-bytes", + "%s-page: %u < %" PRIuPTR " < %u", pagetype_caption, 0, + unused_bytes, envstat.ms_psize); + + if (header_bytes < (int)sizeof(long) || + (size_t)header_bytes >= envstat.ms_psize - sizeof(long)) + problem_add("page", pgno, "illegal header-length", + "%s-page: %" PRIuPTR " < %" PRIuPTR " < %" PRIuPTR, + pagetype_caption, sizeof(long), header_bytes, + envstat.ms_psize - sizeof(long)); + if (payload_bytes < 1) { + if (nentries > 1) { + problem_add("page", pgno, "zero size-of-entry", + "%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR " entries", + pagetype_caption, payload_bytes, nentries); + /* if ((size_t)header_bytes + unused_bytes < page_size) { + // LY: hush a misuse error + page_bytes = page_size; + } */ + } else { + problem_add("page", pgno, "empty", + "%s-page: payload %" PRIuPTR " bytes, %" PRIuPTR + " entries, deep %i", + pagetype_caption, payload_bytes, nentries, deep); + dbi->pages.empty += 1; + } + } + + if (pgnumber) { + if (page_bytes != page_size) { + problem_add("page", pgno, "misused", + "%s-page: %" PRIuPTR " != %" PRIuPTR " (%" PRIuPTR + "h + %" PRIuPTR "p + %" PRIuPTR "u), deep %i", + pagetype_caption, page_size, page_bytes, header_bytes, + payload_bytes, unused_bytes, deep); + if (page_size > page_bytes) + dbi->lost_bytes += page_size - page_bytes; + } else { + dbi->payload_bytes += payload_bytes + header_bytes; + walk.total_payload_bytes += payload_bytes + header_bytes; + } + } + + return user_break ? MDBX_EINTR : MDBX_SUCCESS; +} + +typedef int(visitor)(const uint64_t record_number, const MDBX_val *key, + const MDBX_val *data); +static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler, + bool silent); + +static int handle_userdb(const uint64_t record_number, const MDBX_val *key, + const MDBX_val *data) { + (void)record_number; + (void)key; + (void)data; + return MDBX_SUCCESS; +} + +static int handle_freedb(const uint64_t record_number, const MDBX_val *key, + const MDBX_val *data) { + char *bad = ""; + pgno_t *iptr = data->iov_base; + + if (key->iov_len != sizeof(txnid_t)) + problem_add("entry", record_number, "wrong txn-id size", + "key-size %" PRIiPTR, key->iov_len); + else { + txnid_t txnid; + memcpy(&txnid, key->iov_base, sizeof(txnid)); + if (txnid < 1 || txnid > envinfo.mi_recent_txnid) + problem_add("entry", record_number, "wrong txn-id", "%" PRIaTXN, txnid); + else { + if (data->iov_len < sizeof(pgno_t) || data->iov_len % sizeof(pgno_t)) + problem_add("entry", txnid, "wrong idl size", "%" PRIuPTR, + data->iov_len); + size_t number = (data->iov_len >= sizeof(pgno_t)) ? *iptr++ : 0; + if (number < 1 || number > MDBX_PNL_MAX) + problem_add("entry", txnid, "wrong idl length", "%" PRIuPTR, number); + else if ((number + 1) * sizeof(pgno_t) > data->iov_len) { + problem_add("entry", txnid, "trimmed idl", + "%" PRIuSIZE " > %" PRIuSIZE " (corruption)", + (number + 1) * sizeof(pgno_t), data->iov_len); + number = data->iov_len / sizeof(pgno_t) - 1; + } else if (data->iov_len - (number + 1) * sizeof(pgno_t) >= + /* LY: allow gap upto one page. it is ok + * and better than shink-and-retry inside mdbx_update_gc() */ + envstat.ms_psize) + problem_add("entry", txnid, "extra idl space", + "%" PRIuSIZE " < %" PRIuSIZE " (minor, not a trouble)", + (number + 1) * sizeof(pgno_t), data->iov_len); + + gc_pages += number; + if (envinfo.mi_latter_reader_txnid > txnid) + reclaimable_pages += number; + + pgno_t prev = MDBX_PNL_ASCENDING ? NUM_METAS - 1 : txn->mt_next_pgno; + pgno_t span = 1; + for (unsigned i = 0; i < number; ++i) { + const pgno_t pgno = iptr[i]; + if (pgno < NUM_METAS) + problem_add("entry", txnid, "wrong idl entry", + "pgno %" PRIaPGNO " < meta-pages %u", pgno, NUM_METAS); + else if (pgno >= backed_pages) + problem_add("entry", txnid, "wrong idl entry", + "pgno %" PRIaPGNO " > backed-pages %" PRIu64, pgno, + backed_pages); + else if (pgno >= alloc_pages) + problem_add("entry", txnid, "wrong idl entry", + "pgno %" PRIaPGNO " > alloc-pages %" PRIu64, pgno, + alloc_pages - 1); + else { + if (MDBX_PNL_DISORDERED(prev, pgno)) { + bad = " [bad sequence]"; + problem_add("entry", txnid, "bad sequence", + "%" PRIaPGNO " %c [%u].%" PRIaPGNO, prev, + (prev == pgno) ? '=' : (MDBX_PNL_ASCENDING ? '>' : '<'), + i, pgno); + } + if (walk.pagemap) { + int idx = walk.pagemap[pgno]; + if (idx == 0) + walk.pagemap[pgno] = -1; + else if (idx > 0) + problem_add("page", pgno, "already used", "by %s", + walk.dbi[idx - 1].name); + else + problem_add("page", pgno, "already listed in GC", nullptr); + } + } + prev = pgno; + while (i + span < number && + iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pgno, span) + : pgno_sub(pgno, span))) + ++span; + } + if (verbose > 3 && !only_subdb) { + print(" transaction %" PRIaTXN ", %" PRIuPTR + " pages, maxspan %" PRIaPGNO "%s\n", + txnid, number, span, bad); + if (verbose > 4) { + for (unsigned i = 0; i < number; i += span) { + const pgno_t pgno = iptr[i]; + for (span = 1; + i + span < number && + iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pgno, span) + : pgno_sub(pgno, span)); + ++span) + ; + if (span > 1) { + print(" %9" PRIaPGNO "[%" PRIaPGNO "]\n", pgno, span); + } else + print(" %9" PRIaPGNO "\n", pgno); + } + } + } + } + } + + return MDBX_SUCCESS; +} + +static int handle_maindb(const uint64_t record_number, const MDBX_val *key, + const MDBX_val *data) { + char *name; + int rc; + size_t i; + + name = key->iov_base; + for (i = 0; i < key->iov_len; ++i) { + if (name[i] < ' ') + return handle_userdb(record_number, key, data); + } + + name = mdbx_malloc(key->iov_len + 1); + memcpy(name, key->iov_base, key->iov_len); + name[key->iov_len] = '\0'; + userdb_count++; + + rc = process_db(~0u, name, handle_userdb, false); + mdbx_free(name); + if (rc != MDBX_INCOMPATIBLE) + return rc; + + return handle_userdb(record_number, key, data); +} + +static int process_db(MDBX_dbi dbi_handle, char *dbi_name, visitor *handler, + bool silent) { + MDBX_cursor *mc; + MDBX_stat ms; + MDBX_val key, data; + MDBX_val prev_key, prev_data; + unsigned flags; + int rc, i; + struct problem *saved_list; + uint64_t problems_count; + + uint64_t record_count = 0, dups = 0; + uint64_t key_bytes = 0, data_bytes = 0; + + if (dbi_handle == ~0u) { + rc = mdbx_dbi_open(txn, dbi_name, 0, &dbi_handle); + if (rc) { + if (!dbi_name || + rc != + MDBX_INCOMPATIBLE) /* LY: mainDB's record is not a user's DB. */ { + error("mdbx_open '%s' failed, error %d %s\n", + dbi_name ? dbi_name : "main", rc, mdbx_strerror(rc)); + } + return rc; + } + } + + if (dbi_handle >= CORE_DBS && dbi_name && only_subdb && + strcmp(only_subdb, dbi_name) != 0) { + if (verbose) { + print("Skip processing '%s'...\n", dbi_name); + fflush(NULL); + } + skipped_subdb++; + return MDBX_SUCCESS; + } + + if (!silent && verbose) { + print("Processing '%s'...\n", dbi_name ? dbi_name : "@MAIN"); + fflush(NULL); + } + + rc = mdbx_dbi_flags(txn, dbi_handle, &flags); + if (rc) { + error("mdbx_dbi_flags failed, error %d %s\n", rc, mdbx_strerror(rc)); + return rc; + } + + rc = mdbx_dbi_stat(txn, dbi_handle, &ms, sizeof(ms)); + if (rc) { + error("mdbx_dbi_stat failed, error %d %s\n", rc, mdbx_strerror(rc)); + return rc; + } + + if (!silent && verbose) { + print(" - dbi-id %d, flags:", dbi_handle); + if (!flags) + print(" none"); + else { + for (i = 0; dbflags[i].bit; i++) + if (flags & dbflags[i].bit) + print(" %s", dbflags[i].name); + } + print(" (0x%02X)\n", flags); + if (verbose > 1) { + print(" - page size %u, entries %" PRIu64 "\n", ms.ms_psize, + ms.ms_entries); + print(" - b-tree depth %u, pages: branch %" PRIu64 ", leaf %" PRIu64 + ", overflow %" PRIu64 "\n", + ms.ms_depth, ms.ms_branch_pages, ms.ms_leaf_pages, + ms.ms_overflow_pages); + } + } + + walk_dbi_t *dbi = (dbi_handle < CORE_DBS) + ? &walk.dbi[dbi_handle] + : pagemap_lookup_dbi(dbi_name, true); + if (!dbi) { + error("too many DBIs or out of memory\n"); + return MDBX_ENOMEM; + } + const uint64_t subtotal_pages = + ms.ms_branch_pages + ms.ms_leaf_pages + ms.ms_overflow_pages; + if (subtotal_pages != dbi->pages.total) + error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", "subtotal", + subtotal_pages, dbi->pages.total); + if (ms.ms_branch_pages != dbi->pages.branch) + error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", "branch", + ms.ms_branch_pages, dbi->pages.branch); + const uint64_t allleaf_pages = dbi->pages.leaf + dbi->pages.leaf_dupfixed; + if (ms.ms_leaf_pages != allleaf_pages) + error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", "all-leaf", + ms.ms_leaf_pages, allleaf_pages); + if (ms.ms_overflow_pages != dbi->pages.large_volume) + error("%s pages mismatch (%" PRIu64 " != walked %" PRIu64 ")\n", + "large/overlow", ms.ms_overflow_pages, dbi->pages.large_volume); + + rc = mdbx_cursor_open(txn, dbi_handle, &mc); + if (rc) { + error("mdbx_cursor_open failed, error %d %s\n", rc, mdbx_strerror(rc)); + return rc; + } + + saved_list = problems_push(); + prev_key.iov_base = NULL; + prev_key.iov_len = 0; + prev_data.iov_base = NULL; + prev_data.iov_len = 0; + rc = mdbx_cursor_get(mc, &key, &data, MDBX_FIRST); + while (rc == MDBX_SUCCESS) { + if (user_break) { + print(" - interrupted by signal\n"); + fflush(NULL); + rc = MDBX_EINTR; + goto bailout; + } + + bool bad_key = false; + if (key.iov_len > maxkeysize) { + problem_add("entry", record_count, "key length exceeds max-key-size", + "%" PRIuPTR " > %" PRIuPTR, key.iov_len, maxkeysize); + bad_key = true; + } else if ((flags & MDBX_INTEGERKEY) && key.iov_len != sizeof(uint64_t) && + key.iov_len != sizeof(uint32_t)) { + problem_add("entry", record_count, "wrong key length", + "%" PRIuPTR " != 4or8", key.iov_len); + bad_key = true; + } + + bool bad_data = false; + if ((flags & MDBX_INTEGERDUP) && data.iov_len != sizeof(uint64_t) && + data.iov_len != sizeof(uint32_t)) { + problem_add("entry", record_count, "wrong data length", + "%" PRIuPTR " != 4or8", data.iov_len); + bad_data = true; + } + + if (prev_key.iov_base && !bad_data) { + if ((flags & MDBX_DUPFIXED) && prev_data.iov_len != data.iov_len) { + problem_add("entry", record_count, "different data length", + "%" PRIuPTR " != %" PRIuPTR, prev_data.iov_len, + data.iov_len); + bad_data = true; + } + + if (!bad_key) { + int cmp = mdbx_cmp(txn, dbi_handle, &prev_key, &key); + if (cmp == 0) { + ++dups; + if ((flags & MDBX_DUPSORT) == 0) { + problem_add("entry", record_count, "duplicated entries", NULL); + if (data.iov_len == prev_data.iov_len && + memcmp(data.iov_base, prev_data.iov_base, data.iov_len) == 0) { + problem_add("entry", record_count, "complete duplicate", NULL); + } + } else if (!bad_data) { + cmp = mdbx_dcmp(txn, dbi_handle, &prev_data, &data); + if (cmp == 0) { + problem_add("entry", record_count, "complete duplicate", NULL); + } else if (cmp > 0 && !ignore_wrong_order) { + problem_add("entry", record_count, "wrong order of multi-values", + NULL); + } + } + } else if (cmp > 0 && !ignore_wrong_order) { + problem_add("entry", record_count, "wrong order of entries", NULL); + } + } + } else if (verbose) { + if (flags & MDBX_INTEGERKEY) + print(" - fixed key-size %" PRIuPTR "\n", key.iov_len); + if (flags & (MDBX_INTEGERDUP | MDBX_DUPFIXED)) + print(" - fixed data-size %" PRIuPTR "\n", data.iov_len); + } + + if (handler) { + rc = handler(record_count, &key, &data); + if (MDBX_IS_ERROR(rc)) + goto bailout; + } + + record_count++; + key_bytes += key.iov_len; + data_bytes += data.iov_len; + + if (!bad_key) + prev_key = key; + if (!bad_data) + prev_data = data; + rc = mdbx_cursor_get(mc, &key, &data, MDBX_NEXT); + } + if (rc != MDBX_NOTFOUND) + error("mdbx_cursor_get failed, error %d %s\n", rc, mdbx_strerror(rc)); + else + rc = 0; + + if (record_count != ms.ms_entries) + problem_add("entry", record_count, "differentent number of entries", + "%" PRIu64 " != %" PRIu64, record_count, ms.ms_entries); +bailout: + problems_count = problems_pop(saved_list); + if (!silent && verbose) { + print(" - summary: %" PRIu64 " records, %" PRIu64 " dups, %" PRIu64 + " key's bytes, %" PRIu64 " data's " + "bytes, %" PRIu64 " problems\n", + record_count, dups, key_bytes, data_bytes, problems_count); + fflush(NULL); + } + + mdbx_cursor_close(mc); + return (rc || problems_count) ? MDBX_RESULT_TRUE : MDBX_SUCCESS; +} + +static void usage(char *prog) { + fprintf(stderr, + "usage: %s dbpath [-V] [-v] [-n] [-q] [-w] [-c] [-d] [-s subdb]\n" + " -V\t\tshow version\n" + " -v\t\tmore verbose, could be used multiple times\n" + " -n\t\tNOSUBDIR mode for open\n" + " -q\t\tbe quiet\n" + " -w\t\tlock DB for writing while checking\n" + " -d\t\tdisable page-by-page traversal of B-tree\n" + " -s subdb\tprocess a specific subdatabase only\n" + " -c\t\tforce cooperative mode (don't try exclusive)\n" + " -i\t\tignore wrong order errors (for custom comparators case)\n", + prog); + exit(EXIT_INTERRUPTED); +} + +const char *meta_synctype(uint64_t sign) { + switch (sign) { + case MDBX_DATASIGN_NONE: + return "no-sync/legacy"; + case MDBX_DATASIGN_WEAK: + return "weak"; + default: + return "steady"; + } +} + +static __inline bool meta_ot(txnid_t txn_a, uint64_t sign_a, txnid_t txn_b, + uint64_t sign_b, const bool roolback2steady) { + if (txn_a == txn_b) + return SIGN_IS_STEADY(sign_b); + + if (roolback2steady && SIGN_IS_STEADY(sign_a) != SIGN_IS_STEADY(sign_b)) + return SIGN_IS_STEADY(sign_b); + + return txn_a < txn_b; +} + +static __inline bool meta_eq(txnid_t txn_a, uint64_t sign_a, txnid_t txn_b, + uint64_t sign_b) { + if (txn_a != txn_b) + return false; + + if (SIGN_IS_STEADY(sign_a) != SIGN_IS_STEADY(sign_b)) + return false; + + return true; +} + +static __inline int meta_recent(const bool roolback2steady) { + + if (meta_ot(envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign, + envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign, roolback2steady)) + return meta_ot(envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign, + envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign, + roolback2steady) + ? 1 + : 2; + + return meta_ot(envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign, + envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign, roolback2steady) + ? 2 + : 0; +} + +static __inline int meta_tail(int head) { + + if (head == 0) + return meta_ot(envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign, + envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign, true) + ? 1 + : 2; + if (head == 1) + return meta_ot(envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign, + envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign, true) + ? 0 + : 2; + if (head == 2) + return meta_ot(envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign, + envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign, true) + ? 0 + : 1; + assert(false); + return -1; +} + +static int meta_steady(void) { return meta_recent(true); } + +static int meta_head(void) { return meta_recent(false); } + +void verbose_meta(int num, txnid_t txnid, uint64_t sign) { + print(" - meta-%d: %s %" PRIu64, num, meta_synctype(sign), txnid); + bool stay = true; + + const int steady = meta_steady(); + const int head = meta_head(); + if (num == steady && num == head) { + print(", head"); + stay = false; + } else if (num == steady) { + print(", head-steady"); + stay = false; + } else if (num == head) { + print(", head-weak"); + stay = false; + } + if (num == meta_tail(head)) { + print(", tail"); + stay = false; + } + if (stay) + print(", stay"); + + if (txnid > envinfo.mi_recent_txnid && + (envflags & (MDBX_EXCLUSIVE | MDBX_RDONLY)) == MDBX_EXCLUSIVE) + print(", rolled-back %" PRIu64 " (%" PRIu64 " >>> %" PRIu64 ")", + txnid - envinfo.mi_recent_txnid, txnid, envinfo.mi_recent_txnid); + print("\n"); +} + +static int check_meta_head(bool steady) { + switch (meta_recent(steady)) { + default: + assert(false); + error("unexpected internal error (%s)\n", + steady ? "meta_steady_head" : "meta_weak_head"); + __fallthrough; + case 0: + if (envinfo.mi_meta0_txnid != envinfo.mi_recent_txnid) { + print(" - meta-%d txn-id mismatch recent-txn-id (%" PRIi64 " != %" PRIi64 + ")\n", + 0, envinfo.mi_meta0_txnid, envinfo.mi_recent_txnid); + return 1; + } + break; + case 1: + if (envinfo.mi_meta1_txnid != envinfo.mi_recent_txnid) { + print(" - meta-%d txn-id mismatch recent-txn-id (%" PRIi64 " != %" PRIi64 + ")\n", + 1, envinfo.mi_meta1_txnid, envinfo.mi_recent_txnid); + return 1; + } + break; + case 2: + if (envinfo.mi_meta2_txnid != envinfo.mi_recent_txnid) { + print(" - meta-%d txn-id mismatch recent-txn-id (%" PRIi64 " != %" PRIi64 + ")\n", + 2, envinfo.mi_meta2_txnid, envinfo.mi_recent_txnid); + return 1; + } + } + return 0; +} + +static void print_size(const char *prefix, const uint64_t value, + const char *suffix) { + const char sf[] = + "KMGTPEZY"; /* LY: Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta! */ + double k = 1024.0; + size_t i; + for (i = 0; sf[i + 1] && value / k > 1000.0; ++i) + k *= 1024; + print("%s%" PRIu64 " (%.2f %cb)%s", prefix, value, value / k, sf[i], suffix); +} + +int main(int argc, char *argv[]) { + int rc; + char *prog = argv[0]; + char *envname; + int problems_maindb = 0, problems_freedb = 0, problems_meta = 0; + bool dont_traversal = false; + bool locked = false; + + double elapsed; +#if defined(_WIN32) || defined(_WIN64) + uint64_t timestamp_start, timestamp_finish; + timestamp_start = GetTickCount64(); +#else + struct timespec timestamp_start, timestamp_finish; + if (clock_gettime(CLOCK_MONOTONIC, ×tamp_start)) { + rc = errno; + error("clock_gettime failed, error %d %s\n", rc, mdbx_strerror(rc)); + return EXIT_FAILURE_SYS; + } +#endif + + dbi_meta.name = "@META"; + dbi_free.name = "@GC"; + dbi_main.name = "@MAIN"; + atexit(pagemap_cleanup); + + if (argc < 2) + usage(prog); + + for (int i; (i = getopt(argc, argv, "Vvqnwcdsi:")) != EOF;) { + switch (i) { + case 'V': + printf("mdbx_chk version %d.%d.%d.%d\n" + " - source: %s %s, commit %s, tree %s\n" + " - anchor: %s\n" + " - build: %s for %s by %s\n" + " - flags: %s\n" + " - options: %s\n", + mdbx_version.major, mdbx_version.minor, mdbx_version.release, + mdbx_version.revision, mdbx_version.git.describe, + mdbx_version.git.datetime, mdbx_version.git.commit, + mdbx_version.git.tree, mdbx_sourcery_anchor, mdbx_build.datetime, + mdbx_build.target, mdbx_build.compiler, mdbx_build.flags, + mdbx_build.options); + return EXIT_SUCCESS; + case 'v': + verbose++; + break; + case 'q': + quiet = true; + break; + case 'n': + envflags |= MDBX_NOSUBDIR; + break; + case 'w': + envflags &= ~MDBX_RDONLY; + break; + case 'c': + envflags &= ~MDBX_EXCLUSIVE; + break; + case 'd': + dont_traversal = true; + break; + case 's': + if (only_subdb && strcmp(only_subdb, optarg)) + usage(prog); + only_subdb = optarg; + break; + case 'i': + ignore_wrong_order = true; + break; + default: + usage(prog); + } + } + + if (optind != argc - 1) + usage(prog); + +#if defined(_WIN32) || defined(_WIN64) + SetConsoleCtrlHandler(ConsoleBreakHandlerRoutine, true); +#else +#ifdef SIGPIPE + signal(SIGPIPE, signal_handler); +#endif +#ifdef SIGHUP + signal(SIGHUP, signal_handler); +#endif + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); +#endif /* !WINDOWS */ + + envname = argv[optind]; + print("mdbx_chk %s (%s, T-%s)\nRunning for %s in 'read-%s' mode...\n", + mdbx_version.git.describe, mdbx_version.git.datetime, + mdbx_version.git.tree, envname, + (envflags & MDBX_RDONLY) ? "only" : "write"); + fflush(NULL); + + rc = mdbx_env_create(&env); + if (rc) { + error("mdbx_env_create failed, error %d %s\n", rc, mdbx_strerror(rc)); + return rc < 0 ? EXIT_FAILURE_MDB : EXIT_FAILURE_SYS; + } + + rc = mdbx_env_set_maxdbs(env, MAX_DBI); + if (rc) { + error("mdbx_env_set_maxdbs failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + rc = mdbx_env_open(env, envname, envflags, 0664); + if ((envflags & MDBX_EXCLUSIVE) && + (rc == MDBX_BUSY || +#if defined(_WIN32) || defined(_WIN64) + rc == ERROR_LOCK_VIOLATION || rc == ERROR_SHARING_VIOLATION +#else + rc == EBUSY || rc == EAGAIN +#endif + )) { + envflags &= ~MDBX_EXCLUSIVE; + rc = mdbx_env_open(env, envname, envflags, 0664); + } + + if (rc) { + error("mdbx_env_open failed, error %d %s\n", rc, mdbx_strerror(rc)); + if (rc == MDBX_WANNA_RECOVERY && (envflags & MDBX_RDONLY)) + print("Please run %s in the read-write mode (with '-w' option).\n", prog); + goto bailout; + } + if (verbose) + print(" - %s mode\n", + (envflags & MDBX_EXCLUSIVE) ? "monopolistic" : "cooperative"); + + if ((envflags & MDBX_RDONLY) == 0) { + rc = mdbx_txn_lock(env, false); + if (rc != MDBX_SUCCESS) { + error("mdbx_txn_lock failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + locked = true; + } + + rc = mdbx_txn_begin(env, NULL, MDBX_RDONLY, &txn); + if (rc) { + error("mdbx_txn_begin() failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + rc = mdbx_env_get_maxkeysize(env); + if (rc < 0) { + error("mdbx_env_get_maxkeysize failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto bailout; + } + maxkeysize = rc; + + rc = mdbx_env_info_ex(env, txn, &envinfo, sizeof(envinfo)); + if (rc) { + error("mdbx_env_info failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + rc = mdbx_env_stat_ex(env, txn, &envstat, sizeof(envstat)); + if (rc) { + error("mdbx_env_stat failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + mdbx_filehandle_t dxb_fd; + rc = mdbx_env_get_fd(env, &dxb_fd); + if (rc) { + error("mdbx_env_get_fd failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + uint64_t dxb_filesize = 0; +#if defined(_WIN32) || defined(_WIN64) + { + BY_HANDLE_FILE_INFORMATION info; + if (!GetFileInformationByHandle(dxb_fd, &info)) + rc = GetLastError(); + else + dxb_filesize = info.nFileSizeLow | (uint64_t)info.nFileSizeHigh << 32; + } +#else + { + struct stat st; + STATIC_ASSERT_MSG(sizeof(off_t) <= sizeof(uint64_t), + "libmdbx requires 64-bit file I/O on 64-bit systems"); + if (fstat(dxb_fd, &st)) + rc = errno; + else + dxb_filesize = st.st_size; + } +#endif + if (rc) { + error("mdbx_filesize failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + errno = 0; + const uint64_t dxbfile_pages = dxb_filesize / envinfo.mi_dxb_pagesize; + alloc_pages = txn->mt_next_pgno; + backed_pages = envinfo.mi_geo.current / envinfo.mi_dxb_pagesize; + if (backed_pages > dxbfile_pages) { + print(" ! backed-pages %" PRIu64 " > file-pages %" PRIu64 "\n", + backed_pages, dxbfile_pages); + ++problems_meta; + } + if (dxbfile_pages < NUM_METAS) + print(" ! file-pages %" PRIu64 " < %u\n", dxbfile_pages, NUM_METAS); + if (backed_pages < NUM_METAS) + print(" ! backed-pages %" PRIu64 " < %u\n", backed_pages, NUM_METAS); + if (backed_pages < NUM_METAS || dxbfile_pages < NUM_METAS) + goto bailout; + if (backed_pages > MAX_PAGENO) { + print(" ! backed-pages %" PRIu64 " > max-pages %" PRIaPGNO "\n", + backed_pages, MAX_PAGENO); + ++problems_meta; + backed_pages = MAX_PAGENO; + } + + if ((envflags & (MDBX_EXCLUSIVE | MDBX_RDONLY)) != MDBX_RDONLY) { + if (backed_pages > dxbfile_pages) { + print(" ! backed-pages %" PRIu64 " > file-pages %" PRIu64 "\n", + backed_pages, dxbfile_pages); + ++problems_meta; + backed_pages = dxbfile_pages; + } + if (alloc_pages > backed_pages) { + print(" ! alloc-pages %" PRIu64 " > backed-pages %" PRIu64 "\n", + alloc_pages, backed_pages); + ++problems_meta; + alloc_pages = backed_pages; + } + } else { + /* LY: DB may be shrinked by writer downto the allocated pages. */ + if (alloc_pages > backed_pages) { + print(" ! alloc-pages %" PRIu64 " > backed-pages %" PRIu64 "\n", + alloc_pages, backed_pages); + ++problems_meta; + alloc_pages = backed_pages; + } + if (alloc_pages > dxbfile_pages) { + print(" ! alloc-pages %" PRIu64 " > file-pages %" PRIu64 "\n", + alloc_pages, dxbfile_pages); + ++problems_meta; + alloc_pages = dxbfile_pages; + } + if (backed_pages > dxbfile_pages) + backed_pages = dxbfile_pages; + } + + if (verbose) { + print(" - pagesize %u (%u system), max keysize %" PRIuPTR + ", max readers %u\n", + envinfo.mi_dxb_pagesize, envinfo.mi_sys_pagesize, maxkeysize, + envinfo.mi_maxreaders); + print_size(" - mapsize ", envinfo.mi_mapsize, "\n"); + if (envinfo.mi_geo.lower == envinfo.mi_geo.upper) + print_size(" - fixed datafile: ", envinfo.mi_geo.current, ""); + else { + print_size(" - dynamic datafile: ", envinfo.mi_geo.lower, ""); + print_size(" .. ", envinfo.mi_geo.upper, ", "); + print_size("+", envinfo.mi_geo.grow, ", "); + print_size("-", envinfo.mi_geo.shrink, "\n"); + print_size(" - current datafile: ", envinfo.mi_geo.current, ""); + } + printf(", %" PRIu64 " pages\n", + envinfo.mi_geo.current / envinfo.mi_dxb_pagesize); + print(" - transactions: recent %" PRIu64 ", latter reader %" PRIu64 + ", lag %" PRIi64 "\n", + envinfo.mi_recent_txnid, envinfo.mi_latter_reader_txnid, + envinfo.mi_recent_txnid - envinfo.mi_latter_reader_txnid); + + verbose_meta(0, envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign); + verbose_meta(1, envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign); + verbose_meta(2, envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign); + } + + if (verbose) + print(" - performs check for meta-pages clashes\n"); + if (meta_eq(envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign, + envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign)) { + print(" ! meta-%d and meta-%d are clashed\n", 0, 1); + ++problems_meta; + } + if (meta_eq(envinfo.mi_meta1_txnid, envinfo.mi_meta1_sign, + envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign)) { + print(" ! meta-%d and meta-%d are clashed\n", 1, 2); + ++problems_meta; + } + if (meta_eq(envinfo.mi_meta2_txnid, envinfo.mi_meta2_sign, + envinfo.mi_meta0_txnid, envinfo.mi_meta0_sign)) { + print(" ! meta-%d and meta-%d are clashed\n", 2, 0); + ++problems_meta; + } + + if (envflags & MDBX_EXCLUSIVE) { + if (verbose) + print(" - performs full check recent-txn-id with meta-pages\n"); + problems_meta += check_meta_head(true); + } else if (locked) { + if (verbose) + print(" - performs lite check recent-txn-id with meta-pages (not a " + "monopolistic mode)\n"); + problems_meta += check_meta_head(false); + } else if (verbose) { + print(" - skip check recent-txn-id with meta-pages (monopolistic or " + "read-write mode only)\n"); + } + + if (!dont_traversal) { + struct problem *saved_list; + size_t traversal_problems; + uint64_t empty_pages, lost_bytes; + + print("Traversal b-tree by txn#%" PRIaTXN "...\n", txn->mt_txnid); + fflush(NULL); + walk.pagemap = mdbx_calloc((size_t)backed_pages, sizeof(*walk.pagemap)); + if (!walk.pagemap) { + rc = errno ? errno : MDBX_ENOMEM; + error("calloc failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto bailout; + } + + saved_list = problems_push(); + rc = mdbx_env_pgwalk(txn, pgvisitor, NULL); + traversal_problems = problems_pop(saved_list); + + if (rc) { + if (rc == MDBX_EINTR && user_break) { + print(" - interrupted by signal\n"); + fflush(NULL); + } else { + error("mdbx_env_pgwalk failed, error %d %s\n", rc, mdbx_strerror(rc)); + } + goto bailout; + } + + for (uint64_t n = 0; n < alloc_pages; ++n) + if (!walk.pagemap[n]) + unused_pages += 1; + + empty_pages = lost_bytes = 0; + for (walk_dbi_t *dbi = &dbi_main; dbi < ARRAY_END(walk.dbi) && dbi->name; + ++dbi) { + empty_pages += dbi->pages.empty; + lost_bytes += dbi->lost_bytes; + } + + if (verbose) { + uint64_t total_page_bytes = walk.pgcount * envstat.ms_psize; + print(" - pages: total %" PRIu64 ", unused %" PRIu64 "\n", walk.pgcount, + unused_pages); + if (verbose > 1) { + for (walk_dbi_t *dbi = walk.dbi; dbi < ARRAY_END(walk.dbi) && dbi->name; + ++dbi) { + print(" %s: subtotal %" PRIu64, dbi->name, dbi->pages.total); + if (dbi->pages.other && dbi->pages.other != dbi->pages.total) + print(", other %" PRIu64, dbi->pages.other); + if (dbi->pages.branch) + print(", branch %" PRIu64, dbi->pages.branch); + if (dbi->pages.large_count) + print(", large %" PRIu64, dbi->pages.large_count); + uint64_t all_leaf = dbi->pages.leaf + dbi->pages.leaf_dupfixed; + if (all_leaf) { + print(", leaf %" PRIu64, all_leaf); + if (verbose > 2 && + (dbi->pages.leaf_dupfixed | dbi->pages.subleaf_dupsort | + dbi->pages.subleaf_dupsort)) + print(" (usual %" PRIu64 ", sub-dupsort %" PRIu64 + ", dupfixed %" PRIu64 ", sub-dupfixed %" PRIu64 ")", + dbi->pages.leaf, dbi->pages.subleaf_dupsort, + dbi->pages.leaf_dupfixed, dbi->pages.subleaf_dupfixed); + } + print("\n"); + } + } + + if (verbose > 1) + print(" - usage: total %" PRIu64 " bytes, payload %" PRIu64 + " (%.1f%%), unused " + "%" PRIu64 " (%.1f%%)\n", + total_page_bytes, walk.total_payload_bytes, + walk.total_payload_bytes * 100.0 / total_page_bytes, + total_page_bytes - walk.total_payload_bytes, + (total_page_bytes - walk.total_payload_bytes) * 100.0 / + total_page_bytes); + if (verbose > 2) { + for (walk_dbi_t *dbi = walk.dbi; dbi < ARRAY_END(walk.dbi) && dbi->name; + ++dbi) + if (dbi->pages.total) { + uint64_t dbi_bytes = dbi->pages.total * envstat.ms_psize; + print(" %s: subtotal %" PRIu64 " bytes (%.1f%%)," + " payload %" PRIu64 " (%.1f%%), unused %" PRIu64 " (%.1f%%)", + dbi->name, dbi_bytes, dbi_bytes * 100.0 / total_page_bytes, + dbi->payload_bytes, dbi->payload_bytes * 100.0 / dbi_bytes, + dbi_bytes - dbi->payload_bytes, + (dbi_bytes - dbi->payload_bytes) * 100.0 / dbi_bytes); + if (dbi->pages.empty) + print(", %" PRIu64 " empty pages", dbi->pages.empty); + if (dbi->lost_bytes) + print(", %" PRIu64 " bytes lost", dbi->lost_bytes); + print("\n"); + } else + print(" %s: empty\n", dbi->name); + } + print(" - summary: average fill %.1f%%", + walk.total_payload_bytes * 100.0 / total_page_bytes); + if (empty_pages) + print(", %" PRIu64 " empty pages", empty_pages); + if (lost_bytes) + print(", %" PRIu64 " bytes lost", lost_bytes); + print(", %" PRIuPTR " problems\n", traversal_problems); + } + } else if (verbose) { + print("Skipping b-tree walk...\n"); + fflush(NULL); + } + + if (!verbose) + print("Iterating DBIs...\n"); + problems_maindb = process_db(~0u, /* MAIN_DBI */ NULL, NULL, false); + problems_freedb = process_db(FREE_DBI, "@GC", handle_freedb, false); + + if (verbose) { + uint64_t value = envinfo.mi_mapsize / envstat.ms_psize; + double percent = value / 100.0; + print(" - space: %" PRIu64 " total pages", value); + print(", backed %" PRIu64 " (%.1f%%)", backed_pages, + backed_pages / percent); + print(", allocated %" PRIu64 " (%.1f%%)", alloc_pages, + alloc_pages / percent); + + if (verbose > 1) { + value = envinfo.mi_mapsize / envstat.ms_psize - alloc_pages; + print(", remained %" PRIu64 " (%.1f%%)", value, value / percent); + + value = alloc_pages - gc_pages; + print(", used %" PRIu64 " (%.1f%%)", value, value / percent); + + print(", gc %" PRIu64 " (%.1f%%)", gc_pages, gc_pages / percent); + + value = gc_pages - reclaimable_pages; + print(", detained %" PRIu64 " (%.1f%%)", value, value / percent); + + print(", reclaimable %" PRIu64 " (%.1f%%)", reclaimable_pages, + reclaimable_pages / percent); + } + + value = + envinfo.mi_mapsize / envstat.ms_psize - alloc_pages + reclaimable_pages; + print(", available %" PRIu64 " (%.1f%%)\n", value, value / percent); + } + + if (problems_maindb == 0 && problems_freedb == 0) { + if (!dont_traversal && + (envflags & (MDBX_EXCLUSIVE | MDBX_RDONLY)) != MDBX_RDONLY) { + if (walk.pgcount != alloc_pages - gc_pages) { + error("used pages mismatch (%" PRIu64 "(walked) != %" PRIu64 + "(allocated - GC))\n", + walk.pgcount, alloc_pages - gc_pages); + } + if (unused_pages != gc_pages) { + error("gc pages mismatch (%" PRIu64 "(walked) != %" PRIu64 "(GC))\n", + unused_pages, gc_pages); + } + } else if (verbose) { + print(" - skip check used and gc pages (btree-traversal with " + "monopolistic or read-write mode only)\n"); + } + + if (!process_db(MAIN_DBI, NULL, handle_maindb, true)) { + if (!userdb_count && verbose) + print(" - does not contain multiple databases\n"); + } + } + +bailout: + if (txn) + mdbx_txn_abort(txn); + if (locked) + mdbx_txn_unlock(env); + if (env) + mdbx_env_close(env); + fflush(NULL); + if (rc) { + if (rc < 0) + return (user_break) ? EXIT_INTERRUPTED : EXIT_FAILURE_SYS; + return EXIT_FAILURE_MDB; + } + +#if defined(_WIN32) || defined(_WIN64) + timestamp_finish = GetTickCount64(); + elapsed = (timestamp_finish - timestamp_start) * 1e-3; +#else + if (clock_gettime(CLOCK_MONOTONIC, ×tamp_finish)) { + rc = errno; + error("clock_gettime failed, error %d %s\n", rc, mdbx_strerror(rc)); + return EXIT_FAILURE_SYS; + } + elapsed = timestamp_finish.tv_sec - timestamp_start.tv_sec + + (timestamp_finish.tv_nsec - timestamp_start.tv_nsec) * 1e-9; +#endif /* !WINDOWS */ + + total_problems += problems_meta; + if (total_problems || problems_maindb || problems_freedb) { + print("Total %" PRIu64 " error%s detected, elapsed %.3f seconds.\n", + total_problems, (total_problems > 1) ? "s are" : " is", elapsed); + if (problems_meta || problems_maindb || problems_freedb) + return EXIT_FAILURE_CHECK_MAJOR; + return EXIT_FAILURE_CHECK_MINOR; + } + print("No error is detected, elapsed %.3f seconds\n", elapsed); + return EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/src/tools/mdbx_copy.c b/contrib/db/libmdbx/src/tools/mdbx_copy.c new file mode 100644 index 00000000..a58fd740 --- /dev/null +++ b/contrib/db/libmdbx/src/tools/mdbx_copy.c @@ -0,0 +1,130 @@ +/* mdbx_copy.c - memory-mapped database backup tool */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +#ifdef _MSC_VER +#if _MSC_VER > 1800 +#pragma warning(disable : 4464) /* relative include path contains '..' */ +#endif +#pragma warning(disable : 4996) /* The POSIX name is deprecated... */ +#endif /* _MSC_VER (warnings) */ + +#define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#include "../elements/internals.h" + +#if defined(_WIN32) || defined(_WIN64) +#include "wingetopt.h" + +static volatile BOOL user_break; +static BOOL WINAPI ConsoleBreakHandlerRoutine(DWORD dwCtrlType) { + (void)dwCtrlType; + user_break = true; + return true; +} + +#else /* WINDOWS */ + +static volatile sig_atomic_t user_break; +static void signal_handler(int sig) { + (void)sig; + user_break = 1; +} + +#endif /* !WINDOWS */ + +int main(int argc, char *argv[]) { + int rc; + MDBX_env *env = NULL; + const char *progname = argv[0], *act; + unsigned flags = MDBX_RDONLY; + unsigned cpflags = 0; + bool quiet = false; + + for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) { + if (argv[1][1] == 'n' && argv[1][2] == '\0') + flags |= MDBX_NOSUBDIR; + else if (argv[1][1] == 'c' && argv[1][2] == '\0') + cpflags |= MDBX_CP_COMPACT; + else if (argv[1][1] == 'q' && argv[1][2] == '\0') + quiet = true; + else if (argv[1][1] == 'V' && argv[1][2] == '\0') { + printf("mdbx_copy version %d.%d.%d.%d\n" + " - source: %s %s, commit %s, tree %s\n" + " - anchor: %s\n" + " - build: %s for %s by %s\n" + " - flags: %s\n" + " - options: %s\n", + mdbx_version.major, mdbx_version.minor, mdbx_version.release, + mdbx_version.revision, mdbx_version.git.describe, + mdbx_version.git.datetime, mdbx_version.git.commit, + mdbx_version.git.tree, mdbx_sourcery_anchor, mdbx_build.datetime, + mdbx_build.target, mdbx_build.compiler, mdbx_build.flags, + mdbx_build.options); + return EXIT_SUCCESS; + } else + argc = 0; + } + + if (argc < 2 || argc > 3) { + fprintf(stderr, "usage: %s [-V] [-q] [-c] [-n] srcpath [dstpath]\n", + progname); + exit(EXIT_FAILURE); + } + +#if defined(_WIN32) || defined(_WIN64) + SetConsoleCtrlHandler(ConsoleBreakHandlerRoutine, true); +#else +#ifdef SIGPIPE + signal(SIGPIPE, signal_handler); +#endif +#ifdef SIGHUP + signal(SIGHUP, signal_handler); +#endif + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); +#endif /* !WINDOWS */ + + if (!quiet) { + fprintf((argc == 2) ? stderr : stdout, + "mdbx_copy %s (%s, T-%s)\nRunning for copy %s to %s...\n", + mdbx_version.git.describe, mdbx_version.git.datetime, + mdbx_version.git.tree, argv[1], (argc == 2) ? "stdout" : argv[2]); + fflush(NULL); + } + + act = "opening environment"; + rc = mdbx_env_create(&env); + if (rc == MDBX_SUCCESS) { + rc = mdbx_env_open(env, argv[1], flags, 0640); + } + if (rc == MDBX_SUCCESS) { + act = "copying"; + if (argc == 2) { + mdbx_filehandle_t fd; +#if defined(_WIN32) || defined(_WIN64) + fd = GetStdHandle(STD_OUTPUT_HANDLE); +#else + fd = fileno(stdout); +#endif + rc = mdbx_env_copy2fd(env, fd, cpflags); + } else + rc = mdbx_env_copy(env, argv[2], cpflags); + } + if (rc) + fprintf(stderr, "%s: %s failed, error %d (%s)\n", progname, act, rc, + mdbx_strerror(rc)); + mdbx_env_close(env); + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/src/tools/mdbx_dump.c b/contrib/db/libmdbx/src/tools/mdbx_dump.c new file mode 100644 index 00000000..20cd3762 --- /dev/null +++ b/contrib/db/libmdbx/src/tools/mdbx_dump.c @@ -0,0 +1,352 @@ +/* mdbx_dump.c - memory-mapped database dump tool */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +#ifdef _MSC_VER +#if _MSC_VER > 1800 +#pragma warning(disable : 4464) /* relative include path contains '..' */ +#endif +#pragma warning(disable : 4996) /* The POSIX name is deprecated... */ +#endif /* _MSC_VER (warnings) */ + +#define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#include "../elements/internals.h" + +#include + +#define PRINT 1 +static int mode; + +typedef struct flagbit { + int bit; + char *name; +} flagbit; + +flagbit dbflags[] = {{MDBX_REVERSEKEY, "reversekey"}, + {MDBX_DUPSORT, "dupsort"}, + {MDBX_INTEGERKEY, "integerkey"}, + {MDBX_DUPFIXED, "dupfixed"}, + {MDBX_INTEGERDUP, "integerdup"}, + {MDBX_REVERSEDUP, "reversedup"}, + {0, NULL}}; + +#if defined(_WIN32) || defined(_WIN64) +#include "wingetopt.h" + +static volatile BOOL user_break; +static BOOL WINAPI ConsoleBreakHandlerRoutine(DWORD dwCtrlType) { + (void)dwCtrlType; + user_break = true; + return true; +} + +#else /* WINDOWS */ + +static volatile sig_atomic_t user_break; +static void signal_handler(int sig) { + (void)sig; + user_break = 1; +} + +#endif /* !WINDOWS */ + +static const char hexc[] = "0123456789abcdef"; + +static void dumpbyte(unsigned char c) { + putchar(hexc[c >> 4]); + putchar(hexc[c & 0xf]); +} + +static void text(MDBX_val *v) { + unsigned char *c, *end; + + putchar(' '); + c = v->iov_base; + end = c + v->iov_len; + while (c < end) { + if (isprint(*c) && *c != '\\') { + putchar(*c); + } else { + putchar('\\'); + dumpbyte(*c); + } + c++; + } + putchar('\n'); +} + +static void dumpval(MDBX_val *v) { + unsigned char *c, *end; + + putchar(' '); + c = v->iov_base; + end = c + v->iov_len; + while (c < end) { + dumpbyte(*c++); + } + putchar('\n'); +} + +/* Dump in BDB-compatible format */ +static int dumpit(MDBX_txn *txn, MDBX_dbi dbi, char *name) { + MDBX_cursor *mc; + MDBX_stat ms; + MDBX_val key, data; + MDBX_envinfo info; + unsigned int flags; + int rc, i; + + rc = mdbx_dbi_flags(txn, dbi, &flags); + if (rc) + return rc; + + rc = mdbx_dbi_stat(txn, dbi, &ms, sizeof(ms)); + if (rc) + return rc; + + rc = mdbx_env_info(mdbx_txn_env(txn), &info, sizeof(info)); + if (rc) + return rc; + + printf("VERSION=3\n"); + printf("format=%s\n", mode & PRINT ? "print" : "bytevalue"); + if (name) + printf("database=%s\n", name); + printf("type=btree\n"); + printf("mapsize=%" PRIu64 "\n", info.mi_mapsize); + printf("maxreaders=%u\n", info.mi_maxreaders); + + for (i = 0; dbflags[i].bit; i++) + if (flags & dbflags[i].bit) + printf("%s=1\n", dbflags[i].name); + + printf("db_pagesize=%d\n", ms.ms_psize); + printf("HEADER=END\n"); + + rc = mdbx_cursor_open(txn, dbi, &mc); + if (rc) + return rc; + + while ((rc = mdbx_cursor_get(mc, &key, &data, MDBX_NEXT)) == MDBX_SUCCESS) { + if (user_break) { + rc = MDBX_EINTR; + break; + } + if (mode & PRINT) { + text(&key); + text(&data); + } else { + dumpval(&key); + dumpval(&data); + } + } + printf("DATA=END\n"); + if (rc == MDBX_NOTFOUND) + rc = MDBX_SUCCESS; + + return rc; +} + +static void usage(char *prog) { + fprintf(stderr, + "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", + prog); + exit(EXIT_FAILURE); +} + +int main(int argc, char *argv[]) { + int i, rc; + MDBX_env *env; + MDBX_txn *txn; + MDBX_dbi dbi; + char *prog = argv[0]; + char *envname; + char *subname = NULL; + int alldbs = 0, envflags = 0, list = 0; + + if (argc < 2) + usage(prog); + + /* -a: dump main DB and all subDBs + * -s: dump only the named subDB + * -n: use NOSUBDIR flag on env_open + * -p: use printable characters + * -f: write to file instead of stdout + * -V: print version and exit + * (default) dump only the main DB + */ + while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) { + switch (i) { + case 'V': + printf("mdbx_dump version %d.%d.%d.%d\n" + " - source: %s %s, commit %s, tree %s\n" + " - anchor: %s\n" + " - build: %s for %s by %s\n" + " - flags: %s\n" + " - options: %s\n", + mdbx_version.major, mdbx_version.minor, mdbx_version.release, + mdbx_version.revision, mdbx_version.git.describe, + mdbx_version.git.datetime, mdbx_version.git.commit, + mdbx_version.git.tree, mdbx_sourcery_anchor, mdbx_build.datetime, + mdbx_build.target, mdbx_build.compiler, mdbx_build.flags, + mdbx_build.options); + return EXIT_SUCCESS; + case 'l': + list = 1; + /*FALLTHROUGH*/; + __fallthrough; + case 'a': + if (subname) + usage(prog); + alldbs++; + break; + case 'f': + if (freopen(optarg, "w", stdout) == NULL) { + fprintf(stderr, "%s: %s: reopen: %s\n", prog, optarg, + mdbx_strerror(errno)); + exit(EXIT_FAILURE); + } + break; + case 'n': + envflags |= MDBX_NOSUBDIR; + break; + case 'p': + mode |= PRINT; + break; + case 's': + if (alldbs) + usage(prog); + subname = optarg; + break; + default: + usage(prog); + } + } + + if (optind != argc - 1) + usage(prog); + +#if defined(_WIN32) || defined(_WIN64) + SetConsoleCtrlHandler(ConsoleBreakHandlerRoutine, true); +#else +#ifdef SIGPIPE + signal(SIGPIPE, signal_handler); +#endif +#ifdef SIGHUP + signal(SIGHUP, signal_handler); +#endif + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); +#endif /* !WINDOWS */ + + envname = argv[optind]; + printf("mdbx_dump %s (%s, T-%s)\nRunning for %s...\n", + mdbx_version.git.describe, mdbx_version.git.datetime, + mdbx_version.git.tree, envname); + fflush(NULL); + + rc = mdbx_env_create(&env); + if (rc) { + fprintf(stderr, "mdbx_env_create failed, error %d %s\n", rc, + mdbx_strerror(rc)); + return EXIT_FAILURE; + } + + if (alldbs || subname) { + mdbx_env_set_maxdbs(env, 2); + } + + rc = mdbx_env_open(env, envname, envflags | MDBX_RDONLY, 0664); + if (rc) { + fprintf(stderr, "mdbx_env_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + + rc = mdbx_txn_begin(env, NULL, MDBX_RDONLY, &txn); + if (rc) { + fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + + rc = mdbx_dbi_open(txn, subname, 0, &dbi); + if (rc) { + fprintf(stderr, "mdbx_open failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto txn_abort; + } + + if (alldbs) { + MDBX_cursor *cursor; + MDBX_val key; + int count = 0; + + rc = mdbx_cursor_open(txn, dbi, &cursor); + if (rc) { + fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + while ((rc = mdbx_cursor_get(cursor, &key, NULL, MDBX_NEXT_NODUP)) == 0) { + if (user_break) { + rc = MDBX_EINTR; + break; + } + char *str; + MDBX_dbi db2; + if (memchr(key.iov_base, '\0', key.iov_len)) + continue; + count++; + str = mdbx_malloc(key.iov_len + 1); + memcpy(str, key.iov_base, key.iov_len); + str[key.iov_len] = '\0'; + rc = mdbx_dbi_open(txn, str, 0, &db2); + if (rc == MDBX_SUCCESS) { + if (list) { + printf("%s\n", str); + list++; + } else { + rc = dumpit(txn, db2, str); + if (rc) + break; + } + mdbx_dbi_close(env, db2); + } + mdbx_free(str); + if (rc) + continue; + } + mdbx_cursor_close(cursor); + if (!count) { + fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, + envname); + rc = MDBX_NOTFOUND; + } else if (rc == MDBX_INCOMPATIBLE) { + /* LY: the record it not a named sub-db. */ + rc = MDBX_SUCCESS; + } + } else { + rc = dumpit(txn, dbi, subname); + } + if (rc && rc != MDBX_NOTFOUND) + fprintf(stderr, "%s: %s: %s\n", prog, envname, mdbx_strerror(rc)); + + mdbx_dbi_close(env, dbi); +txn_abort: + mdbx_txn_abort(txn); +env_close: + mdbx_env_close(env); + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/src/tools/mdbx_load.c b/contrib/db/libmdbx/src/tools/mdbx_load.c new file mode 100644 index 00000000..00bfa87e --- /dev/null +++ b/contrib/db/libmdbx/src/tools/mdbx_load.c @@ -0,0 +1,567 @@ +/* mdbx_load.c - memory-mapped database load tool */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +#ifdef _MSC_VER +#if _MSC_VER > 1800 +#pragma warning(disable : 4464) /* relative include path contains '..' */ +#endif +#pragma warning(disable : 4996) /* The POSIX name is deprecated... */ +#endif /* _MSC_VER (warnings) */ + +#define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#include "../elements/internals.h" + +#include + +#if defined(_WIN32) || defined(_WIN64) +#include "wingetopt.h" + +static volatile BOOL user_break; +static BOOL WINAPI ConsoleBreakHandlerRoutine(DWORD dwCtrlType) { + (void)dwCtrlType; + user_break = true; + return true; +} + +#else /* WINDOWS */ + +static volatile sig_atomic_t user_break; +static void signal_handler(int sig) { + (void)sig; + user_break = 1; +} + +#endif /* !WINDOWS */ + +#define PRINT 1 +#define NOHDR 2 +static int mode; + +static char *subname = NULL; +static size_t lineno; +static int version; + +static int dbi_flags; +static char *prog; +static int Eof; + +static MDBX_envinfo envinfo; +static MDBX_val kbuf, dbuf; +static MDBX_val k0buf; + +#define STRLENOF(s) (sizeof(s) - 1) + +typedef struct flagbit { + int bit; + char *name; + int len; +} flagbit; + +#define S(s) s, STRLENOF(s) + +flagbit dbflags[] = {{MDBX_REVERSEKEY, S("reversekey")}, + {MDBX_DUPSORT, S("dupsort")}, + {MDBX_INTEGERKEY, S("integerkey")}, + {MDBX_DUPFIXED, S("dupfixed")}, + {MDBX_INTEGERDUP, S("integerdup")}, + {MDBX_REVERSEDUP, S("reversedup")}, + {0, NULL, 0}}; + +static void readhdr(void) { + char *ptr; + + dbi_flags = 0; + while (fgets(dbuf.iov_base, (int)dbuf.iov_len, stdin) != NULL) { + lineno++; + if (!strncmp(dbuf.iov_base, "db_pagesize=", STRLENOF("db_pagesize=")) || + !strncmp(dbuf.iov_base, "duplicates=", STRLENOF("duplicates="))) { + /* LY: silently ignore information fields. */ + continue; + } else if (!strncmp(dbuf.iov_base, "VERSION=", STRLENOF("VERSION="))) { + version = atoi((char *)dbuf.iov_base + STRLENOF("VERSION=")); + if (version > 3) { + fprintf(stderr, "%s: line %" PRIiSIZE ": unsupported VERSION %d\n", + prog, lineno, version); + exit(EXIT_FAILURE); + } + } else if (!strncmp(dbuf.iov_base, "HEADER=END", STRLENOF("HEADER=END"))) { + break; + } else if (!strncmp(dbuf.iov_base, "format=", STRLENOF("format="))) { + if (!strncmp((char *)dbuf.iov_base + STRLENOF("FORMAT="), "print", + STRLENOF("print"))) + mode |= PRINT; + else if (strncmp((char *)dbuf.iov_base + STRLENOF("FORMAT="), "bytevalue", + STRLENOF("bytevalue"))) { + fprintf(stderr, "%s: line %" PRIiSIZE ": unsupported FORMAT %s\n", prog, + lineno, (char *)dbuf.iov_base + STRLENOF("FORMAT=")); + exit(EXIT_FAILURE); + } + } else if (!strncmp(dbuf.iov_base, "database=", STRLENOF("database="))) { + ptr = memchr(dbuf.iov_base, '\n', dbuf.iov_len); + if (ptr) + *ptr = '\0'; + if (subname) + mdbx_free(subname); + subname = mdbx_strdup((char *)dbuf.iov_base + STRLENOF("database=")); + } else if (!strncmp(dbuf.iov_base, "type=", STRLENOF("type="))) { + if (strncmp((char *)dbuf.iov_base + STRLENOF("type="), "btree", + STRLENOF("btree"))) { + fprintf(stderr, "%s: line %" PRIiSIZE ": unsupported type %s\n", prog, + lineno, (char *)dbuf.iov_base + STRLENOF("type=")); + exit(EXIT_FAILURE); + } + } else if (!strncmp(dbuf.iov_base, "mapaddr=", STRLENOF("mapaddr="))) { + int i; + ptr = memchr(dbuf.iov_base, '\n', dbuf.iov_len); + if (ptr) + *ptr = '\0'; + void *unused; + i = sscanf((char *)dbuf.iov_base + STRLENOF("mapaddr="), "%p", &unused); + if (i != 1) { + fprintf(stderr, "%s: line %" PRIiSIZE ": invalid mapaddr %s\n", prog, + lineno, (char *)dbuf.iov_base + STRLENOF("mapaddr=")); + exit(EXIT_FAILURE); + } + } else if (!strncmp(dbuf.iov_base, "mapsize=", STRLENOF("mapsize="))) { + int i; + ptr = memchr(dbuf.iov_base, '\n', dbuf.iov_len); + if (ptr) + *ptr = '\0'; + i = sscanf((char *)dbuf.iov_base + STRLENOF("mapsize="), "%" PRIu64, + &envinfo.mi_mapsize); + if (i != 1) { + fprintf(stderr, "%s: line %" PRIiSIZE ": invalid mapsize %s\n", prog, + lineno, (char *)dbuf.iov_base + STRLENOF("mapsize=")); + exit(EXIT_FAILURE); + } + } else if (!strncmp(dbuf.iov_base, + "maxreaders=", STRLENOF("maxreaders="))) { + int i; + ptr = memchr(dbuf.iov_base, '\n', dbuf.iov_len); + if (ptr) + *ptr = '\0'; + i = sscanf((char *)dbuf.iov_base + STRLENOF("maxreaders="), "%u", + &envinfo.mi_maxreaders); + if (i != 1) { + fprintf(stderr, "%s: line %" PRIiSIZE ": invalid maxreaders %s\n", prog, + lineno, (char *)dbuf.iov_base + STRLENOF("maxreaders=")); + exit(EXIT_FAILURE); + } + } else { + int i; + for (i = 0; dbflags[i].bit; i++) { + if (!strncmp(dbuf.iov_base, dbflags[i].name, dbflags[i].len) && + ((char *)dbuf.iov_base)[dbflags[i].len] == '=') { + if (((char *)dbuf.iov_base)[dbflags[i].len + 1] == '1') + dbi_flags |= dbflags[i].bit; + break; + } + } + if (!dbflags[i].bit) { + ptr = memchr(dbuf.iov_base, '=', dbuf.iov_len); + if (!ptr) { + fprintf(stderr, "%s: line %" PRIiSIZE ": unexpected format\n", prog, + lineno); + exit(EXIT_FAILURE); + } else { + *ptr = '\0'; + fprintf(stderr, + "%s: line %" PRIiSIZE ": unrecognized keyword ignored: %s\n", + prog, lineno, (char *)dbuf.iov_base); + } + } + } + } +} + +static void badend(void) { + fprintf(stderr, "%s: line %" PRIiSIZE ": unexpected end of input\n", prog, + lineno); +} + +static int unhex(unsigned char *c2) { + int x, c; + x = *c2++ & 0x4f; + if (x & 0x40) + x -= 55; + c = x << 4; + x = *c2 & 0x4f; + if (x & 0x40) + x -= 55; + c |= x; + return c; +} + +static int readline(MDBX_val *out, MDBX_val *buf) { + unsigned char *c1, *c2, *end; + size_t len, l2; + int c; + + if (!(mode & NOHDR)) { + c = fgetc(stdin); + if (c == EOF) { + Eof = 1; + return EOF; + } + if (c != ' ') { + lineno++; + if (fgets(buf->iov_base, (int)buf->iov_len, stdin) == NULL) { + badend: + Eof = 1; + badend(); + return EOF; + } + if (c == 'D' && !strncmp(buf->iov_base, "ATA=END", STRLENOF("ATA=END"))) + return EOF; + goto badend; + } + } + if (fgets(buf->iov_base, (int)buf->iov_len, stdin) == NULL) { + Eof = 1; + return EOF; + } + lineno++; + + c1 = buf->iov_base; + len = strlen((char *)c1); + l2 = len; + + /* Is buffer too short? */ + while (c1[len - 1] != '\n') { + buf->iov_base = mdbx_realloc(buf->iov_base, buf->iov_len * 2); + if (!buf->iov_base) { + Eof = 1; + fprintf(stderr, "%s: line %" PRIiSIZE ": out of memory, line too long\n", + prog, lineno); + return EOF; + } + c1 = buf->iov_base; + c1 += l2; + if (fgets((char *)c1, (int)buf->iov_len + 1, stdin) == NULL) { + Eof = 1; + badend(); + return EOF; + } + buf->iov_len *= 2; + len = strlen((char *)c1); + l2 += len; + } + c1 = c2 = buf->iov_base; + len = l2; + c1[--len] = '\0'; + end = c1 + len; + + if (mode & PRINT) { + while (c2 < end) { + if (unlikely(*c2 == '\\')) { + if (c2[1] == '\\') { + *c1++ = '\\'; + } else { + if (c2 + 3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) { + Eof = 1; + badend(); + return EOF; + } + *c1++ = (char)unhex(++c2); + } + c2 += 2; + } else { + /* copies are redundant when no escapes were used */ + *c1++ = *c2++; + } + } + } else { + /* odd length not allowed */ + if (len & 1) { + Eof = 1; + badend(); + return EOF; + } + while (c2 < end) { + if (!isxdigit(*c2) || !isxdigit(c2[1])) { + Eof = 1; + badend(); + return EOF; + } + *c1++ = (char)unhex(c2); + c2 += 2; + } + } + c2 = out->iov_base = buf->iov_base; + out->iov_len = c1 - c2; + + return 0; +} + +static void usage(void) { + fprintf(stderr, + "usage: %s [-V] [-a] [-f input] [-n] [-s name] [-N] [-T] dbpath\n", + prog); + exit(EXIT_FAILURE); +} + +static int anyway_greater(const MDBX_val *a, const MDBX_val *b) { + (void)a; + (void)b; + return 1; +} + +int main(int argc, char *argv[]) { + int i, rc; + MDBX_env *env = NULL; + MDBX_txn *txn = NULL; + MDBX_cursor *mc = NULL; + MDBX_dbi dbi; + char *envname = NULL; + int envflags = MDBX_UTTERLY_NOSYNC, putflags = 0; + int append = 0; + MDBX_val prevk; + + prog = argv[0]; + if (argc < 2) + usage(); + + /* -a: append records in input order + * -f: load file instead of stdin + * -n: use NOSUBDIR flag on env_open + * -s: load into named subDB + * -N: use NOOVERWRITE on puts + * -T: read plaintext + * -V: print version and exit + */ + while ((i = getopt(argc, argv, "af:ns:NTV")) != EOF) { + switch (i) { + case 'V': + printf("mdbx_load version %d.%d.%d.%d\n" + " - source: %s %s, commit %s, tree %s\n" + " - anchor: %s\n" + " - build: %s for %s by %s\n" + " - flags: %s\n" + " - options: %s\n", + mdbx_version.major, mdbx_version.minor, mdbx_version.release, + mdbx_version.revision, mdbx_version.git.describe, + mdbx_version.git.datetime, mdbx_version.git.commit, + mdbx_version.git.tree, mdbx_sourcery_anchor, mdbx_build.datetime, + mdbx_build.target, mdbx_build.compiler, mdbx_build.flags, + mdbx_build.options); + return EXIT_SUCCESS; + case 'a': + append = 1; + break; + case 'f': + if (freopen(optarg, "r", stdin) == NULL) { + fprintf(stderr, "%s: %s: reopen: %s\n", prog, optarg, + mdbx_strerror(errno)); + exit(EXIT_FAILURE); + } + break; + case 'n': + envflags |= MDBX_NOSUBDIR; + break; + case 's': + subname = mdbx_strdup(optarg); + break; + case 'N': + putflags = MDBX_NOOVERWRITE | MDBX_NODUPDATA; + break; + case 'T': + mode |= NOHDR | PRINT; + break; + default: + usage(); + } + } + + if (optind != argc - 1) + usage(); + +#if defined(_WIN32) || defined(_WIN64) + SetConsoleCtrlHandler(ConsoleBreakHandlerRoutine, true); +#else +#ifdef SIGPIPE + signal(SIGPIPE, signal_handler); +#endif +#ifdef SIGHUP + signal(SIGHUP, signal_handler); +#endif + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); +#endif /* !WINDOWS */ + + envname = argv[optind]; + printf("mdbx_load %s (%s, T-%s)\nRunning for %s...\n", + mdbx_version.git.describe, mdbx_version.git.datetime, + mdbx_version.git.tree, envname); + fflush(NULL); + + dbuf.iov_len = 4096; + dbuf.iov_base = mdbx_malloc(dbuf.iov_len); + + /* read first header for mapsize= */ + if (!(mode & NOHDR)) + readhdr(); + + rc = mdbx_env_create(&env); + if (rc) { + fprintf(stderr, "mdbx_env_create failed, error %d %s\n", rc, + mdbx_strerror(rc)); + return EXIT_FAILURE; + } + + mdbx_env_set_maxdbs(env, 2); + + if (envinfo.mi_maxreaders) + mdbx_env_set_maxreaders(env, envinfo.mi_maxreaders); + + if (envinfo.mi_mapsize) { + if (envinfo.mi_mapsize > SIZE_MAX) { + fprintf(stderr, "mdbx_env_set_mapsize failed, error %d %s\n", rc, + mdbx_strerror(MDBX_TOO_LARGE)); + return EXIT_FAILURE; + } + mdbx_env_set_mapsize(env, (size_t)envinfo.mi_mapsize); + } + +#ifdef MDBX_FIXEDMAP + if (info.mi_mapaddr) + envflags |= MDBX_FIXEDMAP; +#endif + + rc = mdbx_env_open(env, envname, envflags, 0664); + if (rc) { + fprintf(stderr, "mdbx_env_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + + kbuf.iov_len = mdbx_env_get_maxkeysize(env); + if (kbuf.iov_len >= SIZE_MAX / 4) { + fprintf(stderr, "mdbx_env_get_maxkeysize failed, returns %zu\n", + kbuf.iov_len); + goto env_close; + } + kbuf.iov_len = (kbuf.iov_len + 1) * 2; + kbuf.iov_base = malloc(kbuf.iov_len * 2); + k0buf.iov_len = kbuf.iov_len; + k0buf.iov_base = (char *)kbuf.iov_base + kbuf.iov_len; + prevk.iov_base = k0buf.iov_base; + + while (!Eof) { + if (user_break) { + rc = MDBX_EINTR; + break; + } + + rc = mdbx_txn_begin(env, NULL, 0, &txn); + if (rc) { + fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + + rc = mdbx_dbi_open_ex(txn, subname, dbi_flags | MDBX_CREATE, &dbi, + append ? anyway_greater : NULL, + append ? anyway_greater : NULL); + if (rc) { + fprintf(stderr, "mdbx_open failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto txn_abort; + } + + rc = mdbx_cursor_open(txn, dbi, &mc); + if (rc) { + fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + + int batch = 0; + prevk.iov_len = 0; + while (1) { + MDBX_val key; + rc = readline(&key, &kbuf); + if (rc) /* rc == EOF */ + break; + + MDBX_val data; + rc = readline(&data, &dbuf); + if (rc) { + fprintf(stderr, "%s: line %" PRIiSIZE ": failed to read key value\n", + prog, lineno); + goto txn_abort; + } + + int appflag = 0; + if (append) { + appflag = MDBX_APPEND; + if (dbi_flags & MDBX_DUPSORT) { + if (prevk.iov_len == key.iov_len && + memcmp(prevk.iov_base, key.iov_base, key.iov_len) == 0) + appflag = MDBX_APPEND | MDBX_APPENDDUP; + else + memcpy(prevk.iov_base, key.iov_base, prevk.iov_len = key.iov_len); + } + } + rc = mdbx_cursor_put(mc, &key, &data, putflags | appflag); + if (rc == MDBX_KEYEXIST && putflags) + continue; + if (rc) { + fprintf(stderr, "mdbx_cursor_put failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + batch++; + if (batch == 100) { + rc = mdbx_txn_commit(txn); + if (rc) { + fprintf(stderr, "%s: line %" PRIiSIZE ": txn_commit: %s\n", prog, + lineno, mdbx_strerror(rc)); + goto env_close; + } + rc = mdbx_txn_begin(env, NULL, 0, &txn); + if (rc) { + fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + rc = mdbx_cursor_open(txn, dbi, &mc); + if (rc) { + fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + batch = 0; + } + } + rc = mdbx_txn_commit(txn); + txn = NULL; + if (rc) { + fprintf(stderr, "%s: line %" PRIiSIZE ": txn_commit: %s\n", prog, lineno, + mdbx_strerror(rc)); + goto env_close; + } + mdbx_dbi_close(env, dbi); + + /* try read next header */ + if (!(mode & NOHDR)) + readhdr(); + } + +txn_abort: + mdbx_txn_abort(txn); +env_close: + mdbx_env_close(env); + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/src/tools/mdbx_stat.c b/contrib/db/libmdbx/src/tools/mdbx_stat.c new file mode 100644 index 00000000..04c1fb93 --- /dev/null +++ b/contrib/db/libmdbx/src/tools/mdbx_stat.c @@ -0,0 +1,436 @@ +/* mdbx_stat.c - memory-mapped database status tool */ + +/* + * Copyright 2015-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . */ + +#ifdef _MSC_VER +#if _MSC_VER > 1800 +#pragma warning(disable : 4464) /* relative include path contains '..' */ +#endif +#pragma warning(disable : 4996) /* The POSIX name is deprecated... */ +#endif /* _MSC_VER (warnings) */ + +#define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#include "../elements/internals.h" + +#if defined(_WIN32) || defined(_WIN64) +#include "wingetopt.h" + +static volatile BOOL user_break; +static BOOL WINAPI ConsoleBreakHandlerRoutine(DWORD dwCtrlType) { + (void)dwCtrlType; + user_break = true; + return true; +} + +#else /* WINDOWS */ + +static volatile sig_atomic_t user_break; +static void signal_handler(int sig) { + (void)sig; + user_break = 1; +} + +#endif /* !WINDOWS */ + +static void prstat(MDBX_stat *ms) { + printf(" Pagesize: %u\n", ms->ms_psize); + printf(" Tree depth: %u\n", ms->ms_depth); + printf(" Branch pages: %" PRIu64 "\n", ms->ms_branch_pages); + printf(" Leaf pages: %" PRIu64 "\n", ms->ms_leaf_pages); + printf(" Overflow pages: %" PRIu64 "\n", ms->ms_overflow_pages); + printf(" Entries: %" PRIu64 "\n", ms->ms_entries); +} + +static void usage(char *prog) { + fprintf(stderr, + "usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n", + prog); + exit(EXIT_FAILURE); +} + +static int reader_list_func(void *ctx, int num, int slot, mdbx_pid_t pid, + mdbx_tid_t thread, uint64_t txnid, uint64_t lag, + size_t bytes_used, size_t bytes_retained) { + (void)ctx; + if (num == 1) + printf("Reader Table Status\n" + " #\tslot\t%6s %*s %20s %10s %13s %13s\n", + "pid", (int)sizeof(size_t) * 2, "thread", "txnid", "lag", "used", + "retained"); + + printf(" %3d)\t[%d]\t%6" PRIdSIZE " %*" PRIxSIZE, num, slot, (size_t)pid, + (int)sizeof(size_t) * 2, (size_t)thread); + if (txnid) + printf(" %20" PRIu64 " %10" PRIu64 " %12.1fM %12.1fM\n", txnid, lag, + bytes_used / 1048576.0, bytes_retained / 1048576.0); + else + printf(" %20s %10s %13s %13s\n", "-", "0", "0", "0"); + + return user_break ? MDBX_RESULT_TRUE : MDBX_RESULT_FALSE; +} + +int main(int argc, char *argv[]) { + int o, rc; + MDBX_env *env; + MDBX_txn *txn; + MDBX_dbi dbi; + MDBX_stat mst; + MDBX_envinfo mei; + char *prog = argv[0]; + char *envname; + char *subname = NULL; + int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0; + + if (argc < 2) + usage(prog); + + /* -a: print stat of main DB and all subDBs + * -s: print stat of only the named subDB + * -e: print env info + * -f: print freelist info + * -r: print reader info + * -n: use NOSUBDIR flag on env_open + * -V: print version and exit + * (default) print stat of only the main DB + */ + while ((o = getopt(argc, argv, "Vaefnrs:")) != EOF) { + switch (o) { + case 'V': + printf("mdbx_stat version %d.%d.%d.%d\n" + " - source: %s %s, commit %s, tree %s\n" + " - anchor: %s\n" + " - build: %s for %s by %s\n" + " - flags: %s\n" + " - options: %s\n", + mdbx_version.major, mdbx_version.minor, mdbx_version.release, + mdbx_version.revision, mdbx_version.git.describe, + mdbx_version.git.datetime, mdbx_version.git.commit, + mdbx_version.git.tree, mdbx_sourcery_anchor, mdbx_build.datetime, + mdbx_build.target, mdbx_build.compiler, mdbx_build.flags, + mdbx_build.options); + return EXIT_SUCCESS; + case 'a': + if (subname) + usage(prog); + alldbs++; + break; + case 'e': + envinfo++; + break; + case 'f': + freinfo++; + break; + case 'n': + envflags |= MDBX_NOSUBDIR; + break; + case 'r': + rdrinfo++; + break; + case 's': + if (alldbs) + usage(prog); + subname = optarg; + break; + default: + usage(prog); + } + } + + if (optind != argc - 1) + usage(prog); + +#if defined(_WIN32) || defined(_WIN64) + SetConsoleCtrlHandler(ConsoleBreakHandlerRoutine, true); +#else +#ifdef SIGPIPE + signal(SIGPIPE, signal_handler); +#endif +#ifdef SIGHUP + signal(SIGHUP, signal_handler); +#endif + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); +#endif /* !WINDOWS */ + + envname = argv[optind]; + envname = argv[optind]; + printf("mdbx_stat %s (%s, T-%s)\nRunning for %s...\n", + mdbx_version.git.describe, mdbx_version.git.datetime, + mdbx_version.git.tree, envname); + fflush(NULL); + + rc = mdbx_env_create(&env); + if (rc) { + fprintf(stderr, "mdbx_env_create failed, error %d %s\n", rc, + mdbx_strerror(rc)); + return EXIT_FAILURE; + } + + if (alldbs || subname) + mdbx_env_set_maxdbs(env, 4); + + rc = mdbx_env_open(env, envname, envflags | MDBX_RDONLY, 0664); + if (rc) { + fprintf(stderr, "mdbx_env_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + + if (envinfo || freinfo) { + (void)mdbx_env_info(env, &mei, sizeof(mei)); + } else { + /* LY: zap warnings from gcc */ + memset(&mei, 0, sizeof(mei)); + } + + if (envinfo) { + (void)mdbx_env_stat(env, &mst, sizeof(mst)); + printf("Environment Info\n"); + printf(" Pagesize: %u\n", mst.ms_psize); + if (mei.mi_geo.lower != mei.mi_geo.upper) { + printf(" Dynamic datafile: %" PRIu64 "..%" PRIu64 " bytes (+%" PRIu64 + "/-%" PRIu64 "), %" PRIu64 "..%" PRIu64 " pages (+%" PRIu64 + "/-%" PRIu64 ")\n", + mei.mi_geo.lower, mei.mi_geo.upper, mei.mi_geo.grow, + mei.mi_geo.shrink, mei.mi_geo.lower / mst.ms_psize, + mei.mi_geo.upper / mst.ms_psize, mei.mi_geo.grow / mst.ms_psize, + mei.mi_geo.shrink / mst.ms_psize); + printf(" Current datafile: %" PRIu64 " bytes, %" PRIu64 " pages\n", + mei.mi_geo.current, mei.mi_geo.current / mst.ms_psize); + } else { + printf(" Fixed datafile: %" PRIu64 " bytes, %" PRIu64 " pages\n", + mei.mi_geo.current, mei.mi_geo.current / mst.ms_psize); + } + printf(" Current mapsize: %" PRIu64 " bytes, %" PRIu64 " pages \n", + mei.mi_mapsize, mei.mi_mapsize / mst.ms_psize); + printf(" Number of pages used: %" PRIu64 "\n", mei.mi_last_pgno + 1); + printf(" Last transaction ID: %" PRIu64 "\n", mei.mi_recent_txnid); + printf(" Tail transaction ID: %" PRIu64 " (%" PRIi64 ")\n", + mei.mi_latter_reader_txnid, + mei.mi_latter_reader_txnid - mei.mi_recent_txnid); + printf(" Max readers: %u\n", mei.mi_maxreaders); + printf(" Number of readers used: %u\n", mei.mi_numreaders); + } else { + /* LY: zap warnings from gcc */ + memset(&mst, 0, sizeof(mst)); + } + + if (rdrinfo) { + rc = mdbx_reader_list(env, reader_list_func, nullptr); + if (rc == MDBX_RESULT_TRUE) + printf("Reader Table is empty\n"); + else if (rc == MDBX_SUCCESS && rdrinfo > 1) { + int dead; + rc = mdbx_reader_check(env, &dead); + if (rc == MDBX_RESULT_TRUE) { + printf(" %d stale readers cleared.\n", dead); + rc = mdbx_reader_list(env, reader_list_func, nullptr); + if (rc == MDBX_RESULT_TRUE) + printf(" Now Reader Table is empty\n"); + } else + printf(" No stale readers.\n"); + } + if (MDBX_IS_ERROR(rc)) { + fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + if (!(subname || alldbs || freinfo)) + goto env_close; + } + + rc = mdbx_txn_begin(env, NULL, MDBX_RDONLY, &txn); + if (rc) { + fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto env_close; + } + + if (freinfo) { + MDBX_cursor *cursor; + MDBX_val key, data; + pgno_t pages = 0, *iptr; + pgno_t reclaimable = 0; + + printf("Freelist Status\n"); + dbi = 0; + rc = mdbx_cursor_open(txn, dbi, &cursor); + if (rc) { + fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + rc = mdbx_dbi_stat(txn, dbi, &mst, sizeof(mst)); + if (rc) { + fprintf(stderr, "mdbx_dbi_stat failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + prstat(&mst); + while ((rc = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) == + MDBX_SUCCESS) { + if (user_break) { + rc = MDBX_EINTR; + break; + } + iptr = data.iov_base; + const pgno_t number = *iptr++; + + pages += number; + if (envinfo && mei.mi_latter_reader_txnid > *(size_t *)key.iov_base) + reclaimable += number; + + if (freinfo > 1) { + char *bad = ""; + pgno_t prev = + MDBX_PNL_ASCENDING ? NUM_METAS - 1 : (pgno_t)mei.mi_last_pgno + 1; + pgno_t span = 1; + for (unsigned i = 0; i < number; ++i) { + pgno_t pg = iptr[i]; + if (MDBX_PNL_DISORDERED(prev, pg)) + bad = " [bad sequence]"; + prev = pg; + while (i + span < number && + iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pg, span) + : pgno_sub(pg, span))) + ++span; + } + printf(" Transaction %" PRIaTXN ", %" PRIaPGNO + " pages, maxspan %" PRIaPGNO "%s\n", + *(txnid_t *)key.iov_base, number, span, bad); + if (freinfo > 2) { + for (unsigned i = 0; i < number; i += span) { + const pgno_t pg = iptr[i]; + for (span = 1; + i + span < number && + iptr[i + span] == (MDBX_PNL_ASCENDING ? pgno_add(pg, span) + : pgno_sub(pg, span)); + ++span) + ; + if (span > 1) + printf(" %9" PRIaPGNO "[%" PRIaPGNO "]\n", pg, span); + else + printf(" %9" PRIaPGNO "\n", pg); + } + } + } + } + mdbx_cursor_close(cursor); + + switch (rc) { + case MDBX_SUCCESS: + case MDBX_NOTFOUND: + break; + case MDBX_EINTR: + fprintf(stderr, "Interrupted by signal/user\n"); + goto txn_abort; + default: + fprintf(stderr, "mdbx_cursor_get failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + + if (envinfo) { + uint64_t value = mei.mi_mapsize / mst.ms_psize; + double percent = value / 100.0; + printf("Page Allocation Info\n"); + printf(" Max pages: %" PRIu64 " 100%%\n", value); + + value = mei.mi_last_pgno + 1; + printf(" Pages used: %" PRIu64 " %.1f%%\n", value, value / percent); + + value = mei.mi_mapsize / mst.ms_psize - (mei.mi_last_pgno + 1); + printf(" Remained: %" PRIu64 " %.1f%%\n", value, value / percent); + + value = mei.mi_last_pgno + 1 - pages; + printf(" Used now: %" PRIu64 " %.1f%%\n", value, value / percent); + + value = pages; + printf(" Unallocated: %" PRIu64 " %.1f%%\n", value, value / percent); + + value = pages - reclaimable; + printf(" Detained: %" PRIu64 " %.1f%%\n", value, value / percent); + + value = reclaimable; + printf(" Reclaimable: %" PRIu64 " %.1f%%\n", value, value / percent); + + value = + mei.mi_mapsize / mst.ms_psize - (mei.mi_last_pgno + 1) + reclaimable; + printf(" Available: %" PRIu64 " %.1f%%\n", value, value / percent); + } else + printf(" Free pages: %" PRIaPGNO "\n", pages); + } + + rc = mdbx_dbi_open(txn, subname, 0, &dbi); + if (rc) { + fprintf(stderr, "mdbx_open failed, error %d %s\n", rc, mdbx_strerror(rc)); + goto txn_abort; + } + + rc = mdbx_dbi_stat(txn, dbi, &mst, sizeof(mst)); + if (rc) { + fprintf(stderr, "mdbx_dbi_stat failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + printf("Status of %s\n", subname ? subname : "Main DB"); + prstat(&mst); + + if (alldbs) { + MDBX_cursor *cursor; + MDBX_val key; + + rc = mdbx_cursor_open(txn, dbi, &cursor); + if (rc) { + fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + while ((rc = mdbx_cursor_get(cursor, &key, NULL, MDBX_NEXT_NODUP)) == 0) { + char *str; + MDBX_dbi db2; + if (memchr(key.iov_base, '\0', key.iov_len)) + continue; + str = mdbx_malloc(key.iov_len + 1); + memcpy(str, key.iov_base, key.iov_len); + str[key.iov_len] = '\0'; + rc = mdbx_dbi_open(txn, str, 0, &db2); + if (rc == MDBX_SUCCESS) + printf("Status of %s\n", str); + mdbx_free(str); + if (rc) + continue; + rc = mdbx_dbi_stat(txn, db2, &mst, sizeof(mst)); + if (rc) { + fprintf(stderr, "mdbx_dbi_stat failed, error %d %s\n", rc, + mdbx_strerror(rc)); + goto txn_abort; + } + prstat(&mst); + mdbx_dbi_close(env, db2); + } + mdbx_cursor_close(cursor); + } + + if (rc == MDBX_NOTFOUND) + rc = MDBX_SUCCESS; + + mdbx_dbi_close(env, dbi); +txn_abort: + mdbx_txn_abort(txn); +env_close: + mdbx_env_close(env); + + return rc ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/src/tools/wingetopt.c b/contrib/db/libmdbx/src/tools/wingetopt.c new file mode 100644 index 00000000..7feb223f --- /dev/null +++ b/contrib/db/libmdbx/src/tools/wingetopt.c @@ -0,0 +1,95 @@ +/* + * POSIX getopt for Windows + * + * AT&T Public License + * + * Code given out at the 1985 UNIFORUM conference in Dallas. + */ + +/*----------------------------------------------------------------------------*/ +/* Microsoft compiler generates a lot of warning for self includes... */ + +#ifdef _MSC_VER +#pragma warning(push, 1) +#pragma warning(disable : 4548) /* expression before comma has no effect; \ + expected expression with side - effect */ +#pragma warning(disable : 4530) /* C++ exception handler used, but unwind \ + * semantics are not enabled. Specify /EHsc */ +#pragma warning(disable : 4577) /* 'noexcept' used with no exception handling \ + * mode specified; termination on exception is \ + * not guaranteed. Specify /EHsc */ +#if !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif +#endif /* _MSC_VER (warnings) */ + +#include "wingetopt.h" +#include +#include + +#ifdef _MSC_VER +#pragma warning(pop) +#endif +/*----------------------------------------------------------------------------*/ + +#ifndef NULL +#define NULL 0 +#endif + +#ifndef EOF +#define EOF (-1) +#endif + +#define ERR(s, c) \ + if (opterr) { \ + fputs(argv[0], stderr); \ + fputs(s, stderr); \ + fputc(c, stderr); \ + } + +int opterr = 1; +int optind = 1; +int optopt; +char *optarg; + +int getopt(int argc, char *const argv[], const char *opts) { + static int sp = 1; + int c; + const char *cp; + + if (sp == 1) { + if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0') + return EOF; + else if (strcmp(argv[optind], "--") == 0) { + optind++; + return EOF; + } + } + optopt = c = argv[optind][sp]; + if (c == ':' || (cp = strchr(opts, c)) == NULL) { + ERR(": illegal option -- ", c); + if (argv[optind][++sp] == '\0') { + optind++; + sp = 1; + } + return '?'; + } + if (*++cp == ':') { + if (argv[optind][sp + 1] != '\0') + optarg = &argv[optind++][sp + 1]; + else if (++optind >= argc) { + ERR(": option requires an argument -- ", c); + sp = 1; + return '?'; + } else + optarg = argv[optind++]; + sp = 1; + } else { + if (argv[optind][++sp] == '\0') { + sp = 1; + optind++; + } + optarg = NULL; + } + return c; +} diff --git a/contrib/db/libmdbx/src/tools/wingetopt.h b/contrib/db/libmdbx/src/tools/wingetopt.h new file mode 100644 index 00000000..d328e38c --- /dev/null +++ b/contrib/db/libmdbx/src/tools/wingetopt.h @@ -0,0 +1,30 @@ +/* + * POSIX getopt for Windows + * + * AT&T Public License + * + * Code given out at the 1985 UNIFORUM conference in Dallas. + */ + +#ifndef _WINGETOPT_H_ +#define _WINGETOPT_H_ + +/* Bit of madness for Windows console */ +#define mdbx_strerror mdbx_strerror_ANSI2OEM +#define mdbx_strerror_r mdbx_strerror_r_ANSI2OEM + +#ifdef __cplusplus +extern "C" { +#endif + +extern int opterr; +extern int optind; +extern int optopt; +extern char *optarg; +int getopt(int argc, char *const argv[], const char *optstring); + +#ifdef __cplusplus +} +#endif + +#endif /* _GETOPT_H_ */ diff --git a/contrib/db/libmdbx/test/CMakeLists.txt b/contrib/db/libmdbx/test/CMakeLists.txt new file mode 100644 index 00000000..0cc22182 --- /dev/null +++ b/contrib/db/libmdbx/test/CMakeLists.txt @@ -0,0 +1,53 @@ +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + set(TEST_OSAL windows) +else() + set(TEST_OSAL unix) +endif() + +add_executable(mdbx_test + base.h + cases.cc + chrono.cc + chrono.h + config.cc + config.h + copy.cc + dead.cc + hill.cc + jitter.cc + keygen.cc + keygen.h + log.cc + log.h + main.cc + osal.h + osal-${TEST_OSAL}.cc + test.cc + test.h + try.cc + utils.cc + utils.h + append.cc + ttl.cc + nested.cc + ) + +set_target_properties(mdbx_test PROPERTIES + INTERPROCEDURAL_OPTIMIZATION $ + CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON) + +if(CC_HAS_FASTMATH) + target_compile_options(mdbx_test PRIVATE "-ffast-math") +endif() +if(CC_HAS_VISIBILITY AND (LTO_ENABLED OR INTERPROCEDURAL_OPTIMIZATION)) + set_target_properties(mdbx_test PROPERTIES LINK_FLAGS "-fvisibility=hidden") +endif() + +target_link_libraries(mdbx_test mdbx ${LIB_MATH} ${CMAKE_THREAD_LIBS_INIT}) +if(${CMAKE_SYSTEM_NAME} STREQUAL "Windows") + target_link_libraries(mdbx_test winmm.lib) +endif() + +if(UNIX AND NOT SUBPROJECT) + add_subdirectory(pcrf) +endif() diff --git a/contrib/db/libmdbx/test/append.cc b/contrib/db/libmdbx/test/append.cc new file mode 100644 index 00000000..9f567c7e --- /dev/null +++ b/contrib/db/libmdbx/test/append.cc @@ -0,0 +1,164 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +bool testcase_append::run() { + int err = db_open__begin__table_create_open_clean(dbi); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("append: bailout-prepare due '%s'", mdbx_strerror(err)); + return true; + } + + keyvalue_maker.setup(config.params, config.actor_id, 0 /* thread_number */); + /* LY: тест наполнения таблиц в append-режиме, + * при котором записи добавляются строго в конец (в порядке сортировки) */ + const unsigned flags = (config.params.table_flags & MDBX_DUPSORT) + ? MDBX_APPEND | MDBX_APPENDDUP + : MDBX_APPEND; + keyvalue_maker.make_ordered(); + + key = keygen::alloc(config.params.keylen_max); + data = keygen::alloc(config.params.datalen_max); + keygen::buffer last_key = keygen::alloc(config.params.keylen_max); + keygen::buffer last_data = keygen::alloc(config.params.datalen_max); + last_key->value.iov_base = last_key->bytes; + last_key->value.iov_len = 0; + last_data->value.iov_base = last_data->bytes; + last_data->value.iov_len = 0; + + simple_checksum inserted_checksum; + uint64_t inserted_number = 0; + uint64_t serial_count = 0; + + unsigned txn_nops = 0; + uint64_t commited_inserted_number = inserted_number; + simple_checksum commited_inserted_checksum = inserted_checksum; + while (should_continue()) { + const keygen::serial_t serial = serial_count; + if (!keyvalue_maker.increment(serial_count, 1)) { + // дошли до границы пространства ключей + break; + } + + log_trace("append: append-a %" PRIu64, serial); + generate_pair(serial, key, data); + int cmp = inserted_number ? mdbx_cmp(txn_guard.get(), dbi, &key->value, + &last_key->value) + : 1; + if (cmp == 0 && (config.params.table_flags & MDBX_DUPSORT)) + cmp = mdbx_dcmp(txn_guard.get(), dbi, &data->value, &last_data->value); + + err = mdbx_put(txn_guard.get(), dbi, &key->value, &data->value, flags); + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("append: bailout-insert due '%s'", mdbx_strerror(err)); + txn_end(true); + inserted_number = commited_inserted_number; + inserted_checksum = commited_inserted_checksum; + break; + } + + if (cmp > 0) { + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_put(appenda-a)", err); + + memcpy(last_key->value.iov_base, key->value.iov_base, + last_key->value.iov_len = key->value.iov_len); + memcpy(last_data->value.iov_base, data->value.iov_base, + last_data->value.iov_len = data->value.iov_len); + ++inserted_number; + inserted_checksum.push((uint32_t)inserted_number, key->value); + inserted_checksum.push(10639, data->value); + } else { + if (unlikely(err != MDBX_EKEYMISMATCH)) + failure_perror("mdbx_put(appenda-a) != MDBX_EKEYMISMATCH", err); + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("append: bailout-commit due '%s'", mdbx_strerror(err)); + inserted_number = commited_inserted_number; + inserted_checksum = commited_inserted_checksum; + break; + } + commited_inserted_number = inserted_number; + commited_inserted_checksum = inserted_checksum; + txn_nops = 0; + } + + report(1); + } + + if (txn_guard) { + err = breakable_commit(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("append: bailout-commit due '%s'", mdbx_strerror(err)); + inserted_number = commited_inserted_number; + inserted_checksum = commited_inserted_checksum; + } + } + //---------------------------------------------------------------------------- + txn_begin(true); + cursor_open(dbi); + + MDBX_val check_key, check_data; + err = + mdbx_cursor_get(cursor_guard.get(), &check_key, &check_data, MDBX_FIRST); + if (likely(inserted_number)) { + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_cursor_get(MDBX_FIRST)", err); + } + + simple_checksum read_checksum; + uint64_t read_count = 0; + while (err == MDBX_SUCCESS) { + ++read_count; + read_checksum.push((uint32_t)read_count, check_key); + read_checksum.push(10639, check_data); + + err = + mdbx_cursor_get(cursor_guard.get(), &check_key, &check_data, MDBX_NEXT); + } + + if (unlikely(err != MDBX_NOTFOUND)) + failure_perror("mdbx_cursor_get(MDBX_NEXT) != EOF", err); + + if (unlikely(read_count != inserted_number)) + failure("read_count(%" PRIu64 ") != inserted_number(%" PRIu64 ")", + read_count, inserted_number); + + if (unlikely(read_checksum.value != inserted_checksum.value)) + failure("read_checksum(0x%016" PRIu64 ") " + "!= inserted_checksum(0x%016" PRIu64 ")", + read_checksum.value, inserted_checksum.value); + + cursor_close(); + txn_end(true); + //---------------------------------------------------------------------------- + + if (dbi) { + if (config.params.drop_table && !mode_readonly()) { + txn_begin(false); + db_table_drop(dbi); + err = breakable_commit(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("append: bailout-clean due '%s'", mdbx_strerror(err)); + return true; + } + } else + db_table_close(dbi); + } + return true; +} diff --git a/contrib/db/libmdbx/test/base.h b/contrib/db/libmdbx/test/base.h new file mode 100644 index 00000000..04942818 --- /dev/null +++ b/contrib/db/libmdbx/test/base.h @@ -0,0 +1,116 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) +#ifdef _MSC_VER +#define _CRT_SECURE_NO_WARNINGS +#pragma warning(push, 1) +#pragma warning(disable : 4548) /* expression before comma has no effect; \ + expected expression with side - effect */ +#pragma warning(disable : 4530) /* C++ exception handler used, but unwind \ + semantics are not enabled. Specify /EHsc */ +#pragma warning(disable : 4577) /* 'noexcept' used with no exception handling \ + mode specified; termination on exception \ + is not guaranteed. Specify /EHsc */ +#endif /* _MSC_VER (warnings) */ + +/* If you wish to build your application for a previous Windows platform, + * include WinSDKVer.h and set the _WIN32_WINNT macro to the platform you + * wish to support before including SDKDDKVer.h. + * + * TODO: #define _WIN32_WINNT WIN32_MUSTDIE */ +#include +#endif /* WINDOWS */ + +#ifdef __APPLE__ +#define _DARWIN_C_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +#ifdef _BSD_SOURCE +#include +#endif + +#include +#include +#include // for PRId64, PRIu64 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MDBX_INTERNAL_FUNC +#define MDBX_INTERNAL_VAR extern +#define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ +#include "../mdbx.h" +#include "../src/elements/defs.h" +#include "../src/elements/osal.h" + +#if !defined(__thread) && (defined(_MSC_VER) || defined(__DMC__)) +#define __thread __declspec(thread) +#endif /* __thread */ + +#ifdef _MSC_VER +#pragma warning(pop) +#pragma warning(disable : 4201) /* nonstandard extension used : \ + nameless struct / union */ +#pragma warning(disable : 4127) /* conditional expression is constant */ +#if _MSC_VER < 1900 +#pragma warning(disable : 4510) /* default constructor could \ + not be generated */ +#pragma warning(disable : 4512) /* assignment operator could \ + not be generated */ +#pragma warning(disable : 4610) /* user-defined constructor required */ +#ifndef snprintf +#define snprintf(buffer, buffer_size, format, ...) \ + _snprintf_s(buffer, buffer_size, _TRUNCATE, format, __VA_ARGS__) +#endif +#ifndef vsnprintf +#define vsnprintf(buffer, buffer_size, format, args) \ + _vsnprintf_s(buffer, buffer_size, _TRUNCATE, format, args) +#endif +#pragma warning(disable : 4996) /* 'vsnprintf': This function or variable \ + may be unsafe */ +#endif +#endif /* _MSC_VER */ diff --git a/contrib/db/libmdbx/test/cases.cc b/contrib/db/libmdbx/test/cases.cc new file mode 100644 index 00000000..df6d4021 --- /dev/null +++ b/contrib/db/libmdbx/test/cases.cc @@ -0,0 +1,99 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +void configure_actor(unsigned &last_space_id, const actor_testcase testcase, + const char *space_id_cstr, const actor_params ¶ms) { + unsigned wait4id = 0; + + if (params.waitfor_nops) { + for (auto i = global::actors.rbegin(); i != global::actors.rend(); ++i) { + if (i->is_waitable(params.waitfor_nops)) { + if (i->signal_nops && i->signal_nops != params.waitfor_nops) + failure("Previous waitable actor (id=%u) already linked on %u-ops\n", + i->actor_id, i->signal_nops); + wait4id = i->actor_id; + i->signal_nops = params.waitfor_nops; + break; + } + } + if (!wait4id) + failure("No previous waitable actor for %u-ops\n", params.waitfor_nops); + } + + unsigned space_id = 0; + if (!space_id_cstr || strcmp(space_id_cstr, "auto") == 0) + space_id = last_space_id + 1; + else { + char *end = nullptr; + errno = 0; + space_id = strtoul(space_id_cstr, &end, 0); + if (errno) + failure_perror("Expects an integer value for space-id\n", errno); + if (end && *end) + failure("The '%s' is unexpected for space-id\n", end); + } + + if (space_id > ACTOR_ID_MAX) + failure("Invalid space-id %u\n", space_id); + last_space_id = space_id; + + log_trace("configure_actor: space %u for %s", space_id, + testcase2str(testcase)); + global::actors.emplace_back( + actor_config(testcase, params, space_id, wait4id)); + global::databases.insert(params.pathname_db); +} + +void testcase_setup(const char *casename, actor_params ¶ms, + unsigned &last_space_id) { + if (strcmp(casename, "basic") == 0) { + log_notice(">>> testcase_setup(%s)", casename); + configure_actor(last_space_id, ac_nested, nullptr, params); + configure_actor(last_space_id, ac_hill, nullptr, params); + configure_actor(last_space_id, ac_ttl, nullptr, params); + configure_actor(last_space_id, ac_copy, nullptr, params); + configure_actor(last_space_id, ac_append, nullptr, params); + configure_actor(last_space_id, ac_jitter, nullptr, params); + configure_actor(last_space_id, ac_try, nullptr, params); + configure_actor(last_space_id, ac_jitter, nullptr, params); + configure_actor(last_space_id, ac_try, nullptr, params); + log_notice("<<< testcase_setup(%s): done", casename); + } else { + failure("unknown testcase `%s`", casename); + } +} + +void keycase_setup(const char *casename, actor_params ¶ms) { + if (strcmp(casename, "random") == 0 || strcmp(casename, "prng") == 0) { + log_notice(">>> keycase_setup(%s)", casename); + params.keygen.keycase = kc_random; + // TODO + log_notice("<<< keycase_setup(%s): done", casename); + } else if (strcmp(casename, "dashes") == 0 || + strcmp(casename, "aside") == 0) { + log_notice(">>> keycase_setup(%s)", casename); + params.keygen.keycase = kc_dashes; + // TODO + log_notice("<<< keycase_setup(%s): done", casename); + } else if (strcmp(casename, "custom") == 0) { + log_notice("=== keycase_setup(%s): skip", casename); + params.keygen.keycase = kc_custom; + } else { + failure("unknown keycase `%s`", casename); + } +} + +/* TODO */ diff --git a/contrib/db/libmdbx/test/chrono.cc b/contrib/db/libmdbx/test/chrono.cc new file mode 100644 index 00000000..315d3790 --- /dev/null +++ b/contrib/db/libmdbx/test/chrono.cc @@ -0,0 +1,136 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +namespace chrono { + +#ifndef NSEC_PER_SEC +#define NSEC_PER_SEC 1000000000u +#endif /* NSEC_PER_SEC */ + +uint32_t ns2fractional(uint32_t ns) { + assert(ns < NSEC_PER_SEC); + /* LY: здесь и далее используется "длинное деление", которое + * для ясности кода оставлено как есть (без ручной оптимизации). Так как + * GCC, Clang и даже MSVC сами давно умеют конвертировать деление на + * константу в быструю reciprocal-форму. */ + return ((uint64_t)ns << 32) / NSEC_PER_SEC; +} + +uint32_t fractional2ns(uint32_t fractional) { + return (fractional * (uint64_t)NSEC_PER_SEC) >> 32; +} + +#ifndef USEC_PER_SEC +#define USEC_PER_SEC 1000000u +#endif /* USEC_PER_SEC */ +uint32_t us2fractional(uint32_t us) { + assert(us < USEC_PER_SEC); + return ((uint64_t)us << 32) / USEC_PER_SEC; +} + +uint32_t fractional2us(uint32_t fractional) { + return (fractional * (uint64_t)USEC_PER_SEC) >> 32; +} + +#ifndef MSEC_PER_SEC +#define MSEC_PER_SEC 1000u +#endif /* MSEC_PER_SEC */ +uint32_t ms2fractional(uint32_t ms) { + assert(ms < MSEC_PER_SEC); + return ((uint64_t)ms << 32) / MSEC_PER_SEC; +} + +uint32_t fractional2ms(uint32_t fractional) { + return (fractional * (uint64_t)MSEC_PER_SEC) >> 32; +} + +time from_ns(uint64_t ns) { + time result; + result.fixedpoint = ((ns / NSEC_PER_SEC) << 32) | + ns2fractional((uint32_t)(ns % NSEC_PER_SEC)); + return result; +} + +time from_us(uint64_t us) { + time result; + result.fixedpoint = ((us / USEC_PER_SEC) << 32) | + us2fractional((uint32_t)(us % USEC_PER_SEC)); + return result; +} + +time from_ms(uint64_t ms) { + time result; + result.fixedpoint = ((ms / MSEC_PER_SEC) << 32) | + ms2fractional((uint32_t)(ms % MSEC_PER_SEC)); + return result; +} + +time now_realtime() { +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + static void(WINAPI * query_time)(LPFILETIME); + if (!query_time) { + query_time = (void(WINAPI *)(LPFILETIME))GetProcAddress( + GetModuleHandle(TEXT("kernel32.dll")), + "GetSystemTimePreciseAsFileTime"); + if (!query_time) + query_time = GetSystemTimeAsFileTime; + } + + FILETIME filetime; + query_time(&filetime); + uint64_t ns100 = + (uint64_t)filetime.dwHighDateTime << 32 | filetime.dwLowDateTime; + return from_ns((ns100 - UINT64_C(116444736000000000)) * 100u); +#else + struct timespec ts; + if (unlikely(clock_gettime(CLOCK_REALTIME, &ts))) + failure_perror("clock_gettime(CLOCK_REALTIME", errno); + + return from_timespec(ts); +#endif +} + +time now_motonic() { +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + static uint64_t reciprocal; + static LARGE_INTEGER Frequency; + if (reciprocal == 0) { + if (!QueryPerformanceFrequency(&Frequency)) + failure_perror("QueryPerformanceFrequency()", GetLastError()); + reciprocal = (((UINT64_C(1) << 48) + Frequency.QuadPart / 2 + 1) / + Frequency.QuadPart); + assert(reciprocal); + } + + LARGE_INTEGER Counter; + if (!QueryPerformanceCounter(&Counter)) + failure_perror("QueryPerformanceCounter()", GetLastError()); + + time result; + result.fixedpoint = (Counter.QuadPart / Frequency.QuadPart) << 32; + uint64_t mod = Counter.QuadPart % Frequency.QuadPart; + result.fixedpoint += (mod * reciprocal) >> 16; + return result; +#else + struct timespec ts; + if (unlikely(clock_gettime(CLOCK_MONOTONIC, &ts))) + failure_perror("clock_gettime(CLOCK_MONOTONIC)", errno); + + return from_timespec(ts); +#endif +} + +} /* namespace chrono */ diff --git a/contrib/db/libmdbx/test/chrono.h b/contrib/db/libmdbx/test/chrono.h new file mode 100644 index 00000000..07cdef66 --- /dev/null +++ b/contrib/db/libmdbx/test/chrono.h @@ -0,0 +1,99 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#include "base.h" +#include "utils.h" + +namespace chrono { + +#pragma pack(push, 1) + +typedef union time { + uint64_t fixedpoint; + __anonymous_struct_extension__ struct { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ + uint32_t fractional; + union { + uint32_t utc; + uint32_t integer; + }; +#else + union { + uint32_t utc; + uint32_t integer; + }; + uint32_t fractional; +#endif + }; + + void reset() { fixedpoint = 0; } + uint32_t seconds() const { return utc; } +} time; + +#pragma pack(pop) + +uint32_t ns2fractional(uint32_t); +uint32_t fractional2ns(uint32_t); +uint32_t us2fractional(uint32_t); +uint32_t fractional2us(uint32_t); +uint32_t ms2fractional(uint32_t); +uint32_t fractional2ms(uint32_t); + +time from_ns(uint64_t us); +time from_us(uint64_t ns); +time from_ms(uint64_t ms); + +inline time from_seconds(uint64_t seconds) { + assert(seconds < UINT32_MAX); + time result; + result.fixedpoint = seconds << 32; + return result; +} + +inline time from_utc(time_t utc) { + assert(utc >= 0); + return from_seconds((uint64_t)utc); +} + +inline time infinite() { + time result; + result.fixedpoint = UINT64_MAX; + return result; +} + +#if defined(HAVE_TIMESPEC_TV_NSEC) || defined(__timespec_defined) || \ + defined(CLOCK_REALTIME) +inline time from_timespec(const struct timespec &ts) { + time result; + result.fixedpoint = + ((uint64_t)ts.tv_sec << 32) | ns2fractional((uint32_t)ts.tv_nsec); + return result; +} +#endif /* HAVE_TIMESPEC_TV_NSEC */ + +#if defined(HAVE_TIMEVAL_TV_USEC) || defined(_STRUCT_TIMEVAL) +inline time from_timeval(const struct timeval &tv) { + time result; + result.fixedpoint = + ((uint64_t)tv.tv_sec << 32) | us2fractional((uint32_t)tv.tv_usec); + return result; +} +#endif /* HAVE_TIMEVAL_TV_USEC */ + +time now_realtime(); +time now_motonic(); + +} /* namespace chrono */ diff --git a/contrib/db/libmdbx/test/config.cc b/contrib/db/libmdbx/test/config.cc new file mode 100644 index 00000000..e900d4a8 --- /dev/null +++ b/contrib/db/libmdbx/test/config.cc @@ -0,0 +1,602 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +#if defined(_MSC_VER) && !defined(strcasecmp) +#define strcasecmp(str, len) _stricmp(str, len) +#endif /* _MSC_VER && strcasecmp() */ + +namespace config { + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + const char **value, const char *default_value) { + assert(narg < argc); + const char *current = argv[narg]; + const size_t optlen = strlen(option); + + if (strncmp(current, "--", 2) || strncmp(current + 2, option, optlen)) + return false; + + if (!value) { + if (current[optlen + 2] == '=') + failure("Option '--%s' doen't accept any value\n", option); + return true; + } + + *value = nullptr; + if (current[optlen + 2] == '=') { + *value = ¤t[optlen + 3]; + return true; + } + + if (narg + 1 < argc && strncmp("--", argv[narg + 1], 2) != 0) { + *value = argv[narg + 1]; + if (strcmp(*value, "default") == 0) { + if (!default_value) + failure("Option '--%s' doen't accept default value\n", option); + *value = default_value; + } + ++narg; + return true; + } + + if (default_value) { + *value = default_value; + return true; + } + + failure("No value given for '--%s' option\n", option); +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + std::string &value, bool allow_empty) { + return parse_option(argc, argv, narg, option, value, allow_empty, + allow_empty ? "" : nullptr); +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + std::string &value, bool allow_empty, + const char *default_value) { + const char *value_cstr; + if (!parse_option(argc, argv, narg, option, &value_cstr, default_value)) + return false; + + if (!allow_empty && strlen(value_cstr) == 0) + failure("Value for option '--%s' could't be empty\n", option); + + value = value_cstr; + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &mask, const option_verb *verbs) { + const char *list; + if (!parse_option(argc, argv, narg, option, &list)) + return false; + + unsigned clear = 0; + while (*list) { + if (*list == ',' || *list == ' ' || *list == '\t') { + ++list; + continue; + } + + const char *const comma = strchr(list, ','); + const bool strikethrough = *list == '-' || *list == '~'; + if (strikethrough || *list == '+') + ++list; + else + mask = clear; + const size_t len = (comma) ? comma - list : strlen(list); + const option_verb *scan = verbs; + + while (true) { + if (!scan->verb) + failure("Unknown verb '%.*s', for option '==%s'\n", (int)len, list, + option); + if (strlen(scan->verb) == len && strncmp(list, scan->verb, len) == 0) { + mask = strikethrough ? mask & ~scan->mask : mask | scan->mask; + clear = strikethrough ? clear & ~scan->mask : clear | scan->mask; + list += len; + break; + } + ++scan; + } + } + + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + uint64_t &value, const scale_mode scale, + const uint64_t minval, const uint64_t maxval, + const uint64_t default_value) { + + const char *value_cstr; + if (!parse_option(argc, argv, narg, option, &value_cstr)) + return false; + + if (default_value && strcmp(value_cstr, "default") == 0) { + value = default_value; + return true; + } + + if (strcmp(value_cstr, "min") == 0 || strcmp(value_cstr, "minimal") == 0) { + value = minval; + return true; + } + + if (strcmp(value_cstr, "max") == 0 || strcmp(value_cstr, "maximal") == 0) { + value = maxval; + return true; + } + + char *suffix = nullptr; + errno = 0; + unsigned long long raw = strtoull(value_cstr, &suffix, 0); + if ((suffix && *suffix) || errno) { + suffix = nullptr; + errno = 0; + raw = strtoull(value_cstr, &suffix, 10); + } + if (errno) + failure("Option '--%s' expects a numeric value (%s)\n", option, + test_strerror(errno)); + + uint64_t multipler = 1; + if (suffix && *suffix) { + if (scale == no_scale) + failure("Option '--%s' doen't accepts suffixes, so '%s' is unexpected\n", + option, suffix); + if (strcmp(suffix, "K") == 0 || strcasecmp(suffix, "Kilo") == 0) + multipler = (scale == decimal) ? UINT64_C(1000) : UINT64_C(1024); + else if (strcmp(suffix, "M") == 0 || strcasecmp(suffix, "Mega") == 0) + multipler = + (scale == decimal) ? UINT64_C(1000) * 1000 : UINT64_C(1024) * 1024; + else if (strcmp(suffix, "G") == 0 || strcasecmp(suffix, "Giga") == 0) + multipler = (scale == decimal) ? UINT64_C(1000) * 1000 * 1000 + : UINT64_C(1024) * 1024 * 1024; + else if (strcmp(suffix, "T") == 0 || strcasecmp(suffix, "Tera") == 0) + multipler = (scale == decimal) ? UINT64_C(1000) * 1000 * 1000 * 1000 + : UINT64_C(1024) * 1024 * 1024 * 1024; + else if (scale == duration && + (strcmp(suffix, "s") == 0 || strcasecmp(suffix, "Seconds") == 0)) + multipler = 1; + else if (scale == duration && + (strcmp(suffix, "m") == 0 || strcasecmp(suffix, "Minutes") == 0)) + multipler = 60; + else if (scale == duration && + (strcmp(suffix, "h") == 0 || strcasecmp(suffix, "Hours") == 0)) + multipler = 3600; + else if (scale == duration && + (strcmp(suffix, "d") == 0 || strcasecmp(suffix, "Days") == 0)) + multipler = 3600 * 24; + else + failure( + "Option '--%s' expects a numeric value with Kilo/Mega/Giga/Tera %s" + "suffixes, but '%s' is unexpected\n", + option, (scale == duration) ? "or Seconds/Minutes/Hours/Days " : "", + suffix); + } + + if (raw >= UINT64_MAX / multipler) + failure("The value for option '--%s' is too huge\n", option); + + value = raw * multipler; + if (maxval && value > maxval) + failure("The maximal value for option '--%s' is %" PRIu64 "\n", option, + maxval); + if (value < minval) + failure("The minimal value for option '--%s' is %" PRIu64 "\n", option, + minval); + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &value, const scale_mode scale, + const unsigned minval, const unsigned maxval, + const unsigned default_value) { + + uint64_t huge; + if (!parse_option(argc, argv, narg, option, huge, scale, minval, maxval, + default_value)) + return false; + value = (unsigned)huge; + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + uint8_t &value, const uint8_t minval, const uint8_t maxval, + const uint8_t default_value) { + + uint64_t huge; + if (!parse_option(argc, argv, narg, option, huge, no_scale, minval, maxval, + default_value)) + return false; + value = (uint8_t)huge; + return true; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + int64_t &value, const int64_t minval, const int64_t maxval, + const int64_t default_value) { + uint64_t proxy = (uint64_t)value; + if (parse_option(argc, argv, narg, option, proxy, config::binary, + (uint64_t)minval, (uint64_t)maxval, + (uint64_t)default_value)) { + value = (int64_t)proxy; + return true; + } + return false; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + int32_t &value, const int32_t minval, const int32_t maxval, + const int32_t default_value) { + uint64_t proxy = (uint64_t)value; + if (parse_option(argc, argv, narg, option, proxy, config::binary, + (uint64_t)minval, (uint64_t)maxval, + (uint64_t)default_value)) { + value = (int32_t)proxy; + return true; + } + return false; +} + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + bool &value) { + const char *value_cstr = nullptr; + if (!parse_option(argc, argv, narg, option, &value_cstr, "yes")) { + const char *current = argv[narg]; + if (strncmp(current, "--no-", 5) == 0 && strcmp(current + 5, option) == 0) { + value = false; + return true; + } + if (strncmp(current, "--dont-", 7) == 0 && + strcmp(current + 7, option) == 0) { + value = false; + return true; + } + return false; + } + + if (!value_cstr) { + value = true; + return true; + } + + if (strcasecmp(value_cstr, "yes") == 0 || strcasecmp(value_cstr, "1") == 0) { + value = true; + return true; + } + + if (strcasecmp(value_cstr, "no") == 0 || strcasecmp(value_cstr, "0") == 0) { + value = false; + return true; + } + + failure( + "Option '--%s' expects a 'boolean' value Yes/No, so '%s' is unexpected\n", + option, value_cstr); +} + +//----------------------------------------------------------------------------- + +const struct option_verb mode_bits[] = { + {"rdonly", MDBX_RDONLY}, {"mapasync", MDBX_MAPASYNC}, + {"utterly", MDBX_UTTERLY_NOSYNC}, {"nosubdir", MDBX_NOSUBDIR}, + {"nosync", MDBX_NOSYNC}, {"nometasync", MDBX_NOMETASYNC}, + {"writemap", MDBX_WRITEMAP}, {"notls", MDBX_NOTLS}, + {"nordahead", MDBX_NORDAHEAD}, {"nomeminit", MDBX_NOMEMINIT}, + {"coalesce", MDBX_COALESCE}, {"lifo", MDBX_LIFORECLAIM}, + {"perturb", MDBX_PAGEPERTURB}, {nullptr, 0}}; + +const struct option_verb table_bits[] = { + {"key.reverse", MDBX_REVERSEKEY}, + {"key.integer", MDBX_INTEGERKEY}, + {"data.integer", MDBX_INTEGERDUP | MDBX_DUPFIXED | MDBX_DUPSORT}, + {"data.fixed", MDBX_DUPFIXED | MDBX_DUPSORT}, + {"data.reverse", MDBX_REVERSEDUP | MDBX_DUPSORT}, + {"data.dups", MDBX_DUPSORT}, + {nullptr, 0}}; + +static void dump_verbs(const char *caption, size_t bits, + const struct option_verb *verbs) { + log_verbose("%s: 0x%" PRIx64 " = ", caption, (uint64_t)bits); + + const char *comma = ""; + while (verbs->mask && bits) { + if ((bits & verbs->mask) == verbs->mask) { + logging::feed("%s%s", comma, verbs->verb); + bits -= verbs->mask; + comma = ", "; + } + ++verbs; + } + + logging::feed("%s\n", (*comma == '\0') ? "none" : ""); +} + +static void dump_duration(const char *caption, unsigned duration) { + log_verbose("%s: ", caption); + if (duration) { + if (duration > 24 * 3600) + logging::feed("%u_", duration / (24 * 3600)); + if (duration > 3600) + logging::feed("%02u:", (duration % (24 * 3600)) / 3600); + logging::feed("%02u:%02u", (duration % 3600) / 60, duration % 60); + } else { + logging::feed("INFINITE"); + } + logging::feed("\n"); +} + +void dump(const char *title) { + logging::local_suffix indent(title); + + for (auto i = global::actors.begin(); i != global::actors.end(); ++i) { + log_verbose("#%u, testcase %s, space_id/table %u\n", i->actor_id, + testcase2str(i->testcase), i->space_id); + indent.push(); + + if (i->params.loglevel) { + log_verbose("log: level %u, %s\n", i->params.loglevel, + i->params.pathname_log.empty() + ? "console" + : i->params.pathname_log.c_str()); + } + + log_verbose("database: %s, size %" PRIuPTR "[%" PRIiPTR "..%" PRIiPTR + ", %i %i, %i]\n", + i->params.pathname_db.c_str(), i->params.size_now, + i->params.size_lower, i->params.size_upper, + i->params.shrink_threshold, i->params.growth_step, + i->params.pagesize); + + dump_verbs("mode", i->params.mode_flags, mode_bits); + dump_verbs("table", i->params.table_flags, table_bits); + + if (i->params.test_nops) + log_verbose("iterations/records %u\n", i->params.test_nops); + else + dump_duration("duration", i->params.test_duration); + + if (i->params.nrepeat) + log_verbose("repeat %u\n", i->params.nrepeat); + else + log_verbose("repeat ETERNALLY\n"); + + log_verbose("threads %u\n", i->params.nthreads); + + log_verbose( + "keygen.params: case %s, width %u, mesh %u, rotate %u, offset %" PRIu64 + ", split %u/%u\n", + keygencase2str(i->params.keygen.keycase), i->params.keygen.width, + i->params.keygen.mesh, i->params.keygen.rotate, i->params.keygen.offset, + i->params.keygen.split, + i->params.keygen.width - i->params.keygen.split); + log_verbose("keygen.seed: %u\n", i->params.keygen.seed); + log_verbose("key: minlen %u, maxlen %u\n", i->params.keylen_min, + i->params.keylen_max); + log_verbose("data: minlen %u, maxlen %u\n", i->params.datalen_min, + i->params.datalen_max); + + log_verbose("batch: read %u, write %u\n", i->params.batch_read, + i->params.batch_write); + + if (i->params.waitfor_nops) + log_verbose("wait: actor %u for %u ops\n", i->wait4id, + i->params.waitfor_nops); + else if (i->params.delaystart) + dump_duration("delay", i->params.delaystart); + else + log_verbose("no-delay\n"); + + if (i->params.inject_writefaultn) + log_verbose("inject-writefault on %u ops\n", + i->params.inject_writefaultn); + else + log_verbose("no-inject-writefault\n"); + + log_verbose("limits: readers %u, tables %u\n", i->params.max_readers, + i->params.max_tables); + + log_verbose("drop table: %s\n", i->params.drop_table ? "Yes" : "No"); + log_verbose("ignore MDBX_MAP_FULL error: %s\n", + i->params.ignore_dbfull ? "Yes" : "No"); + log_verbose("verifying by speculum: %s\n", + i->params.speculum ? "Yes" : "No"); + + indent.pop(); + } + + dump_duration("timeout", global::config::timeout_duration_seconds); + log_verbose("cleanup: before %s, after %s\n", + global::config::cleanup_before ? "Yes" : "No", + global::config::cleanup_after ? "Yes" : "No"); + + log_verbose("failfast: %s\n", global::config::failfast ? "Yes" : "No"); + log_verbose("progress indicator: %s\n", + global::config::progress_indicator ? "Yes" : "No"); + log_verbose("console mode: %s\n", + global::config::console_mode ? "Yes" : "No"); +} + +} /* namespace config */ + +//----------------------------------------------------------------------------- + +using namespace config; + +actor_config::actor_config(actor_testcase testcase, const actor_params ¶ms, + unsigned space_id, unsigned wait4id) + : params(params) { + this->space_id = space_id; + this->actor_id = 1 + (unsigned)global::actors.size(); + this->testcase = testcase; + this->wait4id = wait4id; + signal_nops = 0; +} + +const std::string actor_config::serialize(const char *prefix) const { + simple_checksum checksum; + + std::string result; + if (prefix) + result.append(prefix); + + checksum.push(params.pathname_db); + result.append(params.pathname_db); + result.push_back('|'); + + checksum.push(params.pathname_log); + result.append(params.pathname_log); + result.push_back('|'); + + static_assert(std::is_pod::value, + "actor_params_pod should by POD"); + result.append(data2hex(static_cast(¶ms), + sizeof(actor_params_pod), checksum)); + result.push_back('|'); + + static_assert(std::is_pod::value, + "actor_config_pod should by POD"); + result.append(data2hex(static_cast(this), + sizeof(actor_config_pod), checksum)); + result.push_back('|'); + result.push_back(global::config::progress_indicator ? 'Y' : 'N'); + checksum.push(global::config::progress_indicator); + result.push_back(global::config::console_mode ? 'Y' : 'N'); + checksum.push(global::config::console_mode); + result.push_back('|'); + + result.append(osal_serialize(checksum)); + result.push_back('|'); + + result.append(std::to_string(checksum.value)); + return result; +} + +bool actor_config::deserialize(const char *str, actor_config &config) { + simple_checksum checksum; + + TRACE(">> actor_config::deserialize: %s\n", str); + + const char *slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-1\n"); + return false; + } + config.params.pathname_db.assign(str, slash - str); + checksum.push(config.params.pathname_db); + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-2\n"); + return false; + } + config.params.pathname_log.assign(str, slash - str); + checksum.push(config.params.pathname_log); + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-3\n"); + return false; + } + static_assert(std::is_pod::value, + "actor_params_pod should by POD"); + if (!hex2data(str, slash, static_cast(&config.params), + sizeof(actor_params_pod), checksum)) { + TRACE("<< actor_config::deserialize: actor_params_pod(%.*s)\n", + (int)(slash - str), str); + return false; + } + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-4\n"); + return false; + } + static_assert(std::is_pod::value, + "actor_config_pod should by POD"); + if (!hex2data(str, slash, static_cast(&config), + sizeof(actor_config_pod), checksum)) { + TRACE("<< actor_config::deserialize: actor_config_pod(%.*s)\n", + (int)(slash - str), str); + return false; + } + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-5\n"); + return false; + } + if ((str[0] == 'Y' || str[0] == 'N') && (str[1] == 'Y' || str[1] == 'N')) { + global::config::progress_indicator = str[0] == 'Y'; + checksum.push(global::config::progress_indicator); + global::config::console_mode = str[1] == 'Y'; + checksum.push(global::config::console_mode); + str = slash + 1; + + slash = strchr(str, '|'); + if (!slash) { + TRACE("<< actor_config::deserialize: slash-6\n"); + return false; + } + } + + if (!config.osal_deserialize(str, slash, checksum)) { + TRACE("<< actor_config::deserialize: osal\n"); + return false; + } + str = slash + 1; + + uint64_t verify = std::stoull(std::string(str)); + if (checksum.value != verify) { + TRACE("<< actor_config::deserialize: checksum mismatch\n"); + return false; + } + + TRACE("<< actor_config::deserialize: OK\n"); + return true; +} + +unsigned actor_params::mdbx_keylen_min() const { + return (table_flags & MDBX_INTEGERKEY) ? 4 : 0; +} + +unsigned actor_params::mdbx_keylen_max() const { + return (table_flags & MDBX_INTEGERKEY) + ? 8 + : std::min((unsigned)mdbx_limits_keysize_max(pagesize), + (unsigned)UINT16_MAX); +} + +unsigned actor_params::mdbx_datalen_min() const { + return (table_flags & MDBX_INTEGERDUP) ? 4 : 0; +} + +unsigned actor_params::mdbx_datalen_max() const { + return (table_flags & MDBX_INTEGERDUP) + ? 8 + : std::min((table_flags & MDBX_DUPSORT) + ? (unsigned)mdbx_limits_keysize_max(pagesize) + : (unsigned)MDBX_MAXDATASIZE, + (unsigned)UINT16_MAX); +} diff --git a/contrib/db/libmdbx/test/config.h b/contrib/db/libmdbx/test/config.h new file mode 100644 index 00000000..3703d3f5 --- /dev/null +++ b/contrib/db/libmdbx/test/config.h @@ -0,0 +1,326 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#include "base.h" +#include "log.h" +#include "utils.h" + +#define ACTOR_ID_MAX INT16_MAX + +enum actor_testcase { + ac_none, + ac_hill, + ac_deadread, + ac_deadwrite, + ac_jitter, + ac_try, + ac_copy, + ac_append, + ac_ttl, + ac_nested +}; + +enum actor_status { + as_unknown, + as_debuging, + as_running, + as_successful, + as_killed, + as_failed, + as_coredump, +}; + +const char *testcase2str(const actor_testcase); +const char *status2str(actor_status status); + +enum keygen_case { + kc_random, /* [ 6.. 2.. 7.. 4.. 0.. 1.. 5.. 3.. ] */ + kc_dashes, /* [ 0123.. 4567.. ] */ + kc_custom, + /* TODO: more cases */ +}; + +const char *keygencase2str(const keygen_case); + +//----------------------------------------------------------------------------- + +namespace config { + +enum scale_mode { no_scale, decimal, binary, duration }; + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + const char **value, const char *default_value = nullptr); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + std::string &value, bool allow_empty = false); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + std::string &value, bool allow_empty, + const char *default_value); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + bool &value); + +struct option_verb { + const char *const verb; + unsigned mask; +}; + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &mask, const option_verb *verbs); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + uint64_t &value, const scale_mode scale, + const uint64_t minval = 0, const uint64_t maxval = INT64_MAX, + const uint64_t default_value = 0); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + unsigned &value, const scale_mode scale, + const unsigned minval = 0, const unsigned maxval = INT32_MAX, + const unsigned default_value = 0); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + uint8_t &value, const uint8_t minval = 0, + const uint8_t maxval = 255, const uint8_t default_value = 0); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + int64_t &value, const int64_t minval, const int64_t maxval, + const int64_t default_value = -1); + +bool parse_option(int argc, char *const argv[], int &narg, const char *option, + int32_t &value, const int32_t minval, const int32_t maxval, + const int32_t default_value = -1); + +inline bool parse_option_intptr(int argc, char *const argv[], int &narg, + const char *option, intptr_t &value, + const intptr_t minval, const intptr_t maxval, + const intptr_t default_value = -1) { + static_assert(sizeof(intptr_t) == 4 || sizeof(intptr_t) == 8, "WTF?"); + if (sizeof(intptr_t) == 8) + return parse_option(argc, argv, narg, option, + *reinterpret_cast(&value), int64_t(minval), + int64_t(maxval), int64_t(default_value)); + else + return parse_option(argc, argv, narg, option, + *reinterpret_cast(&value), int32_t(minval), + int32_t(maxval), int32_t(default_value)); +} + +//----------------------------------------------------------------------------- + +#pragma pack(push, 1) + +struct keygen_params_pod { + /* Параметры генератора пар key-value. + * + * Ключи и значения генерируются по задаваемым параметрам на основе "плоской" + * исходной координаты. При этом, в общем случае, в процессе тестов исходная + * координата последовательно итерируется в заданном диапазоне, а необходимые + * паттерны/последовательности/узоры получаются за счет преобразования + * исходной координаты, согласно описанным ниже параметрам. + * + * Стоит отметить, что порядок описания параметров для удобства совпадает с + * порядком их использования, т.е. с порядком соответствующих преобразований. + * + * Второе важное замечание касается ограничений одновременной координированной + * генерации паттеров как для ключей, так и для значений. Суть в том, что + * такая возможность не нужна по следующим причинам: + * - libmdbx поддерживает два существенно различающихся вида таблиц, + * "уникальные" (без дубликатов и без multi-value), и так называемые + * "с дубликатами" (c multi-value). + * - Для таблиц "без дубликатов" только размер связанных к ключами значений + * (данных) оказывает влияния на работу движка, непосредственно содержимое + * данных не анализируется движком и не оказывает влияния на его работу. + * - Для таблиц "с дубликатами", при наличии более одного значения для + * некоторого ключа, формируется дочернее btree-поддерево. Это дерево + * формируется в отдельном "кусте" страниц и обслуживается независимо + * от окружения родительского ключа. + * - Таким образом, паттерн генерации значений имеет смысл только для + * таблиц "с дубликатами" и только в контексте одного значения ключа. + * Иначе говоря, нет смысла в со-координации генерации паттернов для + * ключей и значений. Более того, генерацию значений всегда необходимо + * рассматривать в контексте связки с одним значением ключа. + * - Тем не менее, во всех случаях достаточно важным является равномерная + * всех возможных сочетаний длин ключей и данных. + * + * width: + * Большинство тестов предполагают создание или итерирование некоторого + * количества записей. При этом требуется итерирование или генерация + * значений и ключей из некоторого ограниченного пространства вариантов. + * + * Параметр width задает такую ширину пространства вариантов в битах. + * Таким образом мощность пространства вариантов (пока) всегда равна + * степени двойки. Это ограничение можно снять, но ценой увеличения + * вычислительной сложности, включая потерю простоты и прозрачности. + * + * С другой стороны, не-битовый width может быть полезен: + * - Позволит генерировать ключи/значения в точно задаваемом диапазоне. + * Например, перебрать в псевдо-случайном порядке 10001 значение. + * - Позволит поровну разделять заданное пространство (диапазон) + * ключей/значений между количеством потоков некратным степени двойки. + * + * mesh и seed: + * Позволяют получить псевдо-случайные последовательности ключей/значений. + * Параметр mesh задает сколько младших бит исходной плоской координаты + * будет "перемешано" (инъективно отображено), а параметр seed позволяет + * выбрать конкретный вариант "перемешивания". + * + * Перемешивание выполняется при ненулевом значении mesh. Перемешивание + * реализуется посредством применения двух инъективных функций для + * заданного количества бит: + * - применяется первая инъективная функция; + * - к результату добавляется salt полученный из seed; + * - применяется вторая инъективная функция; + * + * Следует отметить, что mesh умышленно позволяет перемешать только младшую + * часть, что при ненулевом значении split (см далее) не позволяет получать + * псевдо-случайные значений ключей без псевдо-случайности в значениях. + * + * Такое ограничение соответствуют внутренней алгоритмике libmdbx. Проще + * говоря, мы можем проверить движок псевдо-случайной последовательностью + * ключей на таблицах без дубликатов (без multi-value), а затем проверить + * корректность работу псевдо-случайной последовательностью значений на + * таблицах с дубликатами (с multi-value), опционально добавляя + * псевдо-случайности к последовательности ключей. Однако, нет смысла + * генерировать псевдо-случайные ключи, одновременно с формированием + * какого-либо паттерна в значениях, так как содержимое в данных либо + * не будет иметь значения (для таблиц без дубликатов), либо будет + * обрабатываться в отдельных btree-поддеревьях. + * + * rotate и offset: + * Для проверки слияния и разделения страниц внутри движка требуются + * генерация ключей/значений в виде не-смежных последовательностей, как-бы + * в виде "пунктира", который постепенно заполняет весь заданных диапазон. + * + * Параметры позволяют генерировать такой "пунктир". Соответственно rotate + * задает циклический сдвиг вправо, а offset задает смещение, точнее говоря + * сложение по модулю внутри диапазона заданного посредством width. + * + * Например, при rotate равном 1 (циклический сдвиг вправо на 1 бит), + * четные и нечетные исходные значения сложатся в две линейные + * последовательности, которые постепенно закроют старшую и младшую + * половины диапазона. + * + * split: + * Для таблиц без дубликатов (без multi-value ключей) фактически требуется + * генерация только ключей, а данные могут быть постоянным. Но для таблиц с + * дубликатами (с multi-value ключами) также требуется генерация значений. + * + * Ненулевое значение параметра split фактически включает генерацию значений, + * при этом значение split определяет сколько бит исходного абстрактного + * номера будет отрезано для генерации значения. + */ + + uint8_t width; + uint8_t mesh; + uint8_t rotate; + uint8_t split; + uint32_t seed; + uint64_t offset; + keygen_case keycase; +}; + +struct actor_params_pod { + unsigned mode_flags; + unsigned table_flags; + intptr_t size_lower; + intptr_t size_now; + intptr_t size_upper; + int shrink_threshold; + int growth_step; + int pagesize; + + unsigned test_duration; + unsigned test_nops; + unsigned nrepeat; + unsigned nthreads; + + unsigned keylen_min, keylen_max; + unsigned datalen_min, datalen_max; + + unsigned batch_read; + unsigned batch_write; + + unsigned delaystart; + unsigned waitfor_nops; + unsigned inject_writefaultn; + + unsigned max_readers; + unsigned max_tables; + keygen_params_pod keygen; + + uint8_t loglevel; + bool drop_table; + bool ignore_dbfull; + bool speculum; +}; + +struct actor_config_pod { + unsigned actor_id, space_id; + actor_testcase testcase; + unsigned wait4id; + unsigned signal_nops; +}; + +#pragma pack(pop) + +extern const struct option_verb mode_bits[]; +extern const struct option_verb table_bits[]; +void dump(const char *title = "config-dump: "); + +} /* namespace config */ + +struct actor_params : public config::actor_params_pod { + std::string pathname_log; + std::string pathname_db; + void set_defaults(const std::string &tmpdir); + + unsigned mdbx_keylen_min() const; + unsigned mdbx_keylen_max() const; + unsigned mdbx_datalen_min() const; + unsigned mdbx_datalen_max() const; +}; + +struct actor_config : public config::actor_config_pod { + actor_params params; + + bool wanna_event4signalling() const { return true /* TODO ? */; } + + actor_config(actor_testcase testcase, const actor_params ¶ms, + unsigned space_id, unsigned wait4id); + + actor_config(const char *str) { + if (!deserialize(str, *this)) + failure("Invalid internal parameter '%s'\n", str); + } + + const std::string osal_serialize(simple_checksum &) const; + bool osal_deserialize(const char *str, const char *end, simple_checksum &); + + const std::string serialize(const char *prefix) const; + static bool deserialize(const char *str, actor_config &config); + + bool is_waitable(size_t nops) const { + switch (testcase) { + case ac_hill: + if (!params.test_nops || params.test_nops >= nops) + return true; + __fallthrough; + default: + return false; + } + } +}; diff --git a/contrib/db/libmdbx/test/copy.cc b/contrib/db/libmdbx/test/copy.cc new file mode 100644 index 00000000..ff53153e --- /dev/null +++ b/contrib/db/libmdbx/test/copy.cc @@ -0,0 +1,26 @@ +#include "test.h" + +void testcase_copy::copy_db(const bool with_compaction) { + int err = osal_removefile(copy_pathname); + if (err != MDBX_SUCCESS && err != MDBX_ENOFILE) + failure_perror("mdbx_removefile()", err); + + err = mdbx_env_copy(db_guard.get(), copy_pathname.c_str(), + with_compaction ? MDBX_CP_COMPACT : 0); + if (unlikely(err != MDBX_SUCCESS)) + failure_perror(with_compaction ? "mdbx_env_copy(MDBX_CP_COMPACT)" + : "mdbx_env_copy(MDBX_CP_ASIS)", + err); +} + +bool testcase_copy::run() { + jitter_delay(); + db_open(); + assert(!txn_guard); + const bool order = flipcoin(); + jitter_delay(); + copy_db(order); + jitter_delay(); + copy_db(!order); + return true; +} diff --git a/contrib/db/libmdbx/test/darwin/LICENSE b/contrib/db/libmdbx/test/darwin/LICENSE new file mode 100644 index 00000000..6a0dd306 --- /dev/null +++ b/contrib/db/libmdbx/test/darwin/LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2015, Aleksey Demakov +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/contrib/db/libmdbx/test/darwin/README.md b/contrib/db/libmdbx/test/darwin/README.md new file mode 100644 index 00000000..a6a8fd1a --- /dev/null +++ b/contrib/db/libmdbx/test/darwin/README.md @@ -0,0 +1,8 @@ +# DarwinPthreadBarrier + +A pthread_barrier_t implementation for Mac OS/X + +There is no pthread_barrier_t in Mac OS/X pthreads. This project fixes +this omission by providing a simple-minded barrier implementation based +on a pair of pthread_mutex_t and pthread_cond_t. + diff --git a/contrib/db/libmdbx/test/darwin/pthread_barrier.c b/contrib/db/libmdbx/test/darwin/pthread_barrier.c new file mode 100644 index 00000000..054aa007 --- /dev/null +++ b/contrib/db/libmdbx/test/darwin/pthread_barrier.c @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2015, Aleksey Demakov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pthread_barrier.h" + +#include + +#ifdef __APPLE__ + +int pthread_barrierattr_init(pthread_barrierattr_t *attr) { + memset(attr, 0, sizeof(pthread_barrierattr_t)); + int m = pthread_mutexattr_init(&attr->mattr); + int c = pthread_condattr_init(&attr->cattr); + return m ? m : c; +} + +int pthread_barrierattr_destroy(pthread_barrierattr_t *attr) { + int c = pthread_condattr_destroy(&attr->cattr); + int m = pthread_mutexattr_destroy(&attr->mattr); + return m ? m : c; +} + +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict attr, + int *__restrict pshared) { + return pthread_condattr_getpshared(&attr->cattr, pshared); +} + +int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared) { + int m = pthread_mutexattr_setpshared(&attr->mattr, pshared); + int c = pthread_condattr_setpshared(&attr->cattr, pshared); + return m ? m : c; +} + +int pthread_barrier_init(pthread_barrier_t *__restrict barrier, + const pthread_barrierattr_t *__restrict attr, + unsigned count) { + if (count == 0) + return errno = EINVAL; + + int rc = pthread_mutex_init(&barrier->mutex, attr ? &attr->mattr : 0); + if (rc) + return rc; + + rc = pthread_cond_init(&barrier->cond, attr ? &attr->cattr : 0); + if (rc) { + int errno_save = errno; + pthread_mutex_destroy(&barrier->mutex); + errno = errno_save; + return rc; + } + + barrier->limit = count; + barrier->count = 0; + barrier->phase = 0; + return 0; +} + +int pthread_barrier_destroy(pthread_barrier_t *barrier) { + pthread_mutex_destroy(&barrier->mutex); + pthread_cond_destroy(&barrier->cond); + return 0; +} + +int pthread_barrier_wait(pthread_barrier_t *barrier) { + int rc = pthread_mutex_lock(&barrier->mutex); + if (rc) + return rc; + + barrier->count++; + if (barrier->count >= barrier->limit) { + barrier->phase++; + barrier->count = 0; + pthread_cond_broadcast(&barrier->cond); + pthread_mutex_unlock(&barrier->mutex); + return PTHREAD_BARRIER_SERIAL_THREAD; + } else { + unsigned phase = barrier->phase; + do + pthread_cond_wait(&barrier->cond, &barrier->mutex); + while (phase == barrier->phase); + pthread_mutex_unlock(&barrier->mutex); + return 0; + } +} + +#endif /* __APPLE__ */ diff --git a/contrib/db/libmdbx/test/darwin/pthread_barrier.h b/contrib/db/libmdbx/test/darwin/pthread_barrier.h new file mode 100644 index 00000000..efa9b9b7 --- /dev/null +++ b/contrib/db/libmdbx/test/darwin/pthread_barrier.h @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015, Aleksey Demakov + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef PTHREAD_BARRIER_H +#define PTHREAD_BARRIER_H + +#include + +#ifdef __APPLE__ + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(PTHREAD_BARRIER_SERIAL_THREAD) +#define PTHREAD_BARRIER_SERIAL_THREAD (1) +#endif + +#if !defined(PTHREAD_PROCESS_PRIVATE) +#define PTHREAD_PROCESS_PRIVATE (42) +#endif +#if !defined(PTHREAD_PROCESS_SHARED) +#define PTHREAD_PROCESS_SHARED (43) +#endif + +typedef struct { + pthread_mutexattr_t mattr; + pthread_condattr_t cattr; +} pthread_barrierattr_t; + +typedef struct { + pthread_mutex_t mutex; + pthread_cond_t cond; + unsigned int limit; + unsigned int count; + unsigned int phase; +} pthread_barrier_t; + +int pthread_barrierattr_init(pthread_barrierattr_t *attr); +int pthread_barrierattr_destroy(pthread_barrierattr_t *attr); + +int pthread_barrierattr_getpshared(const pthread_barrierattr_t *__restrict attr, + int *__restrict pshared); +int pthread_barrierattr_setpshared(pthread_barrierattr_t *attr, int pshared); + +int pthread_barrier_init(pthread_barrier_t *__restrict barrier, + const pthread_barrierattr_t *__restrict attr, + unsigned int count); +int pthread_barrier_destroy(pthread_barrier_t *barrier); + +int pthread_barrier_wait(pthread_barrier_t *barrier); + +#ifdef __cplusplus +} +#endif + +#endif /* __APPLE__ */ + +#endif /* PTHREAD_BARRIER_H */ diff --git a/contrib/db/libmdbx/test/dead.cc b/contrib/db/libmdbx/test/dead.cc new file mode 100644 index 00000000..8f83bbeb --- /dev/null +++ b/contrib/db/libmdbx/test/dead.cc @@ -0,0 +1,35 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +bool testcase_deadread::run() { + db_open(); + txn_begin(true); + cursor_guard.reset(); + txn_guard.reset(); + db_guard.reset(); + return true; +} + +//----------------------------------------------------------------------------- + +bool testcase_deadwrite::run() { + db_open(); + txn_begin(false); + cursor_guard.reset(); + txn_guard.reset(); + db_guard.reset(); + return true; +} diff --git a/contrib/db/libmdbx/test/hill.cc b/contrib/db/libmdbx/test/hill.cc new file mode 100644 index 00000000..9d989095 --- /dev/null +++ b/contrib/db/libmdbx/test/hill.cc @@ -0,0 +1,409 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +bool testcase_hill::run() { + int err = db_open__begin__table_create_open_clean(dbi); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("hill: bailout-prepare due '%s'", mdbx_strerror(err)); + return false; + } + speculum.clear(); + speculum_commited.clear(); + + /* LY: тест "холмиком": + * - сначала наполняем таблицу циклическими CRUD-манипуляциями, + * которые в каждом цикле делают несколько операций, включая удаление, + * но в результате добавляют записи. + * - затем очищаем таблицу также CRUD-манипуляциями, но уже с другой + * пропорцией удалений. + * + * При этом очень многое зависит от порядка перебора ключей: + * - (псевдо)случайное распределение требуется лишь для полноты картины, + * но в целом не покрывает важных кейсов. + * - кроме (псевдо)случайного перебора требуется последовательное + * итерирование ключей интервалами различной ширины, с тем чтобы + * проверить различные варианты как разделения, так и слияния страниц + * внутри движка. + * - при не-уникальных ключах (MDBX_DUPSORT с подвариантами), для каждого + * повтора внутри движка формируется вложенное btree-дерево, + * соответственно требуется соблюдение аналогичных принципов + * итерирования для значений. + */ + + /* TODO: работа в несколько потоков */ + keyvalue_maker.setup(config.params, config.actor_id, 0 /* thread_number */); + + keygen::buffer a_key = keygen::alloc(config.params.keylen_max); + keygen::buffer a_data_0 = keygen::alloc(config.params.datalen_max); + keygen::buffer a_data_1 = keygen::alloc(config.params.datalen_max); + keygen::buffer b_key = keygen::alloc(config.params.keylen_max); + keygen::buffer b_data = keygen::alloc(config.params.datalen_max); + + const unsigned insert_flags = (config.params.table_flags & MDBX_DUPSORT) + ? MDBX_NODUPDATA + : MDBX_NODUPDATA | MDBX_NOOVERWRITE; + const unsigned update_flags = + (config.params.table_flags & MDBX_DUPSORT) + ? MDBX_CURRENT | MDBX_NODUPDATA | MDBX_NOOVERWRITE + : MDBX_NODUPDATA; + + uint64_t serial_count = 0; + uint64_t commited_serial = serial_count; + unsigned txn_nops = 0; + + bool rc = false; + while (should_continue()) { + const keygen::serial_t a_serial = serial_count; + if (unlikely(!keyvalue_maker.increment(serial_count, 1))) { + log_notice("uphill: unexpected key-space overflow"); + break; + } + + const keygen::serial_t b_serial = serial_count; + assert(b_serial > a_serial); + + // создаем первую запись из пары + const keygen::serial_t age_shift = UINT64_C(1) << (a_serial % 31); + log_trace("uphill: insert-a (age %" PRIu64 ") %" PRIu64, age_shift, + a_serial); + generate_pair(a_serial, a_key, a_data_1, age_shift); + + err = insert(a_key, a_data_1, insert_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("uphill: bailout at insert-a due '%s'", mdbx_strerror(err)); + txn_restart(true, false); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + failure_perror("mdbx_put(insert-a.1)", err); + } + if (!speculum_verify()) { + log_notice("uphill: bailout after insert-a, before commit"); + goto bailout; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + commited_serial = a_serial; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("uphill: bailout after insert-a, after commit"); + goto bailout; + } + } + + // создаем вторую запись из пары + log_trace("uphill: insert-b %" PRIu64, b_serial); + generate_pair(b_serial, b_key, b_data, 0); + err = insert(b_key, b_data, insert_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("uphill: bailout at insert-b due '%s'", mdbx_strerror(err)); + txn_restart(true, false); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + failure_perror("mdbx_put(insert-b)", err); + } + if (!speculum_verify()) { + log_notice("uphill: bailout after insert-b, before commit"); + goto bailout; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + commited_serial = a_serial; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("uphill: bailout after insert-b, after commit"); + goto bailout; + } + } + + // обновляем данные в первой записи + log_trace("uphill: update-a (age %" PRIu64 "->0) %" PRIu64, age_shift, + a_serial); + generate_pair(a_serial, a_key, a_data_0, 0); + checkdata("uphill: update-a", dbi, a_key->value, a_data_1->value); + err = replace(a_key, a_data_0, a_data_1, update_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("uphill: bailout at update-a due '%s'", mdbx_strerror(err)); + txn_restart(true, false); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + failure_perror("mdbx_replace(update-a: 1->0)", err); + } + if (!speculum_verify()) { + log_notice("uphill: bailout after update-a, before commit"); + goto bailout; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + commited_serial = a_serial; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("uphill: bailout after update-a, after commit"); + goto bailout; + } + } + + // удаляем вторую запись + log_trace("uphill: delete-b %" PRIu64, b_serial); + checkdata("uphill: delete-b", dbi, b_key->value, b_data->value); + err = remove(b_key, b_data); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("uphill: bailout at delete-b due '%s'", mdbx_strerror(err)); + txn_restart(true, false); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + failure_perror("mdbx_del(b)", err); + } + if (!speculum_verify()) { + log_notice("uphill: bailout after delete-b, before commit"); + goto bailout; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("uphill: bailout at commit due '%s'", mdbx_strerror(err)); + serial_count = commited_serial; + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + commited_serial = a_serial; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("uphill: bailout after delete-b, after commit"); + goto bailout; + } + } + + report(1); + if (!keyvalue_maker.increment(serial_count, 1)) { + // дошли до границы пространства ключей + serial_count = a_serial; + goto overflow; + } + } + + while (serial_count > 1) { + if (unlikely(!keyvalue_maker.increment(serial_count, -2))) + failure("downhill: unexpected key-space underflow"); + + overflow: + const keygen::serial_t a_serial = serial_count; + const keygen::serial_t b_serial = a_serial + 1; + assert(b_serial > a_serial); + + // обновляем первую запись из пары + const keygen::serial_t age_shift = UINT64_C(1) << (a_serial % 31); + log_trace("downhill: update-a (age 0->%" PRIu64 ") %" PRIu64, age_shift, + a_serial); + generate_pair(a_serial, a_key, a_data_0, 0); + generate_pair(a_serial, a_key, a_data_1, age_shift); + checkdata("downhill: update-a", dbi, a_key->value, a_data_0->value); + err = replace(a_key, a_data_1, a_data_0, update_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("downhill: bailout at update-a due '%s'", + mdbx_strerror(err)); + txn_end(true); + speculum = speculum_commited; + break; + } + failure_perror("mdbx_put(update-a: 0->1)", err); + } + if (!speculum_verify()) { + log_notice("downhill: bailout after update-a, before commit"); + break; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("downhill: bailout after update-a, after commit"); + break; + } + } + + // создаем вторую запись из пары + log_trace("downhill: insert-b %" PRIu64, b_serial); + generate_pair(b_serial, b_key, b_data, 0); + err = insert(b_key, b_data, insert_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("downhill: bailout at insert-a due '%s'", + mdbx_strerror(err)); + txn_end(true); + speculum = speculum_commited; + break; + } + failure_perror("mdbx_put(insert-b)", err); + } + if (!speculum_verify()) { + log_notice("downhill: bailout after insert-b, before commit"); + break; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("downhill: bailout after insert-b, after commit"); + break; + } + } + + // удаляем первую запись + log_trace("downhill: delete-a (age %" PRIu64 ") %" PRIu64, age_shift, + a_serial); + checkdata("downhill: delete-a", dbi, a_key->value, a_data_1->value); + err = remove(a_key, a_data_1); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("downhill: bailout at delete-a due '%s'", + mdbx_strerror(err)); + txn_end(true); + speculum = speculum_commited; + break; + } + failure_perror("mdbx_del(a)", err); + } + if (!speculum_verify()) { + log_notice("downhill: bailout after delete-a, before commit"); + break; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("downhill: bailout after delete-a, after commit"); + break; + } + } + + // удаляем вторую запись + log_trace("downhill: delete-b %" PRIu64, b_serial); + checkdata("downhill: delete-b", dbi, b_key->value, b_data->value); + err = remove(b_key, b_data); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("downhill: bailout at delete-b due '%s'", + mdbx_strerror(err)); + txn_end(true); + speculum = speculum_commited; + break; + } + failure_perror("mdbx_del(b)", err); + } + if (!speculum_verify()) { + log_notice("downhill: bailout after delete-b, before commit"); + break; + } + + if (++txn_nops >= config.params.batch_write) { + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + speculum = speculum_commited; + break; + } + speculum_commited = speculum; + txn_nops = 0; + if (!speculum_verify()) { + log_notice("downhill: bailout after delete-b, after commit"); + goto bailout; + } + } + + report(1); + } + + rc = speculum_verify(); +bailout: + if (txn_guard) { + err = breakable_commit(); + if (unlikely(err != MDBX_SUCCESS)) + log_notice("downhill: bailout at commit due '%s'", mdbx_strerror(err)); + } + + if (dbi) { + if (config.params.drop_table && !mode_readonly()) { + txn_begin(false); + db_table_drop(dbi); + err = breakable_commit(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("hill: bailout-clean due '%s'", mdbx_strerror(err)); + return rc; + } + } else + db_table_close(dbi); + } + return rc; +} diff --git a/contrib/db/libmdbx/test/jitter.cc b/contrib/db/libmdbx/test/jitter.cc new file mode 100644 index 00000000..38caf33b --- /dev/null +++ b/contrib/db/libmdbx/test/jitter.cc @@ -0,0 +1,59 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +bool testcase_jitter::run() { + while (should_continue()) { + jitter_delay(); + db_open(); + + if (flipcoin()) { + jitter_delay(); + txn_begin(true); + fetch_canary(); + jitter_delay(); + txn_end(flipcoin()); + } + + jitter_delay(); + txn_begin(mode_readonly()); + jitter_delay(); + if (!mode_readonly()) { + fetch_canary(); + update_canary(1); + /* TODO: + * - db_setsize() + * ... + */ + } + txn_end(flipcoin()); + + if (flipcoin()) { + jitter_delay(); + txn_begin(true); + jitter_delay(); + txn_end(flipcoin()); + } + + jitter_delay(); + db_close(); + + /* just 'align' nops with other tests with batching */ + const auto batching = + std::max(config.params.batch_read, config.params.batch_write); + report(std::max(1u, batching / 2)); + } + return true; +} diff --git a/contrib/db/libmdbx/test/keygen.cc b/contrib/db/libmdbx/test/keygen.cc new file mode 100644 index 00000000..e1561420 --- /dev/null +++ b/contrib/db/libmdbx/test/keygen.cc @@ -0,0 +1,275 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +namespace keygen { + +static inline __pure_function serial_t mask(unsigned bits) { + assert(bits > 0 && bits <= serial_maxwith); + return serial_allones >> (serial_maxwith - bits); +} + +/* LY: https://en.wikipedia.org/wiki/Injective_function */ +serial_t injective(const serial_t serial, + const unsigned bits /* at least serial_minwith (8) */, + const serial_t salt) { + assert(bits > serial_minwith && bits <= serial_maxwith); + + /* LY: All these "magic" prime numbers were found + * and verified with a bit of brute force. */ + + static const uint64_t m[64 - serial_minwith + 1] = { + /* 8 - 24 */ + 113, 157, 397, 653, 1753, 5641, 9697, 23873, 25693, 80833, 105953, 316937, + 309277, 834497, 1499933, 4373441, 10184137, + /* 25 - 64 */ + 10184137, 17279209, 33990377, 67295161, 284404553, 1075238767, 6346721573, + 6924051577, 19204053433, 45840188887, 53625693977, 73447827913, + 141638870249, 745683604649, 1283334050489, 1100828289853, 2201656586197, + 5871903036137, 11238507001417, 45264020802263, 105008404482889, + 81921776907059, 199987980256399, 307207457507641, 946769023178273, + 2420886491930041, 3601632139991929, 11984491914483833, 21805846439714153, + 23171543400565993, 53353226456762893, 155627817337932409, + 227827205384840249, 816509268558278821, 576933057762605689, + 2623957345935638441, 5048241705479929949, 4634245581946485653, + 4613509448041658233, 4952535426879925961}; + static const uint8_t s[64 - serial_minwith + 1] = { + /* 8 - 24 */ + 2, 3, 4, 4, 2, 4, 3, 3, 7, 3, 3, 4, 8, 3, 10, 3, 11, + /* 25 - 64 */ + 11, 9, 9, 9, 11, 10, 5, 14, 11, 16, 14, 12, 13, 16, 19, 10, 10, 21, 7, 20, + 10, 14, 22, 19, 3, 21, 18, 19, 26, 24, 2, 21, 25, 29, 24, 10, 11, 14, 20, + 19}; + + const auto mult = m[bits - 8]; + const auto shift = s[bits - 8]; + serial_t result = serial * mult; + if (salt) { + const unsigned left = bits / 2; + const unsigned right = bits - left; + result = (result << left) | ((result & mask(bits)) >> right); + result = (result ^ salt) * mult; + } + + result ^= result << shift; + result &= mask(bits); + log_trace("keygen-injective: serial %" PRIu64 "/%u @%" PRIx64 ",%u,%" PRIu64 + " => %" PRIu64 "/%u", + serial, bits, mult, shift, salt, result, bits); + return result; +} + +void __hot maker::pair(serial_t serial, const buffer &key, buffer &value, + serial_t value_age) { + assert(mapping.width >= serial_minwith && mapping.width <= serial_maxwith); + assert(mapping.split <= mapping.width); + assert(mapping.mesh <= mapping.width); + assert(mapping.rotate <= mapping.width); + assert(mapping.offset <= mask(mapping.width)); + assert(!(key_essentials.flags & + ~(MDBX_INTEGERKEY | MDBX_REVERSEKEY | MDBX_DUPSORT))); + assert(!(value_essentials.flags & ~(MDBX_INTEGERDUP | MDBX_REVERSEDUP))); + + log_trace("keygen-pair: serial %" PRIu64 ", data-age %" PRIu64, serial, + value_age); + + if (mapping.mesh >= serial_minwith) { + serial = + (serial & ~mask(mapping.mesh)) | injective(serial, mapping.mesh, salt); + log_trace("keygen-pair: mesh@%u => %" PRIu64, mapping.mesh, serial); + } + + if (mapping.rotate) { + const unsigned right = mapping.rotate; + const unsigned left = mapping.width - right; + serial = (serial << left) | ((serial & mask(mapping.width)) >> right); + log_trace("keygen-pair: rotate@%u => %" PRIu64 ", 0x%" PRIx64, + mapping.rotate, serial, serial); + } + + if (mapping.offset) { + serial = (serial + mapping.offset) & mask(mapping.width); + log_trace("keygen-pair: offset@%" PRIu64 " => %" PRIu64, mapping.offset, + serial); + } + if (base) { + serial += base; + log_trace("keygen-pair: base@%" PRIu64 " => %" PRIu64, base, serial); + } + + serial_t key_serial = serial; + serial_t value_serial = value_age << mapping.split; + if (mapping.split) { + if (key_essentials.flags & MDBX_DUPSORT) { + key_serial >>= mapping.split; + value_serial += serial & mask(mapping.split); + } else { + /* Без MDBX_DUPSORT требуется уникальность ключей, а для этого нельзя + * отбрасывать какие-либо биты serial после инъективного преобразования. + * Поэтому key_serial не трогаем, а в value_serial нелинейно вмешиваем + * запрошенное количество бит из serial */ + value_serial += + (serial ^ (serial >> mapping.split)) & mask(mapping.split); + } + + value_serial |= value_age << mapping.split; + log_trace("keygen-pair: split@%u => k%" PRIu64 ", v%" PRIu64, mapping.split, + key_serial, value_serial); + } + + log_trace("keygen-pair: key %" PRIu64 ", value %" PRIu64, key_serial, + value_serial); + mk(key_serial, key_essentials, *key); + mk(value_serial, value_essentials, *value); + + if (log_enabled(logging::trace)) { + char dump_key[128], dump_value[128]; + log_trace("keygen-pair: key %s, value %s", + mdbx_dump_val(&key->value, dump_key, sizeof(dump_key)), + mdbx_dump_val(&value->value, dump_value, sizeof(dump_value))); + } +} + +void maker::setup(const config::actor_params_pod &actor, unsigned actor_id, + unsigned thread_number) { + key_essentials.flags = + actor.table_flags & (MDBX_INTEGERKEY | MDBX_REVERSEKEY | MDBX_DUPSORT); + assert(actor.keylen_min <= UINT8_MAX); + key_essentials.minlen = (uint8_t)actor.keylen_min; + assert(actor.keylen_max <= UINT16_MAX); + key_essentials.maxlen = (uint16_t)actor.keylen_max; + + value_essentials.flags = + actor.table_flags & (MDBX_INTEGERDUP | MDBX_REVERSEDUP); + assert(actor.datalen_min <= UINT8_MAX); + value_essentials.minlen = (uint8_t)actor.datalen_min; + assert(actor.datalen_max <= UINT16_MAX); + value_essentials.maxlen = (uint16_t)actor.datalen_max; + + assert(thread_number < 2); + (void)thread_number; + mapping = actor.keygen; + salt = (actor.keygen.seed + actor_id) * UINT64_C(14653293970879851569); + + // FIXME: TODO + base = 0; +} + +void maker::make_ordered() { + mapping.mesh = 0; + mapping.rotate = 0; +} + +bool maker::is_unordered() const { + return (mapping.mesh >= serial_minwith || mapping.rotate) != 0; +} + +bool maker::increment(serial_t &serial, int delta) const { + if (serial > mask(mapping.width)) { + log_extra("keygen-increment: %" PRIu64 " > %" PRIu64 ", overflow", serial, + mask(mapping.width)); + return false; + } + + serial_t target = serial + (int64_t)delta; + if (target > mask(mapping.width) || + ((delta > 0) ? target < serial : target > serial)) { + log_extra("keygen-increment: %" PRIu64 "%-d => %" PRIu64 ", overflow", + serial, delta, target); + return false; + } + + log_extra("keygen-increment: %" PRIu64 "%-d => %" PRIu64 ", continue", serial, + delta, target); + serial = target; + return true; +} + +//----------------------------------------------------------------------------- + +static size_t length(serial_t serial) { + size_t n = 0; + if (serial > UINT32_MAX) { + n = 4; + serial >>= 32; + } + if (serial > UINT16_MAX) { + n += 2; + serial >>= 16; + } + if (serial > UINT8_MAX) { + n += 1; + serial >>= 8; + } + return (serial > 0) ? n + 1 : n; +} + +buffer alloc(size_t limit) { + result *ptr = (result *)malloc(sizeof(result) + limit); + if (unlikely(ptr == nullptr)) + failure_perror("malloc(keyvalue_buffer)", errno); + ptr->value.iov_base = ptr->bytes; + ptr->value.iov_len = 0; + ptr->limit = limit; + return buffer(ptr); +} + +void __hot maker::mk(const serial_t serial, const essentials ¶ms, + result &out) { + assert(out.limit >= params.maxlen); + assert(params.maxlen >= params.minlen); + assert(params.maxlen >= length(serial)); + + out.value.iov_base = out.bytes; + out.value.iov_len = + (params.maxlen > params.minlen) + ? params.minlen + serial % (params.maxlen - params.minlen) + : params.minlen; + + if (params.flags & (MDBX_INTEGERKEY | MDBX_INTEGERDUP)) { + assert(params.maxlen == params.minlen); + assert(params.minlen == 4 || params.minlen == 8); + if (is_byteorder_le() || params.minlen == 8) + out.u64 = serial; + else + out.u32 = (uint32_t)serial; + } else if (params.flags & (MDBX_REVERSEKEY | MDBX_REVERSEDUP)) { + if (out.value.iov_len > 8) { + memset(out.bytes, '\0', out.value.iov_len - 8); + unaligned::store(out.bytes + out.value.iov_len - 8, htobe64(serial)); + } else { + out.u64 = htobe64(serial); + if (out.value.iov_len < 8) { + out.value.iov_len = std::max(length(serial), out.value.iov_len); + out.value.iov_base = out.bytes + 8 - out.value.iov_len; + } + } + } else { + out.u64 = htole64(serial); + if (out.value.iov_len > 8) + memset(out.bytes + 8, '\0', out.value.iov_len - 8); + else + out.value.iov_len = std::max(length(serial), out.value.iov_len); + } + + assert(out.value.iov_len >= params.minlen); + assert(out.value.iov_len <= params.maxlen); + assert(out.value.iov_len >= length(serial)); + assert(out.value.iov_base >= out.bytes); + assert((uint8_t *)out.value.iov_base + out.value.iov_len <= + out.bytes + out.limit); +} + +} /* namespace keygen */ diff --git a/contrib/db/libmdbx/test/keygen.h b/contrib/db/libmdbx/test/keygen.h new file mode 100644 index 00000000..0403ab8a --- /dev/null +++ b/contrib/db/libmdbx/test/keygen.h @@ -0,0 +1,130 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#include "base.h" +#include "config.h" +#include "log.h" +#include "utils.h" + +namespace keygen { + +/* Под "генерацией ключей" здесь понимается генерация обоих значений для + * пар key-value, т.е. не только ключей, но и ассоциированных с ними данных. + */ + +/* Генерацию ключей нельзя отнести к простым задачам, так как требования + * примерно следующие: + * - генерация разного количества уникальных ключей различной длины + * в задаваемом диапазоне; + * - возможность выбора как псевдо-случайного порядка ключей, + * так и по некоторым специфическим законам (ограниченными упорядоченными + * последовательностями, в шахматном порядке по граница диапазона и т.д.); + * - возможность генерации дубликатов с задаваемым законом распределения; + * - возможность генерации непересекающимися кластерами для параллельного + * использования в нескольких потоках; + * - использовать минимум ресурсов, как CPU, так и RAM, в том числе + * включая cache pollution и ram bandwidth. + * + * При этом заведомо известно, что для MDBX не имеет значения: + * - используемый алфавит (значения байтов); + * - частотное распределение по алфавиту; + * - абсолютное значение ключей или разность между отдельными значениями; + * + * Соответственно, в общих чертах, схема генерации следующая: + * - вводится плоская одномерная "координата" serial (uint64_t); + * - генерация специфических паттернов (последовательностей) + * реализуется посредством соответствующих преобразований "координат", при + * этом все подобные преобразования выполняются только над "координатой"; + * - итоговая "координата" преобразуется в 8-байтное суррогатное значение + * ключа; + * - для получения ключей длиной МЕНЕЕ 8 байт суррогат может усекаться + * до ненулевых байт, в том числе до нулевой длины; + * - для получения ключей длиной БОЛЕЕ 8 байт суррогат дополняется + * нулями или псевдослучайной последовательностью; + * + * Механизм генерации паттернов: + * - реализованный механизм является компромиссом между скоростью/простотой + * и гибкостью, необходимой для получения последовательностей, которых + * будет достаточно для проверки сценариев разделения и слияния страниц + * с данными внутри mdbx; + * - псевдо-случайные паттерны реализуются посредством набора инъективных + * отображающих функций; + * - не-псевдо-случайные паттерны реализуются посредством параметризируемого + * трех-этапного преобразования: + * 1) смещение (сложение) по модулю; + * 2) циклический сдвиг; + * 3) добавление абсолютного смещения (базы); + */ + +typedef uint64_t serial_t; + +enum : serial_t { + serial_minwith = 8, + serial_maxwith = sizeof(serial_t) * 8, + serial_allones = ~(serial_t)0u +}; + +struct result { + MDBX_val value; + size_t limit; + union { + uint8_t bytes[sizeof(uint64_t)]; + uint32_t u32; + uint64_t u64; + }; + + std::string as_string() const { + return std::string((const char *)value.iov_base, value.iov_len); + } +}; + +//----------------------------------------------------------------------------- + +struct buffer_deleter /* : public std::unary_function */ { + void operator()(result *buffer) const { free(buffer); } +}; + +typedef std::unique_ptr buffer; + +buffer alloc(size_t limit); + +class maker { + config::keygen_params_pod mapping; + serial_t base; + serial_t salt; + + struct essentials { + uint8_t minlen; + uint8_t flags; + uint16_t maxlen; + } key_essentials, value_essentials; + + static void mk(const serial_t serial, const essentials ¶ms, result &out); + +public: + maker() { memset(this, 0, sizeof(*this)); } + + void pair(serial_t serial, const buffer &key, buffer &value, + serial_t value_age); + void setup(const config::actor_params_pod &actor, unsigned actor_id, + unsigned thread_number); + void make_ordered(); + bool is_unordered() const; + + bool increment(serial_t &serial, int delta) const; +}; + +} /* namespace keygen */ diff --git a/contrib/db/libmdbx/test/log.cc b/contrib/db/libmdbx/test/log.cc new file mode 100644 index 00000000..57139eac --- /dev/null +++ b/contrib/db/libmdbx/test/log.cc @@ -0,0 +1,371 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +static void fflushall() { fflush(nullptr); } + +void failure(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + fflushall(); + logging::output_nocheckloglevel_ap(logging::failure, fmt, ap); + va_end(ap); + fflushall(); + exit(EXIT_FAILURE); +} + +const char *test_strerror(int errnum) { + static __thread char buf[1024]; + return mdbx_strerror_r(errnum, buf, sizeof(buf)); +} + +void __noreturn failure_perror(const char *what, int errnum) { + failure("%s failed: %s (%d)\n", what, test_strerror(errnum), errnum); +} + +//----------------------------------------------------------------------------- + +static void mdbx_logger(int priority, const char *function, int line, + const char *msg, va_list args) { + if (!function) + function = "unknown"; + + if (priority == MDBX_LOG_FATAL) + log_error("mdbx: fatal failure: %s, %d", function, line); + + logging::output_nocheckloglevel( + logging::loglevel(priority), + strncmp(function, "mdbx_", 5) == 0 ? "%s: " : "mdbx %s: ", function); + logging::feed_ap(msg, args); +} + +namespace logging { + +static std::string prefix; +static std::string suffix; +static loglevel level; +static FILE *last; + +void setlevel(loglevel priority) { + level = priority; + int rc = mdbx_setup_debug(priority, + MDBX_DBG_ASSERT | MDBX_DBG_AUDIT | MDBX_DBG_JITTER | + MDBX_DBG_DUMP, + mdbx_logger); + log_trace("set mdbx debug-opts: 0x%02x", rc); +} + +void setup(loglevel priority, const std::string &_prefix) { + setlevel(priority); + prefix = _prefix; +} + +void setup(const std::string &_prefix) { prefix = _prefix; } + +const char *level2str(const loglevel alevel) { + switch (alevel) { + default: + return "invalid/unknown"; + case extra: + return "extra"; + case trace: + return "trace"; + case debug: + return "debug"; + case verbose: + return "verbose"; + case notice: + return "notice"; + case warning: + return "warning"; + case error: + return "error"; + case failure: + return "failure"; + } +} + +bool output(const loglevel priority, const char *format, ...) { + if (lower(priority, level)) + return false; + + va_list ap; + va_start(ap, format); + output_nocheckloglevel_ap(priority, format, ap); + va_end(ap); + return true; +} + +void output_nocheckloglevel_ap(const logging::loglevel priority, + const char *format, va_list ap) { + if (last) { + putc('\n', last); + fflush(last); + if (last == stderr) { + putc('\n', stdout); + fflush(stdout); + } + last = nullptr; + } + + chrono::time now = chrono::now_realtime(); + struct tm tm; +#ifdef _MSC_VER + int rc = _localtime32_s(&tm, (const __time32_t *)&now.utc); +#else + time_t time = now.utc; + int rc = localtime_r(&time, &tm) ? MDBX_SUCCESS : errno; +#endif + if (rc != MDBX_SUCCESS) + failure_perror("localtime_r()", rc); + + last = stdout; + fprintf(last, + "[ %02d%02d%02d-%02d:%02d:%02d.%06d_%05u %-10s %.4s ] %s" /* TODO */, + tm.tm_year - 100, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, + tm.tm_sec, chrono::fractional2us(now.fractional), osal_getpid(), + prefix.c_str(), level2str(priority), suffix.c_str()); + + va_list ones; + memset(&ones, 0, sizeof(ones)) /* zap MSVC and other stupid compilers */; + if (same_or_higher(priority, error)) + va_copy(ones, ap); + vfprintf(last, format, ap); + + size_t len = strlen(format); + char end = len ? format[len - 1] : '\0'; + + switch (end) { + default: + putc('\n', last); + // fall through + case '\n': + fflush(last); + last = nullptr; + // fall through + case ' ': + case '_': + case ':': + case '|': + case ',': + case '\t': + case '\b': + case '\r': + case '\0': + break; + } + + if (same_or_higher(priority, error)) { + if (last != stderr) { + fprintf(stderr, "[ %05u %-10s %.4s ] %s", osal_getpid(), prefix.c_str(), + level2str(priority), suffix.c_str()); + vfprintf(stderr, format, ones); + if (end == '\n') + fflush(stderr); + else + last = stderr; + } + va_end(ones); + } +} + +bool feed_ap(const char *format, va_list ap) { + if (!last) + return false; + + if (last == stderr) { + va_list ones; + va_copy(ones, ap); + vfprintf(stdout, format, ones); + va_end(ones); + } + vfprintf(last, format, ap); + size_t len = strlen(format); + if (len && format[len - 1] == '\n') { + fflush(last); + if (last == stderr) + fflush(stdout); + last = nullptr; + } + return true; +} + +bool feed(const char *format, ...) { + if (!last) + return false; + + va_list ap; + va_start(ap, format); + feed_ap(format, ap); + va_end(ap); + return true; +} + +local_suffix::local_suffix(const char *c_str) + : trim_pos(suffix.size()), indent(0) { + suffix.append(c_str); +} + +local_suffix::local_suffix(const std::string &str) + : trim_pos(suffix.size()), indent(0) { + suffix.append(str); +} + +void local_suffix::push() { + indent += 1; + suffix.push_back('\t'); +} + +void local_suffix::pop() { + assert(indent > 0); + if (indent > 0) { + indent -= 1; + suffix.pop_back(); + } +} + +local_suffix::~local_suffix() { suffix.erase(trim_pos); } + +void progress_canary(bool active) { + static chrono::time progress_timestamp; + chrono::time now = chrono::now_motonic(); + + if (now.fixedpoint - progress_timestamp.fixedpoint < + chrono::from_ms(42).fixedpoint) + return; + + if (osal_progress_push(active)) { + progress_timestamp = now; + return; + } + + if (progress_timestamp.fixedpoint == 0) { + putc('>', stderr); + progress_timestamp = now; + } else if (global::config::console_mode) { + if (active) { + static int last_point = -1; + int point = (now.fixedpoint >> 29) & 3; + if (point != last_point) { + progress_timestamp = now; + fprintf(stderr, "%c\b", "-\\|/"[last_point = point]); + } + } else if (now.fixedpoint - progress_timestamp.fixedpoint > + chrono::from_seconds(2).fixedpoint) { + progress_timestamp = now; + fprintf(stderr, "%c\b", "@*"[now.utc & 1]); + } + } else { + static int count; + if (active && now.fixedpoint - progress_timestamp.fixedpoint > + chrono::from_seconds(1).fixedpoint) { + putc('.', stderr); + progress_timestamp = now; + ++count; + } else if (now.fixedpoint - progress_timestamp.fixedpoint > + chrono::from_seconds(5).fixedpoint) { + putc("@*"[now.utc & 1], stderr); + progress_timestamp = now; + ++count; + } + if (count == 60) { + count = 0; + putc('\n', stderr); + } + } + fflush(stderr); +} + +} // namespace logging + +void log_extra(const char *msg, ...) { + if (logging::same_or_higher(logging::extra, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::extra, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_trace(const char *msg, ...) { + if (logging::same_or_higher(logging::trace, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::trace, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_debug(const char *msg, ...) { + if (logging::same_or_higher(logging::debug, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::debug, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_verbose(const char *msg, ...) { + if (logging::same_or_higher(logging::verbose, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::verbose, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_notice(const char *msg, ...) { + if (logging::same_or_higher(logging::notice, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::notice, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_warning(const char *msg, ...) { + if (logging::same_or_higher(logging::warning, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::warning, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_error(const char *msg, ...) { + if (logging::same_or_higher(logging::error, logging::level)) { + va_list ap; + va_start(ap, msg); + logging::output_nocheckloglevel_ap(logging::error, msg, ap); + va_end(ap); + } else + logging::last = nullptr; +} + +void log_trouble(const char *where, const char *what, int errnum) { + log_error("%s: %s %s", where, what, test_strerror(errnum)); +} + +bool log_enabled(const logging::loglevel priority) { + return logging::same_or_higher(priority, logging::level); +} + +void log_flush(void) { fflushall(); } diff --git a/contrib/db/libmdbx/test/log.h b/contrib/db/libmdbx/test/log.h new file mode 100644 index 00000000..bb8f997b --- /dev/null +++ b/contrib/db/libmdbx/test/log.h @@ -0,0 +1,104 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#include "base.h" +#include "chrono.h" + +void __noreturn usage(void); +void __noreturn __printf_args(1, 2) failure(const char *fmt, ...); +void __noreturn failure_perror(const char *what, int errnum); +const char *test_strerror(int errnum); + +namespace logging { + +enum loglevel { + extra = MDBX_LOG_EXTRA, + trace = MDBX_LOG_TRACE, + debug = MDBX_LOG_DEBUG, + verbose = MDBX_LOG_VERBOSE, + notice = MDBX_LOG_NOTICE, + warning = MDBX_LOG_WARN, + error = MDBX_LOG_ERROR, + failure = MDBX_LOG_FATAL +}; + +inline bool lower(loglevel left, loglevel right) { + static_assert(MDBX_LOG_EXTRA > MDBX_LOG_FATAL, "WTF?"); + return left > right; +} + +inline bool same_or_higher(loglevel left, loglevel right) { + return left <= right; +} + +const char *level2str(const loglevel level); +void setup(loglevel priority, const std::string &prefix); +void setup(const std::string &prefix); +void setlevel(loglevel priority); + +void output_nocheckloglevel_ap(const loglevel priority, const char *format, + va_list ap); +bool __printf_args(2, 3) + output(const loglevel priority, const char *format, ...); +bool feed_ap(const char *format, va_list ap); +bool __printf_args(1, 2) feed(const char *format, ...); + +void inline __printf_args(2, 3) + output_nocheckloglevel(const loglevel priority, const char *format, ...) { + va_list ap; + va_start(ap, format); + output_nocheckloglevel_ap(priority, format, ap); + va_end(ap); +} + +void progress_canary(bool active); + +class local_suffix { +protected: + size_t trim_pos; + int indent; + +public: + local_suffix(const local_suffix &) = delete; + local_suffix(const local_suffix &&) = delete; + const local_suffix &operator=(const local_suffix &) = delete; + + local_suffix(const char *c_str); + local_suffix(const std::string &str); + void push(); + void pop(); + ~local_suffix(); +}; + +} // namespace logging + +void __printf_args(1, 2) log_extra(const char *msg, ...); +void __printf_args(1, 2) log_trace(const char *msg, ...); +void __printf_args(1, 2) log_debug(const char *msg, ...); +void __printf_args(1, 2) log_verbose(const char *msg, ...); +void __printf_args(1, 2) log_notice(const char *msg, ...); +void __printf_args(1, 2) log_warning(const char *msg, ...); +void __printf_args(1, 2) log_error(const char *msg, ...); + +void log_trouble(const char *where, const char *what, int errnum); +void log_flush(void); +bool log_enabled(const logging::loglevel priority); + +#ifdef _DEBUG +#define TRACE(...) log_trace(__VA_ARGS__) +#else +#define TRACE(...) __noop(__VA_ARGS__) +#endif diff --git a/contrib/db/libmdbx/test/long_stochastic.sh b/contrib/db/libmdbx/test/long_stochastic.sh new file mode 100644 index 00000000..7b9f90fa --- /dev/null +++ b/contrib/db/libmdbx/test/long_stochastic.sh @@ -0,0 +1,220 @@ +#!/usr/bin/env bash +if ! which make cc c++ tee lz4 >/dev/null; then + echo "Please install the following prerequisites: make cc c++ tee lz4" >&2 + exit 1 +fi + +set -euo pipefail + +UNAME="$(uname -s 2>/dev/null || echo Unknown)" + +## NOTE: Valgrind could produce some false-positive warnings +## in multi-process environment with shared memory. +## For instance, when the process "A" explicitly marks a memory +## region as "undefined", the process "B" fill it, +## and after this process "A" read such region, etc. +#VALGRIND="valgrind --trace-children=yes --log-file=valgrind-%p.log --leak-check=full --track-origins=yes --error-exitcode=42 --suppressions=test/valgrind_suppress.txt" + +############################################################################### +# 1. clean data from prev runs and examine available RAM + +if [[ -v VALGRIND && ! -z "$VALGRIND" ]]; then + rm -f valgrind-*.log +else + VALGRIND=time +fi + +WANNA_MOUNT=0 +case ${UNAME} in + Linux) + MAKE=make + if [[ ! -v TESTDB_DIR || -z "$TESTDB_DIR" ]]; then + for old_test_dir in $(ls -d /dev/shm/mdbx-test.[0-9]*); do + rm -rf $old_test_dir + done + TESTDB_DIR="/dev/shm/mdbx-test.$$" + fi + mkdir -p $TESTDB_DIR && rm -f $TESTDB_DIR/* + + if LC_ALL=C free | grep -q -i available; then + ram_avail_mb=$(($(LC_ALL=C free | grep -i Mem: | tr -s [:blank:] ' ' | cut -d ' ' -f 7) / 1024)) + else + ram_avail_mb=$(($(LC_ALL=C free | grep -i Mem: | tr -s [:blank:] ' ' | cut -d ' ' -f 4) / 1024)) + fi + ;; + + FreeBSD) + MAKE=gmake + if [[ ! -v TESTDB_DIR || -z "$TESTDB_DIR" ]]; then + for old_test_dir in $(ls -d /tmp/mdbx-test.[0-9]*); do + umount $old_test_dir && rm -r $old_test_dir + done + TESTDB_DIR="/tmp/mdbx-test.$$" + rm -rf $TESTDB_DIR && mkdir -p $TESTDB_DIR + WANNA_MOUNT=1 + else + mkdir -p $TESTDB_DIR && rm -f $TESTDB_DIR/* + fi + + ram_avail_mb=$(($(LC_ALL=C vmstat -s | grep -ie '[0-9] pages free$' | cut -d p -f 1) * ($(LC_ALL=C vmstat -s | grep -ie '[0-9] bytes per page$' | cut -d b -f 1) / 1024) / 1024)) + ;; + + Darwin) + MAKE=make + if [[ ! -v TESTDB_DIR || -z "$TESTDB_DIR" ]]; then + for vol in $(ls -d /Volumes/mdx[0-9]*[0-9]tst); do + disk=$(mount | grep $vol | cut -d ' ' -f 1) + echo "umount: volume $vol disk $disk" + hdiutil unmount $vol -force + hdiutil detach $disk + done + TESTDB_DIR="/Volumes/mdx$$tst" + WANNA_MOUNT=1 + else + mkdir -p $TESTDB_DIR && rm -f $TESTDB_DIR/* + fi + + pagesize=$(($(LC_ALL=C vm_stat | grep -o 'page size of [0-9]\+ bytes' | cut -d' ' -f 4) / 1024)) + freepages=$(LC_ALL=C vm_stat | grep '^Pages free:' | grep -o '[0-9]\+\.$' | cut -d'.' -f 1) + ram_avail_mb=$((pagesize * freepages / 1024)) + echo "pagesize ${pagesize}K, freepages ${freepages}, ram_avail_mb ${ram_avail_mb}" + + ;; + + *) + echo "FIXME: ${UNAME} not supported by this script" + exit 2 + ;; +esac + +############################################################################### +# 2. estimate reasonable RAM space for test-db + +echo "=== ${ram_avail_mb}M RAM available" +ram_reserve4logs_mb=1234 +if [ $ram_avail_mb -lt $ram_reserve4logs_mb ]; then + echo "=== At least ${ram_reserve4logs_mb}Mb RAM required" + exit 3 +fi + +# +# В режимах отличных от MDBX_WRITEMAP изменения до записи в файл +# будут накапливаться в памяти, что может потребовать свободной +# памяти размером с БД. Кроме этого, в тест входит сценарий +# создания копия БД на ходу. Поэтому БД не может быть больше 1/3 +# от доступной памяти. Однако, следует учесть что malloc() будет +# не сразу возвращать выделенную память системе, а также +# предусмотреть места для логов. +# +# In non-MDBX_WRITEMAP modes, updates (dirty pages) will +# accumulate in memory before writing to the disk, which may +# require a free memory up to the size of a whole database. In +# addition, the test includes a script create a copy of the +# database on the go. Therefore, the database cannot be more 1/3 +# of available memory. Moreover, should be taken into account +# that malloc() will not return the allocated memory to the +# system immediately, as well some space is required for logs. +# +db_size_mb=$(((ram_avail_mb - ram_reserve4logs_mb) / 4)) +if [ $db_size_mb -gt 3072 ]; then + db_size_mb=3072 +fi +echo "=== use ${db_size_mb}M for DB" + +############################################################################### +# 3. Create test-directory in ramfs/tmpfs, i.e. create/format/mount if required +case ${UNAME} in + Linux) + ;; + + FreeBSD) + if [[ WANNA_MOUNT ]]; then + mount -t tmpfs tmpfs $TESTDB_DIR + fi + ;; + + Darwin) + if [[ WANNA_MOUNT ]]; then + ramdisk_size_mb=$((42 + db_size_mb * 2 + ram_reserve4logs_mb)) + number_of_sectors=$((ramdisk_size_mb * 2048)) + ramdev=$(hdiutil attach -nomount ram://${number_of_sectors}) + diskutil erasevolume ExFAT "mdx$$tst" ${ramdev} + fi + ;; + + *) + echo "FIXME: ${UNAME} not supported by this script" + exit 2 + ;; +esac + +############################################################################### +# 4. Run basic test, i.e. `make check` + +${MAKE} TEST_DB=${TESTDB_DIR}/smoke.db TEST_LOG=${TESTDB_DIR}/smoke.log check +rm -f ${TESTDB_DIR}/* + +############################################################################### +# 5. run stochastic iterations + +function rep9 { printf "%*s" $1 '' | tr ' ' '9'; } +function join { local IFS="$1"; shift; echo "$*"; } +function bit2option { local -n arr=$1; (( ($2&(1<<$3)) != 0 )) && echo -n '+' || echo -n '-'; echo "${arr[$3]}"; } + +options=(writemap coalesce lifo) + +function bits2list { + local -n arr=$1 + local i + local list=() + for ((i=0; i<${#arr[@]}; ++i)) do + list[$i]=$(bit2option $1 $2 $i) + done + join , "${list[@]}" +} + +function probe { + echo "=============================================== $(date)" + echo "${caption}: $*" + rm -f ${TESTDB_DIR}/* \ + && ${VALGRIND} ./mdbx_test --ignore-dbfull --repeat=42 --pathname=${TESTDB_DIR}/long.db "$@" | lz4 > ${TESTDB_DIR}/long.log.lz4 \ + && ${VALGRIND} ./mdbx_chk -nvvv ${TESTDB_DIR}/long.db | tee ${TESTDB_DIR}/long-chk.log \ + && ([ ! -e ${TESTDB_DIR}/long.db-copy ] || ${VALGRIND} ./mdbx_chk -nvvv ${TESTDB_DIR}/long.db-copy | tee ${TESTDB_DIR}/long-chk-copy.log) \ + || (echo "FAILED"; exit 1) +} + +#------------------------------------------------------------------------------ + +count=0 +for nops in $(seq 2 6); do + for ((wbatch=nops-1; wbatch > 0; --wbatch)); do + loops=$(((111 >> nops) / nops + 3)) + for ((rep=0; rep++ < loops; )); do + for ((bits=2**${#options[@]}; --bits >= 0; )); do + seed=$(($(date +%s) + RANDOM)) + caption="Probe #$((++count)) int-key,w/o-dups, repeat ${rep} of ${loops}" probe \ + --pagesize=min --size-upper=${db_size_mb}M --table=+key.integer,-data.dups --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=1111 \ + --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \ + --keygen.seed=${seed} basic + caption="Probe #$((++count)) int-key,with-dups, repeat ${rep} of ${loops}" probe \ + --pagesize=min --size-upper=${db_size_mb}M --table=+key.integer,+data.dups --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=max \ + --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \ + --keygen.seed=${seed} basic + caption="Probe #$((++count)) int-key,int-data, repeat ${rep} of ${loops}" probe \ + --pagesize=min --size-upper=${db_size_mb}M --table=+key.integer,+data.integer --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=max \ + --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \ + --keygen.seed=${seed} basic + caption="Probe #$((++count)) w/o-dups, repeat ${rep} of ${loops}" probe \ + --pagesize=min --size-upper=${db_size_mb}M --table=-data.dups --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=1111 \ + --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \ + --keygen.seed=${seed} basic + caption="Probe #$((++count)) with-dups, repeat ${rep} of ${loops}" probe \ + --pagesize=min --size-upper=${db_size_mb}M --table=+data.dups --keylen.min=min --keylen.max=max --datalen.min=min --datalen.max=max \ + --nops=$( rep9 $nops ) --batch.write=$( rep9 $wbatch ) --mode=$(bits2list options $bits) \ + --keygen.seed=${seed} basic + done + done + done +done + +echo "=== ALL DONE ====================== $(date)" diff --git a/contrib/db/libmdbx/test/main.cc b/contrib/db/libmdbx/test/main.cc new file mode 100644 index 00000000..fe5243ec --- /dev/null +++ b/contrib/db/libmdbx/test/main.cc @@ -0,0 +1,617 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +#if !(defined(_WIN32) || defined(_WIN64)) +#include +#include +#endif /* !Windows */ + +void __noreturn usage(void) { + puts( + "usage:\n" + " --help or -h Show this text\n" + "Common parameters:\n" + " --pathname=... Path and/or name of database files\n" + " --repeat=N Set repeat counter\n" + " --threads=N Number of thread (unsunpported for now)\n" + " --timeout=N[s|m|h|d] Set timeout in seconds/minutes/hours/days\n" + " --failfast[=YES/no] Lill all actors on first failure/error\n" + " --max-readers=N See mdbx_env_set_maxreaders() description\n" + " --max-tables=N Se mdbx_env_set_maxdbs() description\n" + " --dump-config[=YES/no] Dump entire test config before run\n" + " --progress[=YES/no] Enable/disable progress `canary`\n" + " --console[=yes/no] Enable/disable console-like output\n" + " --cleanup-before[=YES/no] Cleanup/remove and re-create database\n" + " --cleanup-after[=YES/no] Cleanup/remove database after completion\n" + "Database size control:\n" + " --pagesize=... Database page size: min, max, 256..65536\n" + " --size-lower=N[K|M|G|T] Lower-bound of size in Kb/Mb/Gb/Tb\n" + " --size-upper Upper-bound of size in Kb/Mb/Gb/Tb\n" + " --size Initial size in Kb/Mb/Gb/Tb\n" + " --shrink-threshold Shrink threshold in Kb/Mb/Gb/Tb\n" + " --growth-step Grow step in Kb/Mb/Gb/Tb\n" + "Predefined complext scenarios/cases:\n" + " --case=... Only `basic` scenario implemented for now\n" + " basic == Simultaneous multi-process execution\n" + " of test-actors: nested,hill,ttl,copy,append,jitter,try\n" + "Test actors:\n" + " --hill Fill-up and empty-down\n" + " by CRUD-operation quads\n" + " --ttl Stochastic time-to-live simulation\n" + " --nested Nested transactionы\n" + " with stochastic-size bellows\n" + " --jitter Jitter/delays simulation\n" + " --try Try write-transaction, no more\n" + " --copy Online copy/backup\n" + " --append Append-mode insertions\n" + " --dead.reader Dead-reader simulator\n" + " --dead.writer Dead-writer simulator\n" + "Actor options:\n" + " --batch.read=N Read-operations batch size\n" + " --batch.write=N Write-operations batch size\n" + " --delay=N | --no-delay (no)Delay test-actor before start\n" + " --wait4ops=N | --no-wait4ops (no)Wait for previous test-actor\n" + " completes # ops before start\n" + " --duration=N[s|m|h|d] Define running duration\n" + " --nops=N[K|M|G|T] Define number of operations/steps\n" + " --inject-writefault[=yes|NO] TBD (see the source code)\n" + " --drop[=yes|NO] Drop key-value space/table on " + "completion\n" + " --ignore-dbfull[=yes|NO] Ignore MDBX_MAP_FULL error\n" + " --speculum[=yes|NO] Use internal `speculum` to check " + "dataset\n" + "Keys and Value:\n" + " --keygen.min=N Minimal keys length\n" + " --keygen.max=N Miximal keys length\n" + " --datalen.min=N Minimal data length\n" + " --datalen.max=N Miximal data length\n" + " --keygen.width=N TBD (see the source code)\n" + " --keygen.mesh=N TBD (see the source code)\n" + " --keygen.seed=N TBD (see the source code)\n" + " --keygen.split=N TBD (see the source code)\n" + " --keygen.rotate=N TBD (see the source code)\n" + " --keygen.offset=N TBD (see the source code)\n" + " --keygen.case=random Generator case (only `random` for now)\n" + "Database operation mode:\n" + " --mode={[+-]FLAG}[,[+-]FLAG]...\n" + " nosubdir == MDBX_NOSUBDIR\n" + " rdonly == MDBX_RDONLY\n" + " nometasync == MDBX_NOMETASYNC\n" + " lifo == MDBX_LIFORECLAIM\n" + " coalesce == MDBX_COALESCE\n" + " nosync == MDBX_NOSYNC\n" + " writemap == MDBX_WRITEMAP\n" + " mapasync == MDBX_MAPASYNC\n" + " utterly == MDBX_UTTERLY_NOSYNC\n" + " perturb == MDBX_PAGEPERTURB\n" + " notls == MDBX_NOTLS\n" + " nordahead == MDBX_NORDAHEAD\n" + " nomeminit == MDBX_NOMEMINIT\n" + "Key-value space/table options:\n" + " --table={[+-]FLAG}[,[+-]FLAG]...\n" + " key.reverse == MDBX_REVERSEKEY\n" + " key.integer == MDBX_INTEGERKEY\n" + " data.dups == MDBX_DUPSORT\n" + " data.integer == MDBX_INTEGERDUP | MDBX_DUPFIXED | MDBX_DUPSORT\n" + " data.fixed == MDBX_DUPFIXED | MDBX_DUPSORT\n" + " data.reverse == MDBX_REVERSEDUP | MDBX_DUPSORT\n"); + exit(EXIT_FAILURE); +} + +//----------------------------------------------------------------------------- + +void actor_params::set_defaults(const std::string &tmpdir) { + pathname_log = ""; + loglevel = +#if defined(NDEBUG) || defined(_WIN32) || defined(_WIN64) + logging::verbose; +#else + logging::trace; +#endif + + pathname_db = tmpdir + "mdbx-test.db"; + mode_flags = MDBX_NOSUBDIR | MDBX_WRITEMAP | MDBX_MAPASYNC | MDBX_NOMEMINIT | + MDBX_COALESCE | MDBX_LIFORECLAIM; + table_flags = MDBX_DUPSORT; + + size_lower = -1; + size_now = + intptr_t(1024) * 1024 * ((table_flags & MDBX_DUPSORT) ? 256 : 1024); + size_upper = -1; + shrink_threshold = -1; + growth_step = -1; + pagesize = -1; + + keygen.seed = 1; + keygen.keycase = kc_random; + keygen.width = (table_flags & MDBX_DUPSORT) ? 32 : 64; + keygen.mesh = keygen.width; + keygen.split = keygen.width / 2; + keygen.rotate = 3; + keygen.offset = 41; + + test_duration = 0; + test_nops = 1000; + nrepeat = 1; + nthreads = 1; + + keylen_min = mdbx_keylen_min(); + keylen_max = mdbx_keylen_max(); + datalen_min = mdbx_datalen_min(); + datalen_max = std::min(mdbx_datalen_max(), 256u * 1024 + 42); + + batch_read = 42; + batch_write = 42; + + delaystart = 0; + waitfor_nops = 0; + inject_writefaultn = 0; + + drop_table = false; + ignore_dbfull = false; + speculum = false; + + max_readers = 42; + max_tables = 42; + + global::config::timeout_duration_seconds = 0 /* infinite */; + global::config::dump_config = true; + global::config::cleanup_before = true; + global::config::cleanup_after = true; + global::config::failfast = true; + global::config::progress_indicator = true; + global::config::console_mode = osal_istty(STDERR_FILENO); +} + +namespace global { + +std::vector actors; +std::unordered_map events; +std::unordered_map pid2actor; +std::set databases; +unsigned nactors; +chrono::time start_motonic; +chrono::time deadline_motonic; +bool singlemode; + +namespace config { +unsigned timeout_duration_seconds; +bool dump_config; +bool cleanup_before; +bool cleanup_after; +bool failfast; +bool progress_indicator; +bool console_mode; +} /* namespace config */ + +} /* namespace global */ + +//----------------------------------------------------------------------------- + +const char global::thunk_param_prefix[] = "--execute="; + +std::string thunk_param(const actor_config &config) { + return config.serialize(global::thunk_param_prefix); +} + +void cleanup() { + log_trace(">> cleanup"); + /* TODO: remove each database */ + log_trace("<< cleanup"); +} + +int main(int argc, char *const argv[]) { + +#ifdef _DEBUG + log_trace("#argc = %d", argc); + for (int i = 0; i < argc; ++i) + log_trace("#argv[%d] = %s", i, argv[i]); +#endif /* _DEBUG */ + + if (argc < 2) + failure("No parameters given. Try --help\n"); + + if (argc == 2 && strncmp(argv[1], global::thunk_param_prefix, + strlen(global::thunk_param_prefix)) == 0) + return test_execute( + actor_config(argv[1] + strlen(global::thunk_param_prefix))) + ? EXIT_SUCCESS + : EXIT_FAILURE; + + if (argc == 2 && + (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) + usage(); + + actor_params params; + params.set_defaults(osal_tempdir()); + global::config::dump_config = true; + logging::setup((logging::loglevel)params.loglevel, "main"); + unsigned last_space_id = 0; + + for (int narg = 1; narg < argc; ++narg) { + const char *value = nullptr; + + if (config::parse_option(argc, argv, narg, "case", &value)) { + testcase_setup(value, params, last_space_id); + continue; + } + if (config::parse_option(argc, argv, narg, "pathname", params.pathname_db)) + continue; + if (config::parse_option(argc, argv, narg, "mode", params.mode_flags, + config::mode_bits)) + continue; + if (config::parse_option(argc, argv, narg, "table", params.table_flags, + config::table_bits)) { + if ((params.table_flags & MDBX_DUPFIXED) == 0) + params.table_flags &= ~MDBX_INTEGERDUP; + if ((params.table_flags & MDBX_DUPSORT) == 0) + params.table_flags &= + ~(MDBX_DUPFIXED | MDBX_REVERSEDUP | MDBX_INTEGERDUP); + continue; + } + + if (config::parse_option(argc, argv, narg, "pagesize", params.pagesize, + int(mdbx_limits_pgsize_min()), + int(mdbx_limits_pgsize_max()))) { + const unsigned keylen_max = params.mdbx_keylen_max(); + if (params.keylen_min > keylen_max) + params.keylen_min = keylen_max; + if (params.keylen_max > keylen_max) + params.keylen_max = keylen_max; + const unsigned datalen_max = params.mdbx_datalen_max(); + if (params.datalen_min > datalen_max) + params.datalen_min = datalen_max; + if (params.datalen_max > datalen_max) + params.datalen_max = datalen_max; + continue; + } + if (config::parse_option(argc, argv, narg, "repeat", params.nrepeat, + config::no_scale)) + continue; + if (config::parse_option(argc, argv, narg, "threads", params.nthreads, + config::no_scale, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "timeout", + global::config::timeout_duration_seconds, + config::duration, 1)) + continue; + + if (config::parse_option_intptr(argc, argv, narg, "size-lower", + params.size_lower, + mdbx_limits_dbsize_min(params.pagesize), + mdbx_limits_dbsize_max(params.pagesize))) + continue; + if (config::parse_option_intptr(argc, argv, narg, "size-upper", + params.size_upper, + mdbx_limits_dbsize_min(params.pagesize), + mdbx_limits_dbsize_max(params.pagesize))) + continue; + if (config::parse_option_intptr(argc, argv, narg, "size", params.size_now, + mdbx_limits_dbsize_min(params.pagesize), + mdbx_limits_dbsize_max(params.pagesize))) + continue; + if (config::parse_option( + argc, argv, narg, "shrink-threshold", params.shrink_threshold, 0, + (int)std::min((intptr_t)INT_MAX, + mdbx_limits_dbsize_max(params.pagesize) - + mdbx_limits_dbsize_min(params.pagesize)))) + continue; + if (config::parse_option( + argc, argv, narg, "growth-step", params.growth_step, 0, + (int)std::min((intptr_t)INT_MAX, + mdbx_limits_dbsize_max(params.pagesize) - + mdbx_limits_dbsize_min(params.pagesize)))) + continue; + + if (config::parse_option(argc, argv, narg, "keygen.width", + params.keygen.width, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "keygen.mesh", + params.keygen.mesh, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "keygen.seed", + params.keygen.seed, config::no_scale)) + continue; + if (config::parse_option(argc, argv, narg, "keygen.split", + params.keygen.split, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "keygen.rotate", + params.keygen.rotate, 1, 64)) + continue; + if (config::parse_option(argc, argv, narg, "keygen.offset", + params.keygen.offset, config::binary)) + continue; + if (config::parse_option(argc, argv, narg, "keygen.case", &value)) { + keycase_setup(value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "keylen.min", params.keylen_min, + config::no_scale, params.mdbx_keylen_min(), + params.mdbx_keylen_max())) { + if ((params.table_flags & MDBX_INTEGERKEY) || + params.keylen_max < params.keylen_min) + params.keylen_max = params.keylen_min; + continue; + } + if (config::parse_option(argc, argv, narg, "keylen.max", params.keylen_max, + config::no_scale, params.mdbx_keylen_min(), + params.mdbx_keylen_max())) { + if ((params.table_flags & MDBX_INTEGERKEY) || + params.keylen_min > params.keylen_max) + params.keylen_min = params.keylen_max; + continue; + } + if (config::parse_option(argc, argv, narg, "datalen.min", + params.datalen_min, config::no_scale, + params.mdbx_datalen_min(), + params.mdbx_datalen_max())) { + if ((params.table_flags & MDBX_DUPFIXED) || + params.datalen_max < params.datalen_min) + params.datalen_max = params.datalen_min; + continue; + } + if (config::parse_option(argc, argv, narg, "datalen.max", + params.datalen_max, config::no_scale, + params.mdbx_datalen_min(), + params.mdbx_datalen_max())) { + if ((params.table_flags & MDBX_DUPFIXED) || + params.datalen_min > params.datalen_max) + params.datalen_min = params.datalen_max; + continue; + } + if (config::parse_option(argc, argv, narg, "batch.read", params.batch_read, + config::no_scale, 1)) + continue; + if (config::parse_option(argc, argv, narg, "batch.write", + params.batch_write, config::no_scale, 1)) + continue; + if (config::parse_option(argc, argv, narg, "delay", params.delaystart, + config::duration)) + continue; + if (config::parse_option(argc, argv, narg, "wait4ops", params.waitfor_nops, + config::decimal)) + continue; + if (config::parse_option(argc, argv, narg, "inject-writefault", + params.inject_writefaultn, config::decimal)) + continue; + if (config::parse_option(argc, argv, narg, "drop", params.drop_table)) + continue; + if (config::parse_option(argc, argv, narg, "ignore-dbfull", + params.ignore_dbfull)) + continue; + if (config::parse_option(argc, argv, narg, "speculum", params.speculum)) + continue; + if (config::parse_option(argc, argv, narg, "dump-config", + global::config::dump_config)) + continue; + if (config::parse_option(argc, argv, narg, "cleanup-before", + global::config::cleanup_before)) + continue; + if (config::parse_option(argc, argv, narg, "cleanup-after", + global::config::cleanup_after)) + continue; + if (config::parse_option(argc, argv, narg, "max-readers", + params.max_readers, config::no_scale, 1, 255)) + continue; + if (config::parse_option(argc, argv, narg, "max-tables", params.max_tables, + config::no_scale, 1, INT16_MAX)) + continue; + + if (config::parse_option(argc, argv, narg, "no-delay", nullptr)) { + params.delaystart = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "no-wait4ops", nullptr)) { + params.waitfor_nops = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "duration", params.test_duration, + config::duration, 1)) { + params.test_nops = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "nops", params.test_nops, + config::decimal, 1)) { + params.test_duration = 0; + continue; + } + if (config::parse_option(argc, argv, narg, "hill", &value, "auto")) { + configure_actor(last_space_id, ac_hill, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "jitter", nullptr)) { + configure_actor(last_space_id, ac_jitter, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "dead.reader", nullptr)) { + configure_actor(last_space_id, ac_deadread, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "dead.writer", nullptr)) { + configure_actor(last_space_id, ac_deadwrite, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "try", nullptr)) { + configure_actor(last_space_id, ac_try, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "copy", nullptr)) { + configure_actor(last_space_id, ac_copy, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "append", nullptr)) { + configure_actor(last_space_id, ac_append, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "ttl", nullptr)) { + configure_actor(last_space_id, ac_ttl, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "nested", nullptr)) { + configure_actor(last_space_id, ac_nested, value, params); + continue; + } + if (config::parse_option(argc, argv, narg, "failfast", + global::config::failfast)) + continue; + if (config::parse_option(argc, argv, narg, "progress", + global::config::progress_indicator)) + continue; + if (config::parse_option(argc, argv, narg, "console", + global::config::console_mode)) + continue; + + if (*argv[narg] != '-') + testcase_setup(argv[narg], params, last_space_id); + else + failure("Unknown option '%s'. Try --help\n", argv[narg]); + } + + if (global::config::dump_config) + config::dump(); + + //-------------------------------------------------------------------------- + + if (global::actors.empty()) { + log_notice("no testcase(s) configured, exiting"); + return EXIT_SUCCESS; + } + + bool failed = false; + global::start_motonic = chrono::now_motonic(); + global::deadline_motonic.fixedpoint = + (global::config::timeout_duration_seconds == 0) + ? chrono::infinite().fixedpoint + : global::start_motonic.fixedpoint + + chrono::from_seconds(global::config::timeout_duration_seconds) + .fixedpoint; + + if (global::config::cleanup_before) + cleanup(); + + log_trace(">> probe entropy_ticks()"); + entropy_ticks(); + log_trace("<< probe entropy_ticks()"); + + if (global::actors.size() == 1) { + logging::setup("main"); + global::singlemode = true; + if (!test_execute(global::actors.front())) + failed = true; + } else { + logging::setup("overlord"); + + log_trace("=== preparing..."); + log_trace(">> osal_setup"); + osal_setup(global::actors); + log_trace("<< osal_setup"); + + for (auto &a : global::actors) { + mdbx_pid_t pid; + log_trace(">> actor_start"); + int rc = osal_actor_start(a, pid); + log_trace("<< actor_start"); + if (rc) { + log_trace(">> killall_actors: (%s)", "start failed"); + osal_killall_actors(); + log_trace("<< killall_actors"); + failure("Failed to start actor #%u (%s)\n", a.actor_id, + test_strerror(rc)); + } + global::pid2actor[pid] = &a; + } + + log_trace("=== ready to start..."); + atexit(osal_killall_actors); + log_trace(">> wait4barrier"); + osal_wait4barrier(); + log_trace("<< wait4barrier"); + + size_t left = global::actors.size(); + log_trace("=== polling..."); + while (left > 0) { + unsigned timeout_seconds_left = INT_MAX; + chrono::time now_motonic = chrono::now_motonic(); + if (now_motonic.fixedpoint >= global::deadline_motonic.fixedpoint) + timeout_seconds_left = 0; + else { + chrono::time left_motonic; + left_motonic.fixedpoint = + global::deadline_motonic.fixedpoint - now_motonic.fixedpoint; + timeout_seconds_left = left_motonic.seconds(); + } + + mdbx_pid_t pid; + int rc = osal_actor_poll(pid, timeout_seconds_left); + if (rc) + failure("Poll error: %s (%d)\n", test_strerror(rc), rc); + + if (pid) { + actor_status status = osal_actor_info(pid); + actor_config *actor = global::pid2actor.at(pid); + if (!actor) + continue; + + log_verbose("actor #%u, id %d, pid %u: %s\n", actor->actor_id, + actor->space_id, pid, status2str(status)); + if (status > as_running) { + left -= 1; + if (status != as_successful) { + if (global::config::failfast && !failed) { + log_trace(">> killall_actors: (%s)", "failfast"); + osal_killall_actors(); + log_trace("<< killall_actors"); + } + failed = true; + } + } + } else { + if (timeout_seconds_left == 0) + failure("Timeout\n"); + } + } + log_trace("=== done..."); + } + + log_notice("RESULT: %s\n", failed ? "Failed" : "Successful"); + if (global::config::cleanup_before) { + if (failed) + log_verbose("skip cleanup"); + else + cleanup(); + } + +#if !(defined(_WIN32) || defined(_WIN64)) + struct rusage spent; + if (getrusage(RUSAGE_CHILDREN, &spent) == 0) { + log_notice("%6s: user %f, system %f", "CPU", + spent.ru_utime.tv_sec + spent.ru_utime.tv_usec * 1e-6, + spent.ru_stime.tv_sec + spent.ru_stime.tv_usec * 1e-6); +#if defined(__linux__) || defined(__gnu_linux__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) || defined(__BSD__) || \ + defined(__NETBSD__) || defined(__bsdi__) || defined(__DragonFly__) || \ + defined(__APPLE__) || defined(__MACH__) + log_notice("%6s: read %ld, write %ld", "IOPs", spent.ru_inblock, + spent.ru_oublock); + log_notice("%6s: %ld Kb", "RAM", spent.ru_maxrss); + log_notice("%6s: reclaims %ld, faults %ld, swaps %ld", "Paging", + spent.ru_minflt, spent.ru_majflt, spent.ru_nswap); +#endif /* Linux */ + } +#endif /* !Windows */ + + return failed ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/contrib/db/libmdbx/test/nested.cc b/contrib/db/libmdbx/test/nested.cc new file mode 100644 index 00000000..e02fe8b1 --- /dev/null +++ b/contrib/db/libmdbx/test/nested.cc @@ -0,0 +1,284 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" +#include + +bool testcase_nested::setup() { + if (!inherited::setup()) + return false; + int err = db_open__begin__table_create_open_clean(dbi); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("nested: bailout-prepare due '%s'", mdbx_strerror(err)); + return false; + } + + keyvalue_maker.setup(config.params, config.actor_id, 0 /* thread_number */); + key = keygen::alloc(config.params.keylen_max); + data = keygen::alloc(config.params.datalen_max); + serial = 0; + fifo.clear(); + speculum.clear(); + assert(stack.empty()); + stack.emplace(nullptr, serial, fifo, speculum); + return true; +} + +bool testcase_nested::teardown() { + while (!stack.empty()) + pop_txn(true); + + bool ok = true; + if (dbi) { + if (config.params.drop_table && !mode_readonly()) { + txn_begin(false); + db_table_drop(dbi); + int err = breakable_commit(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("nested: bailout-clean due '%s'", mdbx_strerror(err)); + ok = false; + } + } else + db_table_close(dbi); + dbi = 0; + } + return inherited::teardown() && ok; +} + +static unsigned edge2window(uint64_t edge, unsigned window_max) { + const double rnd = u64_to_double1(bleach64(edge)); + const unsigned window = window_max - std::lrint(std::pow(window_max, rnd)); + return window; +} + +static unsigned edge2count(uint64_t edge, unsigned count_max) { + const double rnd = u64_to_double1(prng64_map1_white(edge)); + const unsigned count = std::lrint(std::pow(count_max, rnd)); + return count; +} + +void testcase_nested::push_txn() { + MDBX_txn *txn; + int err = mdbx_txn_begin( + db_guard.get(), txn_guard.get(), + prng32() & (MDBX_NOSYNC | MDBX_NOMETASYNC | MDBX_MAPASYNC), &txn); + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_txn_begin(nested)", err); +#if __cplusplus >= 201703L + stack.emplace(txn, serial, fifo, speculum); +#else + stack.push(std::make_tuple(scoped_txn_guard(txn), serial, fifo, speculum)); +#endif + std::swap(txn_guard, std::get<0>(stack.top())); + log_verbose("begin level#%zu txn, serial %" PRIu64, stack.size(), serial); +} + +bool testcase_nested::pop_txn(bool abort) { + assert(txn_guard && !stack.empty()); + bool should_continue = true; + MDBX_txn *txn = txn_guard.release(); + bool commited = false; + if (abort) { + log_verbose("abort level#%zu txn, undo serial %" PRIu64 " <- %" PRIu64, + stack.size(), serial, std::get<1>(stack.top())); + int err = mdbx_txn_abort(txn); + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_txn_abort()", err); + } else { + log_verbose("commit level#%zu txn, nested serial %" PRIu64 " -> %" PRIu64, + stack.size(), serial, std::get<1>(stack.top())); + int err = mdbx_txn_commit(txn); + if (likely(err == MDBX_SUCCESS)) + commited = true; + else { + should_continue = false; + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + err = mdbx_txn_abort(txn); + if (unlikely(err != MDBX_SUCCESS && err != MDBX_THREAD_MISMATCH && + err != MDBX_BAD_TXN)) + failure_perror("mdbx_txn_abort()", err); + } else + failure_perror("mdbx_txn_commit()", err); + } + } + + std::swap(txn_guard, std::get<0>(stack.top())); + if (!commited) { + serial = std::get<1>(stack.top()); + std::swap(fifo, std::get<2>(stack.top())); + std::swap(speculum, std::get<3>(stack.top())); + } + stack.pop(); + return should_continue; +} + +bool testcase_nested::stochastic_breakable_restart_with_nested( + bool force_restart) { + log_trace(">> stochastic_breakable_restart_with_nested%s", + force_restart ? ": force_restart" : ""); + + if (force_restart) + while (txn_guard) + pop_txn(true); + + bool should_continue = true; + while (!stack.empty() && + (flipcoin() || txn_underutilization_x256(txn_guard.get()) < 42)) + should_continue &= pop_txn(); + + if (should_continue) + while (stack.empty() || + (is_nested_txn_available() && flipcoin() && stack.size() < 5)) + push_txn(); + + log_trace("<< stochastic_breakable_restart_with_nested: should_continue=%s", + should_continue ? "yes" : "no"); + return should_continue; +} + +bool testcase_nested::trim_tail(unsigned window_width) { + if (window_width) { + while (fifo.size() > window_width) { + uint64_t tail_serial = fifo.back().first; + const unsigned tail_count = fifo.back().second; + log_trace("nested: pop-tail (serial %" PRIu64 ", count %u)", tail_serial, + tail_count); + fifo.pop_back(); + for (unsigned n = 0; n < tail_count; ++n) { + log_trace("nested: remove-tail %" PRIu64, tail_serial); + generate_pair(tail_serial); + int err = remove(key, data); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("nested: tail-bailout due '%s'", mdbx_strerror(err)); + return false; + } + failure_perror("mdbx_del(tail)", err); + } + if (unlikely(!keyvalue_maker.increment(tail_serial, 1))) + failure("nested: unexpected key-space overflow on the tail"); + } + } + } else { + log_trace("nested: purge state"); + db_table_clear(dbi, txn_guard.get()); + fifo.clear(); + speculum.clear(); + } + return true; +} + +bool testcase_nested::grow_head(unsigned head_count) { + const unsigned insert_flags = (config.params.table_flags & MDBX_DUPSORT) + ? MDBX_NODUPDATA + : MDBX_NODUPDATA | MDBX_NOOVERWRITE; +retry: + fifo.push_front(std::make_pair(serial, head_count)); + for (unsigned n = 0; n < head_count; ++n) { + log_trace("nested: insert-head %" PRIu64, serial); + generate_pair(serial); + int err = insert(key, data, insert_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("nested: head-insert skip due '%s'", mdbx_strerror(err)); + head_count = n; + stochastic_breakable_restart_with_nested(true); + goto retry; + } + failure_perror("mdbx_put(head)", err); + } + + if (unlikely(!keyvalue_maker.increment(serial, 1))) { + log_notice("nested: unexpected key-space overflow"); + return false; + } + } + + return true; +} + +bool testcase_nested::run() { + /* LY: тест "эмуляцией time-to-live" с вложенными транзакциями: + * - организуется "скользящее окно", которое каждую транзакцию сдвигается + * вперед вдоль числовой оси. + * - по переднему краю "скользящего окна" записи добавляются в таблицу, + * а по заднему удаляются. + * - количество добавляемых/удаляемых записей псевдослучайно зависит + * от номера транзакции, но с экспоненциальным распределением. + * - размер "скользящего окна" также псевдослучайно зависит от номера + * транзакции с "отрицательным" экспоненциальным распределением + * MAX_WIDTH - exp(rnd(N)), при уменьшении окна сдвигается задний + * край и удаляются записи позади него. + * - групповое добавление данных в начало окна и групповое уделение в конце, + * в половине случаев выполняются во вложенных транзакциях. + * - половина запускаемых вложенных транзакций отменяется, последуюим + * повтором групповой операции. + * + * Таким образом имитируется поведение таблицы с TTL: записи стохастически + * добавляются и удаляются, но изредка происходят массивные удаления. */ + + /* LY: для параметризации используем подходящие параметры, которые не имеют + * здесь смысла в первоначальном значении. */ + const unsigned window_max_lower = 333; + const unsigned count_max_lower = 333; + + const unsigned window_max = (config.params.batch_read > window_max_lower) + ? config.params.batch_read + : window_max_lower; + const unsigned count_max = (config.params.batch_write > count_max_lower) + ? config.params.batch_write + : count_max_lower; + log_verbose("nested: using `batch_read` value %u for window_max", window_max); + log_verbose("nested: using `batch_write` value %u for count_max", count_max); + + uint64_t seed = + prng64_map2_white(config.params.keygen.seed) + config.actor_id; + + while (should_continue()) { + const uint64_t salt = prng64_white(seed) /* mdbx_txn_id(txn_guard.get()) */; + const unsigned window_width = + flipcoin_x4() ? 0 : edge2window(salt, window_max); + const unsigned head_count = edge2count(salt, count_max); + log_debug("nested: step #%zu (serial %" PRIu64 + ", window %u, count %u) salt %" PRIu64, + nops_completed, serial, window_width, head_count, salt); + + if (!trim_tail(window_width)) + return false; + if (!stochastic_breakable_restart_with_nested()) { + log_notice("nested: bailout at commit/restart after tail-trim"); + return false; + } + if (!speculum_verify()) { + log_notice("nested: bailout after tail-trim"); + return false; + } + + if (!grow_head(head_count)) + return false; + if (!stochastic_breakable_restart_with_nested()) + log_notice("nested: skip commit/restart after head-grow"); + if (!speculum_verify()) { + log_notice("nested: bailout after head-grow"); + return false; + } + + report(1); + } + + while (!stack.empty()) + pop_txn(false); + + return speculum_verify(); +} diff --git a/contrib/db/libmdbx/test/osal-unix.cc b/contrib/db/libmdbx/test/osal-unix.cc new file mode 100644 index 00000000..d954ceac --- /dev/null +++ b/contrib/db/libmdbx/test/osal-unix.cc @@ -0,0 +1,368 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include "darwin/pthread_barrier.c" +#endif + +struct shared_t { + pthread_barrier_t barrier; + pthread_mutex_t mutex; + size_t conds_size; + pthread_cond_t conds[1]; +}; + +static shared_t *shared; + +void osal_wait4barrier(void) { + assert(shared != nullptr && shared != MAP_FAILED); + int rc = pthread_barrier_wait(&shared->barrier); + if (rc != 0 && rc != PTHREAD_BARRIER_SERIAL_THREAD) { + failure_perror("pthread_barrier_wait(shared)", rc); + } +} + +void osal_setup(const std::vector &actors) { + assert(shared == nullptr); + + pthread_mutexattr_t mutexattr; + int rc = pthread_mutexattr_init(&mutexattr); + if (rc) + failure_perror("pthread_mutexattr_init()", rc); + rc = pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED); + if (rc) + failure_perror("pthread_mutexattr_setpshared()", rc); + + pthread_barrierattr_t barrierattr; + rc = pthread_barrierattr_init(&barrierattr); + if (rc) + failure_perror("pthread_barrierattr_init()", rc); + rc = pthread_barrierattr_setpshared(&barrierattr, PTHREAD_PROCESS_SHARED); + if (rc) + failure_perror("pthread_barrierattr_setpshared()", rc); + + pthread_condattr_t condattr; + rc = pthread_condattr_init(&condattr); + if (rc) + failure_perror("pthread_condattr_init()", rc); + rc = pthread_condattr_setpshared(&condattr, PTHREAD_PROCESS_SHARED); + if (rc) + failure_perror("pthread_condattr_setpshared()", rc); + + shared = (shared_t *)mmap( + nullptr, sizeof(shared_t) + actors.size() * sizeof(pthread_cond_t), + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (MAP_FAILED == (void *)shared) + failure_perror("mmap(shared_conds)", errno); + + rc = pthread_mutex_init(&shared->mutex, &mutexattr); + if (rc) + failure_perror("pthread_mutex_init(shared)", rc); + + rc = pthread_barrier_init(&shared->barrier, &barrierattr, actors.size() + 1); + if (rc) + failure_perror("pthread_barrier_init(shared)", rc); + + const size_t n = actors.size() + 1; + for (size_t i = 0; i < n; ++i) { + pthread_cond_t *event = &shared->conds[i]; + rc = pthread_cond_init(event, &condattr); + if (rc) + failure_perror("pthread_cond_init(shared)", rc); + log_trace("osal_setup: event(shared pthread_cond) %" PRIuPTR " -> %p", i, + __Wpedantic_format_voidptr(event)); + } + shared->conds_size = actors.size() + 1; + + pthread_barrierattr_destroy(&barrierattr); + pthread_condattr_destroy(&condattr); + pthread_mutexattr_destroy(&mutexattr); +} + +void osal_broadcast(unsigned id) { + assert(shared != nullptr && shared != MAP_FAILED); + log_trace("osal_broadcast: event %u", id); + if (id >= shared->conds_size) + failure("osal_broadcast: id > limit"); + int rc = pthread_cond_broadcast(shared->conds + id); + if (rc) + failure_perror("sem_post(shared)", rc); +} + +int osal_waitfor(unsigned id) { + assert(shared != nullptr && shared != MAP_FAILED); + + log_trace("osal_waitfor: event %u", id); + if (id >= shared->conds_size) + failure("osal_waitfor: id > limit"); + + int rc = pthread_mutex_lock(&shared->mutex); + if (rc != 0) + failure_perror("pthread_mutex_lock(shared)", rc); + + rc = pthread_cond_wait(shared->conds + id, &shared->mutex); + if (rc && rc != EINTR) + failure_perror("pthread_cond_wait(shared)", rc); + + rc = pthread_mutex_unlock(&shared->mutex); + if (rc != 0) + failure_perror("pthread_mutex_unlock(shared)", rc); + + return (rc == 0) ? true : false; +} + +//----------------------------------------------------------------------------- + +const std::string +actor_config::osal_serialize(simple_checksum &checksum) const { + (void)checksum; + /* not used in workload, but just for testing */ + return "unix.fork"; +} + +bool actor_config::osal_deserialize(const char *str, const char *end, + simple_checksum &checksum) { + (void)checksum; + /* not used in workload, but just for testing */ + return strncmp(str, "unix.fork", 9) == 0 && str + 9 == end; +} + +//----------------------------------------------------------------------------- + +static pid_t overlord_pid; + +static volatile sig_atomic_t sigusr1_head, sigusr2_head; +static void handler_SIGUSR(int signum) { + switch (signum) { + case SIGUSR1: + sigusr1_head += 1; + return; + case SIGUSR2: + sigusr2_head += 1; + return; + default: + abort(); + } +} + +bool osal_progress_push(bool active) { + if (overlord_pid) { + if (kill(overlord_pid, active ? SIGUSR1 : SIGUSR2)) + failure_perror("osal_progress_push: kill(overload)", errno); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- + +static std::unordered_map childs; + +static volatile sig_atomic_t sigalarm_head; +static void handler_SIGCHLD(int signum) { + if (signum == SIGALRM) + sigalarm_head += 1; +} + +mdbx_pid_t osal_getpid(void) { return getpid(); } + +int osal_delay(unsigned seconds) { return sleep(seconds) ? errno : 0; } + +int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) { + if (childs.empty()) { + struct sigaction act; + memset(&act, 0, sizeof(act)); + act.sa_handler = handler_SIGCHLD; + sigaction(SIGCHLD, &act, nullptr); + sigaction(SIGALRM, &act, nullptr); + act.sa_handler = handler_SIGUSR; + sigaction(SIGUSR1, &act, nullptr); + sigaction(SIGUSR2, &act, nullptr); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sigaddset(&mask, SIGUSR1); + sigaddset(&mask, SIGUSR2); + sigprocmask(SIG_UNBLOCK, &mask, nullptr); + } + + pid = fork(); + + if (pid == 0) { + overlord_pid = getppid(); + const bool result = test_execute(config); + exit(result ? EXIT_SUCCESS : EXIT_FAILURE); + } + + if (pid < 0) + return errno; + + log_trace("osal_actor_start: fork pid %i for %u", pid, config.actor_id); + childs[pid] = as_running; + return 0; +} + +actor_status osal_actor_info(const mdbx_pid_t pid) { return childs.at(pid); } + +void osal_killall_actors(void) { + for (auto &pair : childs) { + kill(pair.first, SIGKILL); + pair.second = as_killed; + } +} + +int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) { + static sig_atomic_t sigalarm_tail; + alarm(0) /* cancel prev timeout */; + sigalarm_tail = sigalarm_head /* reset timeout flag */; + + int options = WNOHANG; + if (timeout) { + alarm((timeout > INT_MAX) ? INT_MAX : timeout); + options = 0; + } + +#ifdef WUNTRACED + options |= WUNTRACED; +#endif +#ifdef WCONTINUED + options |= WCONTINUED; +#endif + + while (sigalarm_tail == sigalarm_head) { + int status; + pid = waitpid(0, &status, options); + + if (pid > 0) { + if (WIFEXITED(status)) + childs[pid] = + (WEXITSTATUS(status) == EXIT_SUCCESS) ? as_successful : as_failed; + else if (WCOREDUMP(status)) + childs[pid] = as_coredump; + else if (WIFSIGNALED(status)) + childs[pid] = as_killed; + else if (WIFSTOPPED(status)) + childs[pid] = as_debuging; + else if (WIFCONTINUED(status)) + childs[pid] = as_running; + else { + assert(false); + } + return 0; + } + + static sig_atomic_t sigusr1_tail, sigusr2_tail; + if (sigusr1_tail != sigusr1_head) { + sigusr1_tail = sigusr1_head; + logging::progress_canary(true); + if (pid < 0 && errno == EINTR) + continue; + } + if (sigusr2_tail != sigusr2_head) { + sigusr2_tail = sigusr2_head; + logging::progress_canary(false); + if (pid < 0 && errno == EINTR) + continue; + } + + if (pid == 0) + break; + + int err = errno; + if (err != EINTR) + return err; + } + return 0 /* timeout */; +} + +void osal_yield(void) { + if (sched_yield()) + failure_perror("sched_yield()", errno); +} + +void osal_udelay(unsigned us) { + chrono::time until, now = chrono::now_motonic(); + until.fixedpoint = now.fixedpoint + chrono::from_us(us).fixedpoint; + struct timespec ts; + + static unsigned threshold_us; + if (threshold_us == 0) { + if (clock_getres(CLOCK_PROCESS_CPUTIME_ID, &ts)) { + int rc = errno; + failure_perror("clock_getres(CLOCK_PROCESS_CPUTIME_ID)", rc); + } + chrono::time threshold = chrono::from_timespec(ts); + assert(threshold.seconds() == 0); + + threshold_us = chrono::fractional2us(threshold.fractional); + if (threshold_us < 1000) + threshold_us = 1000; + } + + ts.tv_sec = ts.tv_nsec = 0; + if (us > threshold_us) { + ts.tv_sec = us / 1000000u; + ts.tv_nsec = (us % 1000000u) * 1000u; + } + + do { + if (us > threshold_us) { + if (nanosleep(&ts, &ts)) { + int rc = errno; + /* if (rc == EINTR) { ... } ? */ + failure_perror("usleep()", rc); + } + us = ts.tv_sec * 1000000u + ts.tv_nsec / 1000u; + } + cpu_relax(); + + now = chrono::now_motonic(); + } while (until.fixedpoint > now.fixedpoint); +} + +bool osal_istty(int fd) { return isatty(fd) == 1; } + +std::string osal_tempdir(void) { + const char *tempdir = getenv("TMPDIR"); + if (!tempdir) + tempdir = getenv("TMP"); + if (!tempdir) + tempdir = getenv("TEMPDIR"); + if (!tempdir) + tempdir = getenv("TEMP"); + if (tempdir) { + std::string dir(tempdir); + if (!dir.empty() && dir.at(dir.length() - 1) != '/') + dir.append("/"); + return dir; + } + if (access("/dev/shm/", R_OK | W_OK | X_OK) == 0) + return "/dev/shm/"; + return ""; +} + +int osal_removefile(const std::string &pathname) { + return unlink(pathname.c_str()) ? errno : MDBX_SUCCESS; +} diff --git a/contrib/db/libmdbx/test/osal-windows.cc b/contrib/db/libmdbx/test/osal-windows.cc new file mode 100644 index 00000000..0b158c0d --- /dev/null +++ b/contrib/db/libmdbx/test/osal-windows.cc @@ -0,0 +1,461 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +static std::unordered_map events; +static HANDLE hBarrierSemaphore, hBarrierEvent; +static HANDLE hProgressActiveEvent, hProgressPassiveEvent; + +static int waitstatus2errcode(DWORD result) { + switch (result) { + case WAIT_OBJECT_0: + return MDBX_SUCCESS; + case WAIT_FAILED: + return GetLastError(); + case WAIT_ABANDONED: + return ERROR_ABANDONED_WAIT_0; + case WAIT_IO_COMPLETION: + return ERROR_USER_APC; + case WAIT_TIMEOUT: + return ERROR_TIMEOUT; + default: + return ERROR_UNHANDLED_ERROR; + } +} + +void osal_wait4barrier(void) { + DWORD rc = WaitForSingleObject(hBarrierSemaphore, 0); + switch (rc) { + default: + failure_perror("WaitForSingleObject(BarrierSemaphore)", + waitstatus2errcode(rc)); + case WAIT_OBJECT_0: + rc = WaitForSingleObject(hBarrierEvent, INFINITE); + if (rc != WAIT_OBJECT_0) + failure_perror("WaitForSingleObject(BarrierEvent)", + waitstatus2errcode(rc)); + break; + case WAIT_TIMEOUT: + if (!SetEvent(hBarrierEvent)) + failure_perror("SetEvent(BarrierEvent)", GetLastError()); + break; + } +} + +static HANDLE make_inheritable(HANDLE hHandle) { + assert(hHandle != NULL && hHandle != INVALID_HANDLE_VALUE); + if (!DuplicateHandle(GetCurrentProcess(), hHandle, GetCurrentProcess(), + &hHandle, 0, TRUE, + DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) + failure_perror("DuplicateHandle()", GetLastError()); + return hHandle; +} + +void osal_setup(const std::vector &actors) { + assert(events.empty()); + const size_t n = actors.size() + 1; + events.reserve(n); + + for (unsigned i = 0; i < n; ++i) { + HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hEvent) + failure_perror("CreateEvent()", GetLastError()); + hEvent = make_inheritable(hEvent); + log_trace("osal_setup: event %" PRIuPTR " -> %p", i, hEvent); + events[i] = hEvent; + } + + hBarrierSemaphore = CreateSemaphore(NULL, 0, (LONG)actors.size(), NULL); + if (!hBarrierSemaphore) + failure_perror("CreateSemaphore(BarrierSemaphore)", GetLastError()); + hBarrierSemaphore = make_inheritable(hBarrierSemaphore); + + hBarrierEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (!hBarrierEvent) + failure_perror("CreateEvent(BarrierEvent)", GetLastError()); + hBarrierEvent = make_inheritable(hBarrierEvent); + + hProgressActiveEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!hProgressActiveEvent) + failure_perror("CreateEvent(ProgressActiveEvent)", GetLastError()); + hProgressActiveEvent = make_inheritable(hProgressActiveEvent); + + hProgressPassiveEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + if (!hProgressPassiveEvent) + failure_perror("CreateEvent(ProgressPassiveEvent)", GetLastError()); + hProgressPassiveEvent = make_inheritable(hProgressPassiveEvent); +} + +void osal_broadcast(unsigned id) { + log_trace("osal_broadcast: event %u", id); + if (!SetEvent(events.at(id))) + failure_perror("SetEvent()", GetLastError()); +} + +int osal_waitfor(unsigned id) { + log_trace("osal_waitfor: event %u", id); + DWORD rc = WaitForSingleObject(events.at(id), INFINITE); + return waitstatus2errcode(rc); +} + +mdbx_pid_t osal_getpid(void) { return GetCurrentProcessId(); } + +int osal_delay(unsigned seconds) { + Sleep(seconds * 1000u); + return 0; +} + +//----------------------------------------------------------------------------- + +const std::string +actor_config::osal_serialize(simple_checksum &checksum) const { + checksum.push(hBarrierSemaphore); + checksum.push(hBarrierEvent); + checksum.push(hProgressActiveEvent); + checksum.push(hProgressPassiveEvent); + + HANDLE hWait = INVALID_HANDLE_VALUE; + if (wait4id) { + hWait = events.at(wait4id); + checksum.push(hWait); + } + + HANDLE hSignal = INVALID_HANDLE_VALUE; + if (wanna_event4signalling()) { + hSignal = events.at(actor_id); + checksum.push(hSignal); + } + + return format("%p.%p.%p.%p.%p.%p", hBarrierSemaphore, hBarrierEvent, hWait, + hSignal, hProgressActiveEvent, hProgressPassiveEvent); +} + +bool actor_config::osal_deserialize(const char *str, const char *end, + simple_checksum &checksum) { + + std::string copy(str, end - str); + TRACE(">> osal_deserialize(%s)\n", copy.c_str()); + + assert(hBarrierSemaphore == 0); + assert(hBarrierEvent == 0); + assert(hProgressActiveEvent == 0); + assert(hProgressPassiveEvent == 0); + assert(events.empty()); + + HANDLE hWait, hSignal; + if (sscanf_s(copy.c_str(), "%p.%p.%p.%p.%p.%p", &hBarrierSemaphore, + &hBarrierEvent, &hWait, &hSignal, &hProgressActiveEvent, + &hProgressPassiveEvent) != 6) { + TRACE("<< osal_deserialize: failed\n"); + return false; + } + + checksum.push(hBarrierSemaphore); + checksum.push(hBarrierEvent); + checksum.push(hProgressActiveEvent); + checksum.push(hProgressPassiveEvent); + + if (wait4id) { + checksum.push(hWait); + events[wait4id] = hWait; + } + + if (wanna_event4signalling()) { + checksum.push(hSignal); + events[actor_id] = hSignal; + } + + TRACE("<< osal_deserialize: OK\n"); + return true; +} + +//----------------------------------------------------------------------------- + +typedef std::pair child; +static std::unordered_map childs; + +bool osal_progress_push(bool active) { + if (childs.empty()) { + if (!SetEvent(active ? hProgressActiveEvent : hProgressPassiveEvent)) + failure_perror("osal_progress_push: SetEvent(overlord.progress)", + GetLastError()); + return true; + } + + return false; +} + +static void ArgvQuote(std::string &CommandLine, const std::string &Argument, + bool Force = false) + +/*++ + +https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ + +Routine Description: + + This routine appends the given argument to a command line such + that CommandLineToArgvW will return the argument string unchanged. + Arguments in a command line should be separated by spaces; this + function does not add these spaces. + +Arguments: + + Argument - Supplies the argument to encode. + + CommandLine - Supplies the command line to which we append the encoded +argument string. + + Force - Supplies an indication of whether we should quote + the argument even if it does not contain any characters that would + ordinarily require quoting. + +Return Value: + + None. + +Environment: + + Arbitrary. + +--*/ + +{ + // + // Unless we're told otherwise, don't quote unless we actually + // need to do so --- hopefully avoid problems if programs won't + // parse quotes properly + // + + if (Force == false && Argument.empty() == false && + Argument.find_first_of(" \t\n\v\"") == Argument.npos) { + CommandLine.append(Argument); + } else { + CommandLine.push_back('"'); + + for (auto It = Argument.begin();; ++It) { + unsigned NumberBackslashes = 0; + + while (It != Argument.end() && *It == '\\') { + ++It; + ++NumberBackslashes; + } + + if (It == Argument.end()) { + // + // Escape all backslashes, but let the terminating + // double quotation mark we add below be interpreted + // as a metacharacter. + // + CommandLine.append(NumberBackslashes * 2, '\\'); + break; + } else if (*It == L'"') { + // + // Escape all backslashes and the following + // double quotation mark. + // + CommandLine.append(NumberBackslashes * 2 + 1, '\\'); + CommandLine.push_back(*It); + } else { + // + // Backslashes aren't special here. + // + CommandLine.append(NumberBackslashes, '\\'); + CommandLine.push_back(*It); + } + } + + CommandLine.push_back('"'); + } +} + +int osal_actor_start(const actor_config &config, mdbx_pid_t &pid) { + if (childs.size() == MAXIMUM_WAIT_OBJECTS) + failure("Could't manage more that %u actors on Windows\n", + MAXIMUM_WAIT_OBJECTS); + + _flushall(); + + STARTUPINFOA StartupInfo; + GetStartupInfoA(&StartupInfo); + + char exename[_MAX_PATH + 1]; + DWORD exename_size = sizeof(exename); + if (!QueryFullProcessImageNameA(GetCurrentProcess(), 0, exename, + &exename_size)) + failure_perror("QueryFullProcessImageName()", GetLastError()); + + if (exename[1] != ':') { + exename_size = GetModuleFileName(NULL, exename, sizeof(exename)); + if (exename_size >= sizeof(exename)) + return ERROR_BAD_LENGTH; + } + + std::string cmdline = "$ "; + ArgvQuote(cmdline, thunk_param(config)); + + if (cmdline.size() >= 32767) + return ERROR_BAD_LENGTH; + + PROCESS_INFORMATION ProcessInformation; + if (!CreateProcessA(exename, const_cast(cmdline.c_str()), + NULL, // Retuned process handle is not inheritable. + NULL, // Retuned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles. + NORMAL_PRIORITY_CLASS | INHERIT_PARENT_AFFINITY, + NULL, // Inherit the parent's environment. + NULL, // Inherit the parent's current directory. + &StartupInfo, &ProcessInformation)) + failure_perror(exename, GetLastError()); + + CloseHandle(ProcessInformation.hThread); + pid = ProcessInformation.dwProcessId; + childs[pid] = std::make_pair(ProcessInformation.hProcess, as_running); + return 0; +} + +actor_status osal_actor_info(const mdbx_pid_t pid) { + actor_status status = childs.at(pid).second; + if (status > as_running) + return status; + + DWORD ExitCode; + if (!GetExitCodeProcess(childs.at(pid).first, &ExitCode)) + failure_perror("GetExitCodeProcess()", GetLastError()); + + switch (ExitCode) { + case STILL_ACTIVE: + return as_running; + case EXIT_SUCCESS: + status = as_successful; + break; + case EXCEPTION_BREAKPOINT: + case EXCEPTION_SINGLE_STEP: + status = as_debuging; + break; + case STATUS_CONTROL_C_EXIT: + status = as_killed; + break; + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_DATATYPE_MISALIGNMENT: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_INVALID_DISPOSITION: + case EXCEPTION_ILLEGAL_INSTRUCTION: + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + status = as_coredump; + break; + default: + status = as_failed; + break; + } + + childs.at(pid).second = status; + return status; +} + +void osal_killall_actors(void) { + for (auto &pair : childs) + TerminateProcess(pair.second.first, STATUS_CONTROL_C_EXIT); +} + +int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout) { + std::vector handles; + handles.reserve(childs.size() + 2); + handles.push_back(hProgressActiveEvent); + handles.push_back(hProgressPassiveEvent); + for (const auto &pair : childs) + if (pair.second.second <= as_running) + handles.push_back(pair.second.first); + + while (true) { + DWORD rc = + MsgWaitForMultipleObjectsEx((DWORD)handles.size(), &handles[0], + (timeout > 60) ? 60 * 1000 : timeout * 1000, + QS_ALLINPUT | QS_ALLPOSTMESSAGE, 0); + + if (rc == WAIT_OBJECT_0) { + logging::progress_canary(true); + continue; + } + if (rc == WAIT_OBJECT_0 + 1) { + logging::progress_canary(false); + continue; + } + + if (rc >= WAIT_OBJECT_0 + 2 && rc < WAIT_OBJECT_0 + handles.size()) { + pid = 0; + for (const auto &pair : childs) + if (pair.second.first == handles[rc - WAIT_OBJECT_0]) { + pid = pair.first; + break; + } + return 0; + } + + if (rc == WAIT_TIMEOUT) { + pid = 0; + return 0; + } + + return waitstatus2errcode(rc); + } +} + +void osal_yield(void) { SwitchToThread(); } + +void osal_udelay(unsigned us) { + chrono::time until, now = chrono::now_motonic(); + until.fixedpoint = now.fixedpoint + chrono::from_us(us).fixedpoint; + + static unsigned threshold_us; + if (threshold_us == 0) { +#if 1 + unsigned timeslice_ms = 1; + while (timeBeginPeriod(timeslice_ms) == TIMERR_NOCANDO) + ++timeslice_ms; + threshold_us = timeslice_ms * 1500u; +#else + ULONGLONG InterruptTimePrecise_100ns; + QueryInterruptTimePrecise(&InterruptTimePrecise_100ns); + threshold_us = InterruptTimePrecise_100ns / 5; +#endif + assert(threshold_us > 0); + } + + do { + if (us > threshold_us && us > 1000) { + DWORD rc = SleepEx(us / 1000, TRUE); + if (rc) + failure_perror("SleepEx()", waitstatus2errcode(rc)); + us = 0; + } + + YieldProcessor(); + now = chrono::now_motonic(); + } while (now.fixedpoint < until.fixedpoint); +} + +bool osal_istty(int fd) { return _isatty(fd) != 0; } + +std::string osal_tempdir(void) { + char buf[MAX_PATH + 1]; + DWORD len = GetTempPathA(sizeof(buf), buf); + return std::string(buf, len); +} + +int osal_removefile(const std::string &pathname) { + return DeleteFileA(pathname.c_str()) ? MDBX_SUCCESS : GetLastError(); +} diff --git a/contrib/db/libmdbx/test/osal.h b/contrib/db/libmdbx/test/osal.h new file mode 100644 index 00000000..6d0e1c4e --- /dev/null +++ b/contrib/db/libmdbx/test/osal.h @@ -0,0 +1,49 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#include "base.h" + +void osal_setup(const std::vector &actors); +void osal_broadcast(unsigned id); +int osal_waitfor(unsigned id); + +int osal_actor_start(const actor_config &config, mdbx_pid_t &pid); +actor_status osal_actor_info(const mdbx_pid_t pid); +void osal_killall_actors(void); +int osal_actor_poll(mdbx_pid_t &pid, unsigned timeout); +void osal_wait4barrier(void); + +bool osal_progress_push(bool active); + +mdbx_pid_t osal_getpid(void); +int osal_delay(unsigned seconds); +void osal_udelay(unsigned us); +void osal_yield(void); +bool osal_istty(int fd); +std::string osal_tempdir(void); +int osal_removefile(const std::string &pathname); + +#ifdef _MSC_VER +#ifndef STDIN_FILENO +#define STDIN_FILENO _fileno(stdin) +#endif +#ifndef STDOUT_FILENO +#define STDOUT_FILENO _fileno(stdout) +#endif +#ifndef STDERR_FILENO +#define STDERR_FILENO _fileno(stderr) +#endif +#endif /* _MSC_VER */ diff --git a/contrib/db/libmdbx/test/pcrf/CMakeLists.txt b/contrib/db/libmdbx/test/pcrf/CMakeLists.txt new file mode 100644 index 00000000..8bd3e3d8 --- /dev/null +++ b/contrib/db/libmdbx/test/pcrf/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET pcrf_test) +add_executable(${TARGET} pcrf_test.c) +target_include_directories(${TARGET} PRIVATE "${PROJECT_SOURCE_DIR}") +target_link_libraries(${TARGET} mdbx) + diff --git a/contrib/db/libmdbx/test/pcrf/README.md b/contrib/db/libmdbx/test/pcrf/README.md new file mode 100644 index 00000000..b2c9b5ce --- /dev/null +++ b/contrib/db/libmdbx/test/pcrf/README.md @@ -0,0 +1,2 @@ +PCRF Session DB emulation test + diff --git a/contrib/db/libmdbx/test/pcrf/pcrf_test.c b/contrib/db/libmdbx/test/pcrf/pcrf_test.c new file mode 100644 index 00000000..90e0e28c --- /dev/null +++ b/contrib/db/libmdbx/test/pcrf/pcrf_test.c @@ -0,0 +1,413 @@ +/* + * Copyright 2016-2019 Leonid Yuriev . + * Copyright 2015 Vladimir Romanov + * , Yota Lab. + * + * This file is part of libmdbx. + * + * ReOpenMDBX is free software; you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * ReOpenMDBX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "mdbx.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define IP_PRINTF_ARG_HOST(addr) \ + (int)((addr) >> 24), (int)((addr) >> 16 & 0xff), (int)((addr) >> 8 & 0xff), \ + (int)((addr)&0xff) + +char opt_db_path[PATH_MAX] = "./mdbx_bench2"; +static MDBX_env *env; +#define REC_COUNT 10240000 +int64_t ids[REC_COUNT * 10]; +int32_t ids_count = 0; + +int64_t mdbx_add_count = 0; +int64_t mdbx_del_count = 0; +uint64_t mdbx_add_time = 0; +uint64_t mdbx_del_time = 0; +int64_t obj_id = 0; +int64_t mdbx_data_size = 0; +int64_t mdbx_key_size = 0; + +typedef struct { + char session_id1[100]; + char session_id2[100]; + char ip[20]; + uint8_t fill[100]; +} session_data_t; + +typedef struct { + int64_t obj_id; + int8_t event_type; +} __attribute__((__packed__)) event_data_t; + +static void add_id_to_pool(int64_t id) { + ids[ids_count] = id; + ids_count++; +} + +static inline int64_t getClockUs(void) { + struct timespec val; +#ifdef CYGWIN + clock_gettime(CLOCK_REALTIME, &val); +#else + clock_gettime(CLOCK_MONOTONIC, &val); +#endif + return val.tv_sec * ((int64_t)1000000) + val.tv_nsec / 1000; +} + +static int64_t get_id_from_pool() { + if (ids_count == 0) { + return -1; + } + int32_t index = rand() % ids_count; + int64_t id = ids[index]; + ids[index] = ids[ids_count - 1]; + ids_count--; + return id; +} + +#define MDBX_CHECK(x) \ + do { \ + const int rc = (x); \ + if (rc != MDBX_SUCCESS) { \ + printf("Error [%d] %s in %s at %s:%d\n", rc, mdbx_strerror(rc), #x, \ + __FILE__, __LINE__); \ + exit(EXIT_FAILURE); \ + } \ + } while (0) + +static void db_connect() { + MDBX_dbi dbi_session; + MDBX_dbi dbi_session_id; + MDBX_dbi dbi_event; + MDBX_dbi dbi_ip; + + MDBX_CHECK(mdbx_env_create(&env)); + MDBX_CHECK( + mdbx_env_set_mapsize(env, REC_COUNT * sizeof(session_data_t) * 10)); + MDBX_CHECK(mdbx_env_set_maxdbs(env, 30)); + MDBX_CHECK(mdbx_env_open(env, opt_db_path, + MDBX_CREATE | MDBX_WRITEMAP | MDBX_MAPASYNC | + MDBX_NOSYNC | MDBX_LIFORECLAIM, + 0664)); + MDBX_txn *txn; + + // transaction init + MDBX_CHECK(mdbx_txn_begin(env, NULL, 0, &txn)); + // open database in read-write mode + MDBX_CHECK(mdbx_dbi_open(txn, "session", MDBX_CREATE, &dbi_session)); + MDBX_CHECK(mdbx_dbi_open(txn, "session_id", MDBX_CREATE, &dbi_session_id)); + MDBX_CHECK(mdbx_dbi_open(txn, "event", MDBX_CREATE, &dbi_event)); + MDBX_CHECK(mdbx_dbi_open(txn, "ip", MDBX_CREATE, &dbi_ip)); + // transaction commit + MDBX_CHECK(mdbx_txn_commit(txn)); + printf("Connection open\n"); +} + +static void create_record(uint64_t record_id) { + MDBX_dbi dbi_session; + MDBX_dbi dbi_session_id; + MDBX_dbi dbi_event; + MDBX_dbi dbi_ip; + event_data_t event; + MDBX_txn *txn; + session_data_t data; + // transaction init + snprintf(data.session_id1, sizeof(data.session_id1), + "prefix%02u_%02u.fill.fill.fill.fill.fill.fill;%" PRIu64, + (unsigned)(record_id % 3) + 1, (unsigned)(record_id % 9) + 1, + record_id); + snprintf(data.session_id2, sizeof(data.session_id2), + "dprefix%" PRIu64 ";%" PRIu64 ".fill.fill.;suffix", record_id, + (record_id + UINT64_C(1442695040888963407)) % + UINT64_C(6364136223846793005)); + snprintf(data.ip, sizeof(data.ip), "%d.%d.%d.%d", + IP_PRINTF_ARG_HOST(record_id & 0xFFFFFFFF)); + event.obj_id = record_id; + event.event_type = 1; + + MDBX_val _session_id1_rec = {data.session_id1, strlen(data.session_id1)}; + MDBX_val _session_id2_rec = {data.session_id2, strlen(data.session_id2)}; + MDBX_val _ip_rec = {data.ip, strlen(data.ip)}; + MDBX_val _obj_id_rec = {&record_id, sizeof(record_id)}; + MDBX_val _data_rec = {&data, offsetof(session_data_t, fill) + + (rand() % sizeof(data.fill))}; + MDBX_val _event_rec = {&event, sizeof(event)}; + + uint64_t start = getClockUs(); + MDBX_CHECK(mdbx_txn_begin(env, NULL, 0, &txn)); + MDBX_CHECK(mdbx_dbi_open(txn, "session", MDBX_CREATE, &dbi_session)); + MDBX_CHECK(mdbx_dbi_open(txn, "session_id", MDBX_CREATE, &dbi_session_id)); + MDBX_CHECK(mdbx_dbi_open(txn, "event", MDBX_CREATE, &dbi_event)); + MDBX_CHECK(mdbx_dbi_open(txn, "ip", MDBX_CREATE, &dbi_ip)); + MDBX_CHECK(mdbx_put(txn, dbi_session, &_obj_id_rec, &_data_rec, + MDBX_NOOVERWRITE | MDBX_NODUPDATA)); + MDBX_CHECK(mdbx_put(txn, dbi_session_id, &_session_id1_rec, &_obj_id_rec, + MDBX_NOOVERWRITE | MDBX_NODUPDATA)); + MDBX_CHECK(mdbx_put(txn, dbi_session_id, &_session_id2_rec, &_obj_id_rec, + MDBX_NOOVERWRITE | MDBX_NODUPDATA)); + MDBX_CHECK(mdbx_put(txn, dbi_ip, &_ip_rec, &_obj_id_rec, 0)); + MDBX_CHECK(mdbx_put(txn, dbi_event, &_event_rec, &_obj_id_rec, 0)); + MDBX_CHECK(mdbx_txn_commit(txn)); + + mdbx_data_size += (_data_rec.iov_len + _obj_id_rec.iov_len * 4); + mdbx_key_size += + (_obj_id_rec.iov_len + _session_id1_rec.iov_len + + _session_id2_rec.iov_len + _ip_rec.iov_len + _event_rec.iov_len); + + // transaction commit + mdbx_add_count++; + mdbx_add_time += (getClockUs() - start); +} + +static void delete_record(int64_t record_id) { + MDBX_dbi dbi_session; + MDBX_dbi dbi_session_id; + MDBX_dbi dbi_event; + MDBX_dbi dbi_ip; + event_data_t event; + MDBX_txn *txn; + + // transaction init + uint64_t start = getClockUs(); + MDBX_CHECK(mdbx_txn_begin(env, NULL, 0, &txn)); + // open database in read-write mode + MDBX_CHECK(mdbx_dbi_open(txn, "session", MDBX_CREATE, &dbi_session)); + MDBX_CHECK(mdbx_dbi_open(txn, "session_id", MDBX_CREATE, &dbi_session_id)); + MDBX_CHECK(mdbx_dbi_open(txn, "event", MDBX_CREATE, &dbi_event)); + MDBX_CHECK(mdbx_dbi_open(txn, "ip", MDBX_CREATE, &dbi_ip)); + // put data + MDBX_val _obj_id_rec = {&record_id, sizeof(record_id)}; + MDBX_val _data_rec; + // get data + MDBX_CHECK(mdbx_get(txn, dbi_session, &_obj_id_rec, &_data_rec)); + session_data_t *data = (session_data_t *)_data_rec.iov_base; + + MDBX_val _session_id1_rec = {data->session_id1, strlen(data->session_id1)}; + MDBX_val _session_id2_rec = {data->session_id2, strlen(data->session_id2)}; + MDBX_val _ip_rec = {data->ip, strlen(data->ip)}; + MDBX_CHECK(mdbx_del(txn, dbi_session_id, &_session_id1_rec, NULL)); + MDBX_CHECK(mdbx_del(txn, dbi_session_id, &_session_id2_rec, NULL)); + MDBX_CHECK(mdbx_del(txn, dbi_ip, &_ip_rec, NULL)); + event.obj_id = record_id; + event.event_type = 1; + MDBX_val _event_rec = {&event, sizeof(event)}; + MDBX_CHECK(mdbx_del(txn, dbi_event, &_event_rec, NULL)); + MDBX_CHECK(mdbx_del(txn, dbi_session, &_obj_id_rec, NULL)); + + mdbx_data_size -= (_data_rec.iov_len + _obj_id_rec.iov_len * 4); + mdbx_key_size -= + (_obj_id_rec.iov_len + _session_id1_rec.iov_len + + _session_id2_rec.iov_len + _ip_rec.iov_len + _event_rec.iov_len); + + // transaction commit + MDBX_CHECK(mdbx_txn_commit(txn)); + mdbx_del_count++; + mdbx_del_time += (getClockUs() - start); +} + +static void db_disconnect() { + mdbx_env_close(env); + printf("Connection closed\n"); +} + +static void get_db_stat(const char *db, int64_t *ms_branch_pages, + int64_t *ms_leaf_pages) { + MDBX_txn *txn; + MDBX_stat stat; + MDBX_dbi dbi; + + MDBX_CHECK(mdbx_txn_begin(env, NULL, MDBX_RDONLY, &txn)); + MDBX_CHECK(mdbx_dbi_open(txn, db, MDBX_CREATE, &dbi)); + MDBX_CHECK(mdbx_dbi_stat(txn, dbi, &stat, sizeof(stat))); + mdbx_txn_abort(txn); + printf("%15s | %15" PRIu64 " | %5u | %10" PRIu64 " | %10" PRIu64 + " | %11" PRIu64 " |\n", + db, stat.ms_branch_pages, stat.ms_depth, stat.ms_entries, + stat.ms_leaf_pages, stat.ms_overflow_pages); + (*ms_branch_pages) += stat.ms_branch_pages; + (*ms_leaf_pages) += stat.ms_leaf_pages; +} + +static void periodic_stat(void) { + int64_t ms_branch_pages = 0; + int64_t ms_leaf_pages = 0; + MDBX_stat mst; + MDBX_envinfo mei; + MDBX_CHECK(mdbx_env_stat(env, &mst, sizeof(mst))); + MDBX_CHECK(mdbx_env_info(env, &mei, sizeof(mei))); + printf("Environment Info\n"); + printf(" Pagesize: %u\n", mst.ms_psize); + if (mei.mi_geo.lower != mei.mi_geo.upper) { + printf(" Dynamic datafile: %" PRIu64 "..%" PRIu64 " bytes (+%" PRIu64 + "/-%" PRIu64 "), %" PRIu64 "..%" PRIu64 " pages (+%" PRIu64 + "/-%" PRIu64 ")\n", + mei.mi_geo.lower, mei.mi_geo.upper, mei.mi_geo.grow, + mei.mi_geo.shrink, mei.mi_geo.lower / mst.ms_psize, + mei.mi_geo.upper / mst.ms_psize, mei.mi_geo.grow / mst.ms_psize, + mei.mi_geo.shrink / mst.ms_psize); + printf(" Current datafile: %" PRIu64 " bytes, %" PRIu64 " pages\n", + mei.mi_geo.current, mei.mi_geo.current / mst.ms_psize); + } else { + printf(" Fixed datafile: %" PRIu64 " bytes, %" PRIu64 " pages\n", + mei.mi_geo.current, mei.mi_geo.current / mst.ms_psize); + } + printf(" Current mapsize: %" PRIu64 " bytes, %" PRIu64 " pages \n", + mei.mi_mapsize, mei.mi_mapsize / mst.ms_psize); + printf(" Number of pages used: %" PRIu64 "\n", mei.mi_last_pgno + 1); + printf(" Last transaction ID: %" PRIu64 "\n", mei.mi_recent_txnid); + printf(" Tail transaction ID: %" PRIu64 " (%" PRIi64 ")\n", + mei.mi_latter_reader_txnid, + mei.mi_latter_reader_txnid - mei.mi_recent_txnid); + printf(" Max readers: %u\n", mei.mi_maxreaders); + printf(" Number of readers used: %u\n", mei.mi_numreaders); + + printf(" Name | ms_branch_pages | depth | entries | leaf_pages " + "| overf_pages |\n"); + get_db_stat("session", &ms_branch_pages, &ms_leaf_pages); + get_db_stat("session_id", &ms_branch_pages, &ms_leaf_pages); + get_db_stat("event", &ms_branch_pages, &ms_leaf_pages); + get_db_stat("ip", &ms_branch_pages, &ms_leaf_pages); + printf("%15s | %15" PRIu64 " | %5s | %10s | %10" PRIu64 " | %11s |\n", "", + ms_branch_pages, "", "", ms_leaf_pages, ""); + + static int64_t prev_add_count; + static int64_t prev_del_count; + static uint64_t prev_add_time; + static uint64_t prev_del_time; + static int64_t t = -1; + if (t > 0) { + int64_t delta = (getClockUs() - t); + printf("CPS: add %" PRIu64 ", delete %" PRIu64 + ", items processed - %" PRIu64 "K data=%" PRIu64 "K key=%" PRIu64 + "K\n", + (mdbx_add_count - prev_add_count) * 1000000 / delta, + (mdbx_del_count - prev_del_count) * 1000000 / delta, obj_id / 1024, + mdbx_data_size / 1024, mdbx_key_size / 1024); + printf("usage data=%" PRIu64 "%%", + ((mdbx_data_size + mdbx_key_size) * 100) / + ((ms_leaf_pages + ms_branch_pages) * 4096)); + if (prev_add_time != mdbx_add_time) { + printf(" Add : %" PRIu64 " c/s", (mdbx_add_count - prev_add_count) * + 1000000 / + (mdbx_add_time - prev_add_time)); + } + if (prev_del_time != mdbx_del_time) { + printf(" Del : %" PRIu64 " c/s", (mdbx_del_count - prev_del_count) * + 1000000 / + (mdbx_del_time - prev_del_time)); + } + if (mdbx_add_time) { + printf(" tAdd : %" PRIu64 " c/s", + mdbx_add_count * 1000000 / mdbx_add_time); + } + if (mdbx_del_time) { + printf(" tDel : %" PRIu64 " c/s", + mdbx_del_count * 1000000 / mdbx_del_time); + } + puts(""); + } + t = getClockUs(); + prev_add_count = mdbx_add_count; + prev_del_count = mdbx_del_count; + prev_add_time = mdbx_add_time; + prev_del_time = mdbx_del_time; +} + +// static void periodic_add_rec() { +// for (int i = 0; i < 10240; i++) { +// if (ids_count <= REC_COUNT) { +// int64_t id = obj_id++; +// create_record(id); +// add_id_to_pool(id); +// } +// if (ids_count > REC_COUNT) { +// int64_t id = get_id_from_pool(); +// delete_record(id); +// } +// } +// periodic_stat(); +//} + +int main(int argc, char **argv) { + (void)argc; + (void)argv; + + char filename[PATH_MAX]; + int i; + + mkdir(opt_db_path, 0775); + + strcpy(filename, opt_db_path); + strcat(filename, "/mdbx.dat"); + remove(filename); + + strcpy(filename, opt_db_path); + strcat(filename, "/mdbx.lck"); + remove(filename); + + puts("Open DB..."); + db_connect(); + puts("Create data..."); + int64_t t = getClockUs(); + for (i = 0; i < REC_COUNT; i++) { + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + if (i % 1000 == 0) { + int64_t now = getClockUs(); + if ((now - t) > 1000000L) { + periodic_stat(); + t = now; + } + } + } + periodic_stat(); + while (1) { + int i; + for (i = 0; i < 1000; i++) { + int64_t id = obj_id++; + create_record(id); + add_id_to_pool(id); + id = get_id_from_pool(); + delete_record(id); + } + // for (i = 0; i < 50; i++) { + // int64_t id = obj_id++; + // create_record(id); + // add_id_to_pool(id); + // } + // int64_t id = obj_id++; + // create_record(id); + // add_id_to_pool(id); + int64_t now = getClockUs(); + if ((now - t) > 10000000L) { + periodic_stat(); + t = now; + } + } + db_disconnect(); + return 0; +} diff --git a/contrib/db/libmdbx/test/test.cc b/contrib/db/libmdbx/test/test.cc new file mode 100644 index 00000000..806e0b3c --- /dev/null +++ b/contrib/db/libmdbx/test/test.cc @@ -0,0 +1,736 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" + +const char *testcase2str(const actor_testcase testcase) { + switch (testcase) { + default: + assert(false); + return "?!"; + case ac_none: + return "none"; + case ac_hill: + return "hill"; + case ac_deadread: + return "deadread"; + case ac_deadwrite: + return "deadwrite"; + case ac_jitter: + return "jitter"; + case ac_try: + return "try"; + case ac_copy: + return "copy"; + case ac_append: + return "append"; + case ac_ttl: + return "ttl"; + case ac_nested: + return "nested"; + } +} + +const char *status2str(actor_status status) { + switch (status) { + default: + assert(false); + return "?!"; + case as_debuging: + return "debuging"; + case as_running: + return "running"; + case as_successful: + return "successful"; + case as_killed: + return "killed"; + case as_failed: + return "failed"; + case as_coredump: + return "coredump"; + } +} + +const char *keygencase2str(const keygen_case keycase) { + switch (keycase) { + default: + assert(false); + return "?!"; + case kc_random: + return "random"; + case kc_dashes: + return "dashes"; + case kc_custom: + return "custom"; + } +} + +//----------------------------------------------------------------------------- + +int testcase::oom_callback(MDBX_env *env, mdbx_pid_t pid, mdbx_tid_t tid, + uint64_t txn, unsigned gap, size_t space, + int retry) { + + testcase *self = (testcase *)mdbx_env_get_userctx(env); + + if (retry == 0) + log_notice("oom_callback: waitfor pid %u, thread %" PRIuPTR + ", txn #%" PRIu64 ", gap %d, scape %zu", + pid, (size_t)tid, txn, gap, space); + + if (self->should_continue(true)) { + osal_yield(); + if (retry > 0) + osal_udelay(retry * 100); + return 0 /* always retry */; + } + + return -1; +} + +void testcase::db_prepare() { + log_trace(">> db_prepare"); + assert(!db_guard); + + MDBX_env *env = nullptr; + int rc = mdbx_env_create(&env); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_create()", rc); + + assert(env != nullptr); + db_guard.reset(env); + + rc = mdbx_env_set_userctx(env, this); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_userctx()", rc); + + rc = mdbx_env_set_maxreaders(env, config.params.max_readers); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_maxreaders()", rc); + + rc = mdbx_env_set_maxdbs(env, config.params.max_tables); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_maxdbs()", rc); + + rc = mdbx_env_set_oomfunc(env, testcase::oom_callback); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_oomfunc()", rc); + + rc = mdbx_env_set_geometry( + env, config.params.size_lower, config.params.size_now, + config.params.size_upper, config.params.growth_step, + config.params.shrink_threshold, config.params.pagesize); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_set_mapsize()", rc); + + log_trace("<< db_prepare"); +} + +void testcase::db_open() { + log_trace(">> db_open"); + + if (!db_guard) + db_prepare(); + + jitter_delay(true); + int rc = mdbx_env_open(db_guard.get(), config.params.pathname_db.c_str(), + (unsigned)config.params.mode_flags, 0640); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_env_open()", rc); + + log_trace("<< db_open"); +} + +void testcase::db_close() { + log_trace(">> db_close"); + cursor_guard.reset(); + txn_guard.reset(); + db_guard.reset(); + log_trace("<< db_close"); +} + +void testcase::txn_begin(bool readonly, unsigned flags) { + assert((flags & MDBX_RDONLY) == 0); + log_trace(">> txn_begin(%s, 0x%04X)", readonly ? "read-only" : "read-write", + flags); + assert(!txn_guard); + + MDBX_txn *txn = nullptr; + int rc = mdbx_txn_begin(db_guard.get(), nullptr, + readonly ? flags | MDBX_RDONLY : flags, &txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_begin()", rc); + txn_guard.reset(txn); + + log_trace("<< txn_begin(%s, 0x%04X)", readonly ? "read-only" : "read-write", + flags); +} + +int testcase::breakable_commit() { + int rc = MDBX_SUCCESS; + log_trace(">> txn_commit"); + assert(txn_guard); + + MDBX_txn *txn = txn_guard.release(); + txn_inject_writefault(txn); + int err = mdbx_txn_commit(txn); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + rc = err; + err = mdbx_txn_abort(txn); + if (unlikely(err != MDBX_SUCCESS && err != MDBX_THREAD_MISMATCH && + err != MDBX_BAD_TXN)) + failure_perror("mdbx_txn_abort()", err); + } else + failure_perror("mdbx_txn_commit()", err); + } + + log_trace("<< txn_commit: %s", rc ? "failed" : "Ok"); + return rc; +} + +unsigned testcase::txn_underutilization_x256(MDBX_txn *txn) const { + if (txn) { + MDBX_txn_info info; + int err = mdbx_txn_info(txn, &info, false); + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_txn_info()", err); + const size_t left = size_t(info.txn_space_leftover); + const size_t total = + size_t(info.txn_space_leftover) + size_t(info.txn_space_dirty); + return (unsigned)(left / (total >> 8)); + } + return 0; +} + +void testcase::txn_end(bool abort) { + log_trace(">> txn_end(%s)", abort ? "abort" : "commit"); + assert(txn_guard); + + MDBX_txn *txn = txn_guard.release(); + if (abort) { + int err = mdbx_txn_abort(txn); + if (unlikely(err != MDBX_SUCCESS && err != MDBX_THREAD_MISMATCH && + err != MDBX_BAD_TXN)) + failure_perror("mdbx_txn_abort()", err); + } else { + txn_inject_writefault(txn); + int err = mdbx_txn_commit(txn); + if (unlikely(err != MDBX_SUCCESS)) + failure_perror("mdbx_txn_commit()", err); + } + + log_trace("<< txn_end(%s)", abort ? "abort" : "commit"); +} + +void testcase::cursor_open(MDBX_dbi handle) { + log_trace(">> cursor_open(%u)", handle); + assert(!cursor_guard); + assert(txn_guard); + + MDBX_cursor *cursor = nullptr; + int rc = mdbx_cursor_open(txn_guard.get(), handle, &cursor); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_cursor_open()", rc); + cursor_guard.reset(cursor); + + log_trace("<< cursor_open(%u)", handle); +} + +void testcase::cursor_close() { + log_trace(">> cursor_close()"); + assert(cursor_guard); + MDBX_cursor *cursor = cursor_guard.release(); + mdbx_cursor_close(cursor); + log_trace("<< cursor_close()"); +} + +int testcase::breakable_restart() { + int rc = MDBX_SUCCESS; + if (txn_guard) + rc = breakable_commit(); + if (cursor_guard) + cursor_close(); + txn_begin(false, 0); + return rc; +} + +void testcase::txn_restart(bool abort, bool readonly, unsigned flags) { + if (txn_guard) + txn_end(abort); + if (cursor_guard) + cursor_close(); + txn_begin(readonly, flags); +} + +void testcase::txn_inject_writefault(void) { + if (txn_guard) + txn_inject_writefault(txn_guard.get()); +} + +void testcase::txn_inject_writefault(MDBX_txn *txn) { + if (config.params.inject_writefaultn && txn) { + if (config.params.inject_writefaultn <= nops_completed && + (mdbx_txn_flags(txn) & MDBX_RDONLY) == 0) { + log_verbose( + "== txn_inject_writefault(): got %u nops or more, inject FAULT", + config.params.inject_writefaultn); + log_flush(); +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + TerminateProcess(GetCurrentProcess(), 42); +#else + raise(SIGKILL); +#endif + } + } +} + +bool testcase::wait4start() { + if (config.wait4id) { + log_trace(">> wait4start(%u)", config.wait4id); + assert(!global::singlemode); + int rc = osal_waitfor(config.wait4id); + if (rc) { + log_trace("<< wait4start(%u), failed %s", config.wait4id, + test_strerror(rc)); + return false; + } + } else { + log_trace("== skip wait4start: not needed"); + } + + if (config.params.delaystart) { + int rc = osal_delay(config.params.delaystart); + if (rc) { + log_trace("<< delay(%u), failed %s", config.params.delaystart, + test_strerror(rc)); + return false; + } + } else { + log_trace("== skip delay: not needed"); + } + + return true; +} + +void testcase::kick_progress(bool active) const { + if (!global::config::progress_indicator) + return; + logging::progress_canary(active); +} + +void testcase::report(size_t nops_done) { + assert(nops_done > 0); + if (!nops_done) + return; + + nops_completed += nops_done; + log_debug("== complete +%" PRIuPTR " iteration, total %" PRIuPTR " done", + nops_done, nops_completed); + + kick_progress(true); + + if (config.signal_nops && !signalled && + config.signal_nops <= nops_completed) { + log_trace(">> signal(n-ops %" PRIuPTR ")", nops_completed); + if (!global::singlemode) + osal_broadcast(config.actor_id); + signalled = true; + log_trace("<< signal(n-ops %" PRIuPTR ")", nops_completed); + } +} + +void testcase::signal() { + if (!signalled) { + log_trace(">> signal(forced)"); + if (!global::singlemode) + osal_broadcast(config.actor_id); + signalled = true; + log_trace("<< signal(forced)"); + } +} + +bool testcase::setup() { + db_prepare(); + if (!wait4start()) + return false; + + start_timestamp = chrono::now_motonic(); + nops_completed = 0; + return true; +} + +bool testcase::teardown() { + log_trace(">> testcase::teardown"); + signal(); + db_close(); + log_trace("<< testcase::teardown"); + return true; +} + +bool testcase::should_continue(bool check_timeout_only) const { + bool result = true; + + if (config.params.test_duration) { + chrono::time since; + since.fixedpoint = + chrono::now_motonic().fixedpoint - start_timestamp.fixedpoint; + if (since.seconds() >= config.params.test_duration) + result = false; + } + + if (!check_timeout_only && config.params.test_nops && + nops_completed >= config.params.test_nops) + result = false; + + if (result) + kick_progress(false); + + return result; +} + +void testcase::fetch_canary() { + mdbx_canary canary_now; + log_trace(">> fetch_canary"); + + int rc = mdbx_canary_get(txn_guard.get(), &canary_now); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_canary_get()", rc); + + if (canary_now.v < last.canary.v) + failure("fetch_canary: %" PRIu64 "(canary-now.v) < %" PRIu64 + "(canary-last.v)", + canary_now.v, last.canary.v); + if (canary_now.y < last.canary.y) + failure("fetch_canary: %" PRIu64 "(canary-now.y) < %" PRIu64 + "(canary-last.y)", + canary_now.y, last.canary.y); + + last.canary = canary_now; + log_trace("<< fetch_canary: db-sequence %" PRIu64 + ", db-sequence.txnid %" PRIu64, + last.canary.y, last.canary.v); +} + +void testcase::update_canary(uint64_t increment) { + mdbx_canary canary_now = last.canary; + + log_trace(">> update_canary: sequence %" PRIu64 " += %" PRIu64, canary_now.y, + increment); + canary_now.y += increment; + + int rc = mdbx_canary_put(txn_guard.get(), &canary_now); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_canary_put()", rc); + + log_trace("<< update_canary: sequence = %" PRIu64, canary_now.y); +} + +int testcase::db_open__begin__table_create_open_clean(MDBX_dbi &handle) { + db_open(); + + int err, retry_left = 42; + for (;;) { + txn_begin(false); + handle = db_table_open(true); + db_table_clear(handle); + err = breakable_commit(); + if (likely(err == MDBX_SUCCESS)) { + txn_begin(false); + return MDBX_SUCCESS; + } + if (--retry_left == 0) + break; + jitter_delay(true); + } + log_notice("db_begin_table_create_open_clean: bailout due '%s'", + mdbx_strerror(err)); + return err; +} + +MDBX_dbi testcase::db_table_open(bool create) { + log_trace(">> testcase::db_table_create"); + + char tablename_buf[16]; + const char *tablename = nullptr; + if (config.space_id) { + int rc = snprintf(tablename_buf, sizeof(tablename_buf), "TBL%04u", + config.space_id); + if (rc < 4 || rc >= (int)sizeof(tablename_buf) - 1) + failure("snprintf(tablename): %d", rc); + tablename = tablename_buf; + } + log_debug("use %s table", tablename ? tablename : "MAINDB"); + + MDBX_dbi handle = 0; + int rc = mdbx_dbi_open(txn_guard.get(), tablename, + (create ? MDBX_CREATE : 0) | config.params.table_flags, + &handle); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_dbi_open()", rc); + + log_trace("<< testcase::db_table_create, handle %u", handle); + return handle; +} + +void testcase::db_table_drop(MDBX_dbi handle) { + log_trace(">> testcase::db_table_drop, handle %u", handle); + + if (config.params.drop_table) { + int rc = mdbx_drop(txn_guard.get(), handle, true); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_drop(delete=true)", rc); + log_trace("<< testcase::db_table_drop"); + } else { + log_trace("<< testcase::db_table_drop: not needed"); + } +} + +void testcase::db_table_clear(MDBX_dbi handle, MDBX_txn *txn) { + log_trace(">> testcase::db_table_clear, handle %u", handle); + int rc = mdbx_drop(txn ? txn : txn_guard.get(), handle, false); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_drop(delete=false)", rc); + log_trace("<< testcase::db_table_clear"); +} + +void testcase::db_table_close(MDBX_dbi handle) { + log_trace(">> testcase::db_table_close, handle %u", handle); + assert(!txn_guard); + int rc = mdbx_dbi_close(db_guard.get(), handle); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_dbi_close()", rc); + log_trace("<< testcase::db_table_close"); +} + +void testcase::checkdata(const char *step, MDBX_dbi handle, MDBX_val key2check, + MDBX_val expected_valued) { + MDBX_val actual_value = expected_valued; + int rc = mdbx_get_nearest(txn_guard.get(), handle, &key2check, &actual_value); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror(step, rc); + if (!is_samedata(&actual_value, &expected_valued)) + failure("%s data mismatch", step); +} + +//----------------------------------------------------------------------------- + +bool test_execute(const actor_config &config_const) { + const mdbx_pid_t pid = osal_getpid(); + actor_config config = config_const; + + if (global::singlemode) { + logging::setup(format("single_%s", testcase2str(config.testcase))); + } else { + logging::setup((logging::loglevel)config.params.loglevel, + format("child_%u.%u", config.actor_id, config.space_id)); + log_trace(">> wait4barrier"); + osal_wait4barrier(); + log_trace("<< wait4barrier"); + } + + try { + std::unique_ptr test; + switch (config.testcase) { + case ac_hill: + test.reset(new testcase_hill(config, pid)); + break; + case ac_deadread: + test.reset(new testcase_deadread(config, pid)); + break; + case ac_deadwrite: + test.reset(new testcase_deadwrite(config, pid)); + break; + case ac_jitter: + test.reset(new testcase_jitter(config, pid)); + break; + case ac_try: + test.reset(new testcase_try(config, pid)); + break; + case ac_copy: + test.reset(new testcase_copy(config, pid)); + break; + case ac_append: + test.reset(new testcase_append(config, pid)); + break; + case ac_ttl: + test.reset(new testcase_ttl(config, pid)); + break; + case ac_nested: + test.reset(new testcase_nested(config, pid)); + break; + default: + test.reset(new testcase(config, pid)); + break; + } + + size_t iter = 0; + do { + iter++; + if (!test->setup()) { + log_notice("test setup failed"); + return false; + } + if (!test->run()) { + log_notice("test failed"); + return false; + } + if (!test->teardown()) { + log_notice("test teardown failed"); + return false; + } + + if (config.params.nrepeat == 1) + log_verbose("test successed"); + else { + if (config.params.nrepeat) + log_verbose("test successed (iteration %zi of %zi)", iter, + size_t(config.params.nrepeat)); + else + log_verbose("test successed (iteration %zi)", iter); + config.params.keygen.seed += INT32_C(0xA4F4D37B); + } + + } while (config.params.nrepeat == 0 || iter < config.params.nrepeat); + return true; + } catch (const std::exception &pipets) { + failure("***** Exception: %s *****", pipets.what()); + return false; + } +} + +//----------------------------------------------------------------------------- + +int testcase::insert(const keygen::buffer &akey, const keygen::buffer &adata, + unsigned flags) { + int err = mdbx_put(txn_guard.get(), dbi, &akey->value, &adata->value, flags); + if (err == MDBX_SUCCESS && config.params.speculum) { + const auto S_key = S(akey); + const auto S_data = S(adata); + const bool inserted = speculum.emplace(S_key, S_data).second; + assert(inserted); + (void)inserted; + } + return err; +} + +int testcase::replace(const keygen::buffer &akey, + const keygen::buffer &new_data, + const keygen::buffer &old_data, unsigned flags) { + if (config.params.speculum) { + const auto S_key = S(akey); + const auto S_old = S(old_data); + const auto S_new = S(new_data); + const auto removed = speculum.erase(SET::key_type(S_key, S_old)); + assert(removed == 1); + (void)removed; + const bool inserted = speculum.emplace(S_key, S_new).second; + assert(inserted); + (void)inserted; + } + return mdbx_replace(txn_guard.get(), dbi, &akey->value, &new_data->value, + &old_data->value, flags); +} + +int testcase::remove(const keygen::buffer &akey, const keygen::buffer &adata) { + if (config.params.speculum) { + const auto S_key = S(akey); + const auto S_data = S(adata); + const auto removed = speculum.erase(SET::key_type(S_key, S_data)); + assert(removed == 1); + (void)removed; + } + return mdbx_del(txn_guard.get(), dbi, &akey->value, &adata->value); +} + +bool testcase::speculum_verify() const { + if (!config.params.speculum) + return true; + + char dump_key[128], dump_value[128]; + char dump_mkey[128], dump_mvalue[128]; + + MDBX_cursor *cursor; + int err = mdbx_cursor_open(txn_guard.get(), dbi, &cursor); + if (err != MDBX_SUCCESS) + failure_perror("mdbx_cursor_open()", err); + + bool rc = true; + MDBX_val akey, avalue; + MDBX_val mkey, mvalue; + err = mdbx_cursor_get(cursor, &akey, &avalue, MDBX_FIRST); + + assert(std::is_sorted(speculum.cbegin(), speculum.cend(), ItemCompare(this))); + auto it = speculum.cbegin(); + while (true) { + if (err != MDBX_SUCCESS) { + akey.iov_len = avalue.iov_len = 0; + akey.iov_base = avalue.iov_base = nullptr; + } + const auto S_key = S(akey); + const auto S_data = S(avalue); + if (it != speculum.cend()) { + mkey.iov_base = (void *)it->first.c_str(); + mkey.iov_len = it->first.size(); + mvalue.iov_base = (void *)it->second.c_str(); + mvalue.iov_len = it->second.size(); + } + if (err == MDBX_SUCCESS && it != speculum.cend() && S_key == it->first && + S_data == it->second) { + ++it; + err = mdbx_cursor_get(cursor, &akey, &avalue, MDBX_NEXT); + } else if (err == MDBX_SUCCESS && + (it == speculum.cend() || S_key < it->first || + (S_key == it->first && S_data < it->second))) { + if (it != speculum.cend()) { + log_error("extra pair: db{%s, %s} < mi{%s, %s}", + mdbx_dump_val(&akey, dump_key, sizeof(dump_key)), + mdbx_dump_val(&avalue, dump_value, sizeof(dump_value)), + mdbx_dump_val(&mkey, dump_mkey, sizeof(dump_mkey)), + mdbx_dump_val(&mvalue, dump_mvalue, sizeof(dump_mvalue))); + } else { + log_error("extra pair: db{%s, %s} < mi.END", + mdbx_dump_val(&akey, dump_key, sizeof(dump_key)), + mdbx_dump_val(&avalue, dump_value, sizeof(dump_value))); + } + err = mdbx_cursor_get(cursor, &akey, &avalue, MDBX_NEXT); + rc = false; + } else if (it != speculum.cend() && + (err == MDBX_NOTFOUND || S_key > it->first || + (S_key == it->first && S_data > it->second))) { + if (err == MDBX_NOTFOUND) { + log_error("lost pair: db.END > mi{%s, %s}", + mdbx_dump_val(&mkey, dump_mkey, sizeof(dump_mkey)), + mdbx_dump_val(&mvalue, dump_mvalue, sizeof(dump_mvalue))); + } else { + log_error("lost pair: db{%s, %s} > mi{%s, %s}", + mdbx_dump_val(&akey, dump_key, sizeof(dump_key)), + mdbx_dump_val(&avalue, dump_value, sizeof(dump_value)), + mdbx_dump_val(&mkey, dump_mkey, sizeof(dump_mkey)), + mdbx_dump_val(&mvalue, dump_mvalue, sizeof(dump_mvalue))); + } + ++it; + rc = false; + } else if (err == MDBX_NOTFOUND && it == speculum.cend()) { + break; + } else if (err != MDBX_SUCCESS) { + failure_perror("mdbx_cursor_get()", err); + } else { + assert(!"WTF?"); + } + } + + mdbx_cursor_close(cursor); + return rc; +} diff --git a/contrib/db/libmdbx/test/test.h b/contrib/db/libmdbx/test/test.h new file mode 100644 index 00000000..178100e1 --- /dev/null +++ b/contrib/db/libmdbx/test/test.h @@ -0,0 +1,314 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once + +#include "base.h" +#include "chrono.h" +#include "config.h" +#include "keygen.h" +#include "log.h" +#include "osal.h" +#include "utils.h" + +#include +#include +#include +#include + +#ifndef HAVE_cxx17_std_string_view +#if __cplusplus >= 201703L && __has_include() +#include +#define HAVE_cxx17_std_string_view 1 +#else +#define HAVE_cxx17_std_string_view 0 +#endif +#endif /* HAVE_cxx17_std_string_view */ + +#if HAVE_cxx17_std_string_view +#include +#endif + +bool test_execute(const actor_config &config); +std::string thunk_param(const actor_config &config); +void testcase_setup(const char *casename, actor_params ¶ms, + unsigned &last_space_id); +void configure_actor(unsigned &last_space_id, const actor_testcase testcase, + const char *space_id_cstr, const actor_params ¶ms); +void keycase_setup(const char *casename, actor_params ¶ms); + +namespace global { + +extern const char thunk_param_prefix[]; +extern std::vector actors; +extern std::unordered_map events; +extern std::unordered_map pid2actor; +extern std::set databases; +extern unsigned nactors; +extern chrono::time start_motonic; +extern chrono::time deadline_motonic; +extern bool singlemode; + +namespace config { +extern unsigned timeout_duration_seconds; +extern bool dump_config; +extern bool cleanup_before; +extern bool cleanup_after; +extern bool failfast; +extern bool progress_indicator; +extern bool console_mode; +} /* namespace config */ + +} /* namespace global */ + +//----------------------------------------------------------------------------- + +struct db_deleter /* : public std::unary_function */ { + void operator()(MDBX_env *env) const { mdbx_env_close(env); } +}; + +struct txn_deleter /* : public std::unary_function */ { + void operator()(MDBX_txn *txn) const { + int rc = mdbx_txn_abort(txn); + if (rc) + log_trouble(__func__, "mdbx_txn_abort()", rc); + } +}; + +struct cursor_deleter /* : public std::unary_function */ { + void operator()(MDBX_cursor *cursor) const { mdbx_cursor_close(cursor); } +}; + +typedef std::unique_ptr scoped_db_guard; +typedef std::unique_ptr scoped_txn_guard; +typedef std::unique_ptr scoped_cursor_guard; + +//----------------------------------------------------------------------------- + +class testcase { +protected: +#if HAVE_cxx17_std_string_view + using data_view = std::string_view; +#else + using data_view = std::string; +#endif + static inline data_view S(const MDBX_val &v) { + return data_view(static_cast(v.iov_base), v.iov_len); + } + static inline data_view S(const keygen::buffer &b) { return S(b->value); } + + using Item = std::pair; + struct ItemCompare { + const testcase *context; + ItemCompare(const testcase *owner) : context(owner) {} + + bool operator()(const Item &a, const Item &b) const { + MDBX_val va, vb; + va.iov_base = (void *)a.first.data(); + va.iov_len = a.first.size(); + vb.iov_base = (void *)b.first.data(); + vb.iov_len = b.first.size(); + int cmp = mdbx_cmp(context->txn_guard.get(), context->dbi, &va, &vb); + if (cmp == 0 && + (context->config.params.table_flags & MDBX_DUPSORT) != 0) { + va.iov_base = (void *)a.second.data(); + va.iov_len = a.second.size(); + vb.iov_base = (void *)b.second.data(); + vb.iov_len = b.second.size(); + cmp = mdbx_dcmp(context->txn_guard.get(), context->dbi, &va, &vb); + } + return cmp < 0; + } + }; + using SET = std::set; + + const actor_config &config; + const mdbx_pid_t pid; + + MDBX_dbi dbi; + scoped_db_guard db_guard; + scoped_txn_guard txn_guard; + scoped_cursor_guard cursor_guard; + bool signalled; + + size_t nops_completed; + chrono::time start_timestamp; + keygen::buffer key; + keygen::buffer data; + keygen::maker keyvalue_maker; + + struct { + mdbx_canary canary; + } last; + + SET speculum; + bool speculum_verify() const; + int insert(const keygen::buffer &akey, const keygen::buffer &adata, + unsigned flags); + int replace(const keygen::buffer &akey, const keygen::buffer &new_value, + const keygen::buffer &old_value, unsigned flags); + int remove(const keygen::buffer &akey, const keygen::buffer &adata); + + static int oom_callback(MDBX_env *env, mdbx_pid_t pid, mdbx_tid_t tid, + uint64_t txn, unsigned gap, size_t space, int retry); + + bool is_nested_txn_available() const { + return (config.params.mode_flags & MDBX_WRITEMAP) == 0; + } + void kick_progress(bool active) const; + void db_prepare(); + void db_open(); + void db_close(); + void txn_begin(bool readonly, unsigned flags = 0); + int breakable_commit(); + void txn_end(bool abort); + int breakable_restart(); + void txn_restart(bool abort, bool readonly, unsigned flags = 0); + void cursor_open(MDBX_dbi handle); + void cursor_close(); + void txn_inject_writefault(void); + void txn_inject_writefault(MDBX_txn *txn); + void fetch_canary(); + void update_canary(uint64_t increment); + void checkdata(const char *step, MDBX_dbi handle, MDBX_val key2check, + MDBX_val expected_valued); + unsigned txn_underutilization_x256(MDBX_txn *txn) const; + + MDBX_dbi db_table_open(bool create); + void db_table_drop(MDBX_dbi handle); + void db_table_clear(MDBX_dbi handle, MDBX_txn *txn = nullptr); + void db_table_close(MDBX_dbi handle); + int db_open__begin__table_create_open_clean(MDBX_dbi &handle); + + bool wait4start(); + void report(size_t nops_done); + void signal(); + bool should_continue(bool check_timeout_only = false) const; + + void generate_pair(const keygen::serial_t serial, keygen::buffer &out_key, + keygen::buffer &out_value, keygen::serial_t data_age = 0) { + keyvalue_maker.pair(serial, out_key, out_value, data_age); + } + + void generate_pair(const keygen::serial_t serial, + keygen::serial_t data_age = 0) { + generate_pair(serial, key, data, data_age); + } + + bool mode_readonly() const { + return (config.params.mode_flags & MDBX_RDONLY) ? true : false; + } + +public: + testcase(const actor_config &config, const mdbx_pid_t pid) + : config(config), pid(pid), signalled(false), nops_completed(0), + speculum(ItemCompare(this)) { + start_timestamp.reset(); + memset(&last, 0, sizeof(last)); + } + + virtual bool setup(); + virtual bool run() { return true; } + virtual bool teardown(); + virtual ~testcase() {} +}; + +class testcase_ttl : public testcase { +public: + testcase_ttl(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool run() override; +}; + +class testcase_hill : public testcase { + using inherited = testcase; + SET speculum_commited; + +public: + testcase_hill(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid), speculum_commited(ItemCompare(this)) {} + bool run() override; +}; + +class testcase_append : public testcase { +public: + testcase_append(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool run() override; +}; + +class testcase_deadread : public testcase { +public: + testcase_deadread(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool run() override; +}; + +class testcase_deadwrite : public testcase { +public: + testcase_deadwrite(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool run() override; +}; + +class testcase_jitter : public testcase { +public: + testcase_jitter(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool run() override; +}; + +class testcase_try : public testcase { +public: + testcase_try(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool run() override; +}; + +class testcase_copy : public testcase { + const std::string copy_pathname; + void copy_db(const bool with_compaction); + +public: + testcase_copy(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid), + copy_pathname(config.params.pathname_db + "-copy") {} + bool run() override; +}; + +class testcase_nested : public testcase { + using inherited = testcase; + using FIFO = std::deque>; + + uint64_t serial; + FIFO fifo; + std::stack> stack; + + bool trim_tail(unsigned window_width); + bool grow_head(unsigned head_count); + bool pop_txn(bool abort); + bool pop_txn() { + return pop_txn(inherited::is_nested_txn_available() ? flipcoin_x3() + : flipcoin_x2()); + } + void push_txn(); + bool stochastic_breakable_restart_with_nested(bool force_restart = false); + +public: + testcase_nested(const actor_config &config, const mdbx_pid_t pid) + : testcase(config, pid) {} + bool setup() override; + bool run() override; + bool teardown() override; +}; diff --git a/contrib/db/libmdbx/test/try.cc b/contrib/db/libmdbx/test/try.cc new file mode 100644 index 00000000..adb01130 --- /dev/null +++ b/contrib/db/libmdbx/test/try.cc @@ -0,0 +1,20 @@ +#include "test.h" + +bool testcase_try::run() { + db_open(); + assert(!txn_guard); + + MDBX_txn *txn = nullptr; + MDBX_txn *txn2 = nullptr; + int rc = mdbx_txn_begin(db_guard.get(), nullptr, 0, &txn); + if (unlikely(rc != MDBX_SUCCESS)) + failure_perror("mdbx_txn_begin(MDBX_TRYTXN)", rc); + else { + rc = mdbx_txn_begin(db_guard.get(), nullptr, MDBX_TRYTXN, &txn2); + if (unlikely(rc != MDBX_BUSY)) + failure_perror("mdbx_txn_begin(MDBX_TRYTXN)", rc); + } + + txn_guard.reset(txn); + return true; +} diff --git a/contrib/db/libmdbx/test/ttl.cc b/contrib/db/libmdbx/test/ttl.cc new file mode 100644 index 00000000..782a8b4d --- /dev/null +++ b/contrib/db/libmdbx/test/ttl.cc @@ -0,0 +1,172 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" +#include +#include + +static unsigned edge2window(uint64_t edge, unsigned window_max) { + const double rnd = u64_to_double1(bleach64(edge)); + const unsigned window = window_max - std::lrint(std::pow(window_max, rnd)); + return window; +} + +static unsigned edge2count(uint64_t edge, unsigned count_max) { + const double rnd = u64_to_double1(prng64_map1_white(edge)); + const unsigned count = std::lrint(std::pow(count_max, rnd)); + return count; +} + +bool testcase_ttl::run() { + int err = db_open__begin__table_create_open_clean(dbi); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("ttl: bailout-prepare due '%s'", mdbx_strerror(err)); + return false; + } + + /* LY: тест "эмуляцией time-to-live": + * - организуется "скользящее окно", которое двигается вперед вдоль + * числовой оси каждую транзакцию. + * - по переднему краю "скользящего окна" записи добавляются в таблицу, + * а по заднему удаляются. + * - количество добавляемых/удаляемых записей псевдослучайно зависит + * от номера транзакции, но с экспоненциальным распределением. + * - размер "скользящего окна" также псевдослучайно зависит от номера + * транзакции с "отрицательным" экспоненциальным распределением + * MAX_WIDTH - exp(rnd(N)), при уменьшении окна сдвигается задний + * край и удаляются записи позади него. + * + * Таким образом имитируется поведение таблицы с TTL: записи стохастически + * добавляются и удаляются, но изредка происходят массивные удаления. + */ + + /* LY: для параметризации используем подходящие параметры, которые не имеют + * здесь смысла в первоначальном значении. */ + const unsigned window_max_lower = 333; + const unsigned count_max_lower = 333; + + const unsigned window_max = (config.params.batch_read > window_max_lower) + ? config.params.batch_read + : window_max_lower; + const unsigned count_max = (config.params.batch_write > count_max_lower) + ? config.params.batch_write + : count_max_lower; + log_verbose("ttl: using `batch_read` value %u for window_max", window_max); + log_verbose("ttl: using `batch_write` value %u for count_max", count_max); + + uint64_t seed = + prng64_map2_white(config.params.keygen.seed) + config.actor_id; + keyvalue_maker.setup(config.params, config.actor_id, 0 /* thread_number */); + key = keygen::alloc(config.params.keylen_max); + data = keygen::alloc(config.params.datalen_max); + const unsigned insert_flags = (config.params.table_flags & MDBX_DUPSORT) + ? MDBX_NODUPDATA + : MDBX_NODUPDATA | MDBX_NOOVERWRITE; + + std::deque> fifo; + uint64_t serial = 0; + bool rc = false; + while (should_continue()) { + const uint64_t salt = prng64_white(seed) /* mdbx_txn_id(txn_guard.get()) */; + + const unsigned window_width = + flipcoin_x4() ? 0 : edge2window(salt, window_max); + unsigned head_count = edge2count(salt, count_max); + log_debug("ttl: step #%zu (serial %" PRIu64 + ", window %u, count %u) salt %" PRIu64, + nops_completed, serial, window_width, head_count, salt); + + if (window_width) { + while (fifo.size() > window_width) { + uint64_t tail_serial = fifo.back().first; + const unsigned tail_count = fifo.back().second; + log_trace("ttl: pop-tail (serial %" PRIu64 ", count %u)", tail_serial, + tail_count); + fifo.pop_back(); + for (unsigned n = 0; n < tail_count; ++n) { + log_trace("ttl: remove-tail %" PRIu64, tail_serial); + generate_pair(tail_serial); + err = mdbx_del(txn_guard.get(), dbi, &key->value, &data->value); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("ttl: tail-bailout due '%s'", mdbx_strerror(err)); + goto bailout; + } + failure_perror("mdbx_del(tail)", err); + } + if (unlikely(!keyvalue_maker.increment(tail_serial, 1))) + failure("ttl: unexpected key-space overflow on the tail"); + } + } + } else { + log_trace("ttl: purge state"); + db_table_clear(dbi); + fifo.clear(); + } + + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("ttl: bailout at commit due '%s'", mdbx_strerror(err)); + break; + } + fifo.push_front(std::make_pair(serial, head_count)); + retry: + for (unsigned n = 0; n < head_count; ++n) { + log_trace("ttl: insert-head %" PRIu64, serial); + generate_pair(serial); + err = mdbx_put(txn_guard.get(), dbi, &key->value, &data->value, + insert_flags); + if (unlikely(err != MDBX_SUCCESS)) { + if (err == MDBX_MAP_FULL && config.params.ignore_dbfull) { + log_notice("ttl: head-insert skip due '%s'", mdbx_strerror(err)); + txn_restart(true, false); + serial = fifo.front().first; + fifo.front().second = head_count = n; + goto retry; + } + failure_perror("mdbx_put(head)", err); + } + + if (unlikely(!keyvalue_maker.increment(serial, 1))) { + log_notice("ttl: unexpected key-space overflow"); + goto bailout; + } + } + err = breakable_restart(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("ttl: head-commit skip due '%s'", mdbx_strerror(err)); + serial = fifo.front().first; + fifo.pop_front(); + } + + report(1); + rc = true; + } + +bailout: + txn_end(true); + if (dbi) { + if (config.params.drop_table && !mode_readonly()) { + txn_begin(false); + db_table_drop(dbi); + err = breakable_commit(); + if (unlikely(err != MDBX_SUCCESS)) { + log_notice("ttl: bailout-clean due '%s'", mdbx_strerror(err)); + return false; + } + } else + db_table_close(dbi); + } + return rc; +} diff --git a/contrib/db/libmdbx/test/utils.cc b/contrib/db/libmdbx/test/utils.cc new file mode 100644 index 00000000..311cf544 --- /dev/null +++ b/contrib/db/libmdbx/test/utils.cc @@ -0,0 +1,370 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#include "test.h" +#include +#if defined(HAVE_IEEE754_H) || __has_include() +#include +#endif +#if defined(__APPLE__) || defined(__MACH__) +#include +#endif /* defined(__APPLE__) || defined(__MACH__) */ + +std::string format(const char *fmt, ...) { + va_list ap, ones; + va_start(ap, fmt); + va_copy(ones, ap); +#ifdef _MSC_VER + int needed = _vscprintf(fmt, ap); +#else + int needed = vsnprintf(nullptr, 0, fmt, ap); +#endif + assert(needed >= 0); + va_end(ap); + std::string result; + result.reserve((size_t)needed + 1); + result.resize((size_t)needed, '\0'); + int actual = vsnprintf((char *)result.data(), result.capacity(), fmt, ones); + assert(actual == needed); + (void)actual; + va_end(ones); + return result; +} + +std::string data2hex(const void *ptr, size_t bytes, simple_checksum &checksum) { + std::string result; + if (bytes > 0) { + const uint8_t *data = (const uint8_t *)ptr; + checksum.push(data, bytes); + result.reserve(bytes * 2); + const uint8_t *const end = data + bytes; + do { + char h = *data >> 4; + char l = *data & 15; + result.push_back((l < 10) ? l + '0' : l - 10 + 'a'); + result.push_back((h < 10) ? h + '0' : h - 10 + 'a'); + } while (++data < end); + } + assert(result.size() == bytes * 2); + return result; +} + +bool hex2data(const char *hex_begin, const char *hex_end, void *ptr, + size_t bytes, simple_checksum &checksum) { + if (bytes * 2 != (size_t)(hex_end - hex_begin)) + return false; + + uint8_t *data = (uint8_t *)ptr; + for (const char *hex = hex_begin; hex != hex_end; hex += 2, ++data) { + unsigned l = hex[0], h = hex[1]; + + if (l >= '0' && l <= '9') + l = l - '0'; + else if (l >= 'A' && l <= 'F') + l = l - 'A' + 10; + else if (l >= 'a' && l <= 'f') + l = l - 'a' + 10; + else + return false; + + if (h >= '0' && h <= '9') + h = h - '0'; + else if (h >= 'A' && h <= 'F') + h = h - 'A' + 10; + else if (h >= 'a' && h <= 'f') + h = h - 'a' + 10; + else + return false; + + uint32_t c = l + (h << 4); + checksum.push(c); + *data = (uint8_t)c; + } + return true; +} + +bool is_samedata(const MDBX_val *a, const MDBX_val *b) { + return a->iov_len == b->iov_len && + memcmp(a->iov_base, b->iov_base, a->iov_len) == 0; +} + +//----------------------------------------------------------------------------- + +/* TODO: replace my 'libmera' from t1ha. */ +uint64_t entropy_ticks(void) { +#if defined(EMSCRIPTEN) + return (uint64_t)emscripten_get_now(); +#endif /* EMSCRIPTEN */ + +#if defined(__APPLE__) || defined(__MACH__) + return mach_absolute_time(); +#endif /* defined(__APPLE__) || defined(__MACH__) */ + +#if defined(__sun__) || defined(__sun) + return gethrtime(); +#endif /* __sun__ */ + +#if defined(__GNUC__) || defined(__clang__) + +#if defined(__ia64__) + uint64_t ticks; + __asm __volatile("mov %0=ar.itc" : "=r"(ticks)); + return ticks; +#elif defined(__hppa__) + uint64_t ticks; + __asm __volatile("mfctl 16, %0" : "=r"(ticks)); + return ticks; +#elif defined(__s390__) + uint64_t ticks; + __asm __volatile("stck 0(%0)" : : "a"(&(ticks)) : "memory", "cc"); + return ticks; +#elif defined(__alpha__) + uint64_t ticks; + __asm __volatile("rpcc %0" : "=r"(ticks)); + return ticks; +#elif defined(__sparc__) || defined(__sparc) || defined(__sparc64__) || \ + defined(__sparc64) || defined(__sparc_v8plus__) || \ + defined(__sparc_v8plus) || defined(__sparc_v8plusa__) || \ + defined(__sparc_v8plusa) || defined(__sparc_v9__) || defined(__sparc_v9) + + union { + uint64_t u64; + struct { +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + uint32_t h, l; +#else + uint32_t l, h; +#endif + } u32; + } cycles; + +#if defined(__sparc_v8plus__) || defined(__sparc_v8plusa__) || \ + defined(__sparc_v9__) || defined(__sparc_v8plus) || \ + defined(__sparc_v8plusa) || defined(__sparc_v9) + +#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul || \ + defined(__sparc64__) || defined(__sparc64) + __asm __volatile("rd %%tick, %0" : "=r"(cycles.u64)); +#else + __asm __volatile("rd %%tick, %1; srlx %1, 32, %0" + : "=r"(cycles.u32.h), "=r"(cycles.u32.l)); +#endif /* __sparc64__ */ + +#else + __asm __volatile(".byte 0x83, 0x41, 0x00, 0x00; mov %%g1, %0" + : "=r"(cycles.u64) + : + : "%g1"); +#endif /* __sparc8plus__ || __sparc_v9__ */ + return cycles.u64; + +#elif (defined(__powerpc64__) || defined(__ppc64__) || defined(__ppc64) || \ + defined(__powerpc64)) + uint64_t ticks; + __asm __volatile("mfspr %0, 268" : "=r"(ticks)); + return ticks; +#elif (defined(__powerpc__) || defined(__ppc__) || defined(__powerpc) || \ + defined(__ppc)) +#if UINTPTR_MAX > 0xffffFFFFul || ULONG_MAX > 0xffffFFFFul + uint64_t ticks; + __asm __volatile("mftb %0" : "=r"(ticks)); + *now = ticks; +#else + uint64_t ticks; + uint32_t low, high_before, high_after; + __asm __volatile("mftbu %0; mftb %1; mftbu %2" + : "=r"(high_before), "=r"(low), "=r"(high_after)); + ticks = (uint64_t)high_after << 32; + ticks |= low & /* zeroes if high part has changed */ + ~(high_before - high_after); +#endif +#elif (defined(__aarch64__) || (defined(__ARM_ARCH) && __ARM_ARCH > 7)) && \ + !defined(MDBX_SAFE4QEMU) + uint64_t virtual_timer; + __asm __volatile("mrs %0, cntvct_el0" : "=r"(virtual_timer)); + return virtual_timer; +#elif (defined(__ARM_ARCH) && __ARM_ARCH > 5 && __ARM_ARCH < 8) || \ + defined(_M_ARM) + static uint32_t pmcntenset = 0x00425B00; + if (unlikely(pmcntenset == 0x00425B00)) { + uint32_t pmuseren; +#ifdef _M_ARM + pmuseren = _MoveFromCoprocessor(15, 0, 9, 14, 0); +#else + __asm("mrc p15, 0, %0, c9, c14, 0" : "=r"(pmuseren)); +#endif + if (1 & pmuseren /* Is it allowed for user mode code? */) { +#ifdef _M_ARM + pmcntenset = _MoveFromCoprocessor(15, 0, 9, 12, 1); +#else + __asm("mrc p15, 0, %0, c9, c12, 1" : "=r"(pmcntenset)); +#endif + } else + pmcntenset = 0; + } + if (pmcntenset & 0x80000000ul /* Is it counting? */) { +#ifdef _M_ARM + return __rdpmccntr64(); +#else + uint32_t pmccntr; + __asm __volatile("mrc p15, 0, %0, c9, c13, 0" : "=r"(pmccntr)); + return pmccntr; +#endif + } +#elif defined(__mips__) || defined(__mips) || defined(_R4000) + unsigned count; + __asm __volatile("rdhwr %0, $2" : "=r"(count)); + return count; +#endif /* arch selector */ +#endif /* __GNUC__ || __clang__ */ + +#if defined(__e2k__) || defined(__ia32__) + return __rdtsc(); +#elif defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + LARGE_INTEGER PerformanceCount; + if (QueryPerformanceCounter(&PerformanceCount)) + return PerformanceCount.QuadPart; + return GetTickCount64(); +#else + struct timespec ts; +#if defined(CLOCK_MONOTONIC_COARSE) + clockid_t clk_id = CLOCK_MONOTONIC_COARSE; +#elif defined(CLOCK_MONOTONIC_RAW) + clockid_t clk_id = CLOCK_MONOTONIC_RAW; +#else + clockid_t clk_id = CLOCK_MONOTONIC; +#endif + int rc = clock_gettime(clk_id, &ts); + if (unlikely(rc)) + failure_perror("clock_gettime()", rc); + + return (((uint64_t)ts.tv_sec) << 32) + ts.tv_nsec; +#endif +} + +//----------------------------------------------------------------------------- + +uint64_t prng64_white(uint64_t &state) { + state = prng64_map2_careless(state); + return bleach64(state); +} + +uint32_t prng32(uint64_t &state) { + return (uint32_t)(prng64_careless(state) >> 32); +} + +void prng_fill(uint64_t &state, void *ptr, size_t bytes) { + while (bytes >= 4) { + *((uint32_t *)ptr) = prng32(state); + ptr = (uint32_t *)ptr + 1; + bytes -= 4; + } + + switch (bytes & 3) { + case 3: { + uint32_t u32 = prng32(state); + memcpy(ptr, &u32, 3); + } break; + case 2: + *((uint16_t *)ptr) = (uint16_t)prng32(state); + break; + case 1: + *((uint8_t *)ptr) = (uint8_t)prng32(state); + break; + case 0: + break; + } +} + +static __thread uint64_t prng_state; + +void prng_seed(uint64_t seed) { prng_state = bleach64(seed); } + +uint32_t prng32(void) { return prng32(prng_state); } + +uint64_t prng64(void) { return prng64_white(prng_state); } + +void prng_fill(void *ptr, size_t bytes) { prng_fill(prng_state, ptr, bytes); } + +uint64_t entropy_white() { return bleach64(entropy_ticks()); } + +double double_from_lower(uint64_t salt) { +#ifdef IEEE754_DOUBLE_BIAS + ieee754_double r; + r.ieee.negative = 0; + r.ieee.exponent = IEEE754_DOUBLE_BIAS; + r.ieee.mantissa0 = (unsigned)(salt >> 32); + r.ieee.mantissa1 = (unsigned)salt; + return r.d; +#else + const uint64_t top = (UINT64_C(1) << DBL_MANT_DIG) - 1; + const double scale = 1.0 / (double)top; + return (salt & top) * scale; +#endif +} + +double double_from_upper(uint64_t salt) { +#ifdef IEEE754_DOUBLE_BIAS + ieee754_double r; + r.ieee.negative = 0; + r.ieee.exponent = IEEE754_DOUBLE_BIAS; + salt >>= 64 - DBL_MANT_DIG; + r.ieee.mantissa0 = (unsigned)(salt >> 32); + r.ieee.mantissa1 = (unsigned)salt; + return r.d; +#else + const uint64_t top = (UINT64_C(1) << DBL_MANT_DIG) - 1; + const double scale = 1.0 / (double)top; + return (salt >> (64 - DBL_MANT_DIG)) * scale; +#endif +} + +bool flipcoin() { return bleach32((uint32_t)entropy_ticks()) & 1; } +bool flipcoin_x2() { return (bleach32((uint32_t)entropy_ticks()) & 3) == 0; } +bool flipcoin_x3() { return (bleach32((uint32_t)entropy_ticks()) & 7) == 0; } +bool flipcoin_x4() { return (bleach32((uint32_t)entropy_ticks()) & 15) == 0; } + +bool jitter(unsigned probability_percent) { + const uint32_t top = UINT32_MAX - UINT32_MAX % 100; + uint32_t dice, edge = (top) / 100 * probability_percent; + do + dice = bleach32((uint32_t)entropy_ticks()); + while (dice >= top); + return dice < edge; +} + +void jitter_delay(bool extra) { + unsigned dice = entropy_white() & 3; + if (dice == 0) { + log_trace("== jitter.no-delay"); + } else { + log_trace(">> jitter.delay: dice %u", dice); + do { + cpu_relax(); + memory_barrier(); + cpu_relax(); + if (dice > 1) { + osal_yield(); + cpu_relax(); + if (dice > 2) { + unsigned us = entropy_white() & + (extra ? 0xffff /* 656 ms */ : 0x3ff /* 1 ms */); + log_trace("== jitter.delay: %0.6f", us / 1000000.0); + osal_udelay(us); + } + } + } while (flipcoin()); + log_trace("<< jitter.delay: dice %u", dice); + } +} diff --git a/contrib/db/libmdbx/test/utils.h b/contrib/db/libmdbx/test/utils.h new file mode 100644 index 00000000..2a5a54de --- /dev/null +++ b/contrib/db/libmdbx/test/utils.h @@ -0,0 +1,362 @@ +/* + * Copyright 2017-2019 Leonid Yuriev + * and other libmdbx authors: please see AUTHORS file. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted only as authorized by the OpenLDAP + * Public License. + * + * A copy of this license is available in the file LICENSE in the + * top-level directory of the distribution or, alternatively, at + * . + */ + +#pragma once +#include "base.h" + +#if !defined(__BYTE_ORDER__) || !defined(__ORDER_LITTLE_ENDIAN__) || \ + !defined(__ORDER_BIG_ENDIAN__) +#error __BYTE_ORDER__ should be defined. +#endif + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ && \ + __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ +#error Unsupported byte order. +#endif + +#if __GNUC_PREREQ(4, 4) || defined(__clang__) +#ifndef bswap64 +#define bswap64(v) __builtin_bswap64(v) +#endif +#ifndef bswap32 +#define bswap32(v) __builtin_bswap32(v) +#endif +#if (__GNUC_PREREQ(4, 8) || __has_builtin(__builtin_bswap16)) && \ + !defined(bswap16) +#define bswap16(v) __builtin_bswap16(v) +#endif + +#elif defined(_MSC_VER) + +#if _MSC_FULL_VER < 190024215 +#pragma message( \ + "It is recommended to use Visual Studio 2015 (MSC 19.0) or newer.") +#endif + +#define bswap64(v) _byteswap_uint64(v) +#define bswap32(v) _byteswap_ulong(v) +#define bswap16(v) _byteswap_ushort(v) +#define rot64(v, s) _rotr64(v, s) +#define rot32(v, s) _rotr(v, s) + +#if defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64) +#pragma intrinsic(_umul128) +#define mul_64x64_128(a, b, ph) _umul128(a, b, ph) +#pragma intrinsic(__umulh) +#define mul_64x64_high(a, b) __umulh(a, b) +#endif + +#if defined(_M_IX86) +#pragma intrinsic(__emulu) +#define mul_32x32_64(a, b) __emulu(a, b) +#elif defined(_M_ARM) +#define mul_32x32_64(a, b) _arm_umull(a, b) +#endif + +#endif /* compiler */ + +#ifndef bswap64 +#ifdef __bswap_64 +#define bswap64(v) __bswap_64(v) +#else +static __inline uint64_t bswap64(uint64_t v) { + return v << 56 | v >> 56 | ((v << 40) & UINT64_C(0x00ff000000000000)) | + ((v << 24) & UINT64_C(0x0000ff0000000000)) | + ((v << 8) & UINT64_C(0x000000ff00000000)) | + ((v >> 8) & UINT64_C(0x00000000ff0000000)) | + ((v >> 24) & UINT64_C(0x0000000000ff0000)) | + ((v >> 40) & UINT64_C(0x000000000000ff00)); +} +#endif +#endif /* bswap64 */ + +#ifndef bswap32 +#ifdef __bswap_32 +#define bswap32(v) __bswap_32(v) +#else +static __inline uint32_t bswap32(uint32_t v) { + return v << 24 | v >> 24 | ((v << 8) & UINT32_C(0x00ff0000)) | + ((v >> 8) & UINT32_C(0x0000ff00)); +} +#endif +#endif /* bswap32 */ + +#ifndef bswap16 +#ifdef __bswap_16 +#define bswap16(v) __bswap_16(v) +#else +static __inline uint16_t bswap16(uint16_t v) { return v << 8 | v >> 8; } +#endif +#endif /* bswap16 */ + +#define is_byteorder_le() (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) +#define is_byteorder_be() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) + +#ifndef htole16 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe16(v) bswap16(v) +#define htole16(v) (v) +#define be16toh(v) bswap16(v) +#define le16toh(v) (v) +#else +#define htobe16(v) (v) +#define htole16(v) bswap16(v) +#define be16toh(v) (v) +#define le16toh(v) bswap16(v) +#endif +#endif /* htole16 */ + +#ifndef htole32 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe32(v) bswap32(v) +#define htole32(v) (v) +#define be32toh(v) bswap32(v) +#define le32toh(v) (v) +#else +#define htobe32(v) (v) +#define htole32(v) bswap32(v) +#define be32toh(v) (v) +#define le32toh(v) bswap32(v) +#endif +#endif /* htole32 */ + +#ifndef htole64 +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#define htobe64(v) bswap64(v) +#define htole64(v) (v) +#define be64toh(v) bswap64(v) +#define le64toh(v) (v) +#else +#define htobe64(v) (v) +#define htole64(v) bswap_64(v) +#define be64toh(v) (v) +#define le64toh(v) bswap_64(v) +#endif +#endif /* htole64 */ + +namespace unaligned { + +template static __inline T load(const void *ptr) { +#if defined(_MSC_VER) && \ + (defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)) + return *(const T __unaligned *)ptr; +#elif MDBX_UNALIGNED_OK + return *(const T *)ptr; +#else + T local; +#if defined(__GNUC__) || defined(__clang__) + __builtin_memcpy(&local, (const T *)ptr, sizeof(T)); +#else + memcpy(&local, (const T *)ptr, sizeof(T)); +#endif /* __GNUC__ || __clang__ */ + return local; +#endif /* MDBX_UNALIGNED_OK */ +} + +template static __inline void store(void *ptr, const T &value) { +#if defined(_MSC_VER) && \ + (defined(_M_ARM64) || defined(_M_X64) || defined(_M_IA64)) + *((T __unaligned *)ptr) = value; +#elif MDBX_UNALIGNED_OK + *(volatile T *)ptr = value; +#else +#if defined(__GNUC__) || defined(__clang__) + __builtin_memcpy(ptr, &value, sizeof(T)); +#else + memcpy(ptr, &value, sizeof(T)); +#endif /* __GNUC__ || __clang__ */ +#endif /* MDBX_UNALIGNED_OK */ +} + +} /* namespace unaligned */ + +//----------------------------------------------------------------------------- + +#ifndef rot64 +static __inline uint64_t rot64(uint64_t v, unsigned s) { + return (v >> s) | (v << (64 - s)); +} +#endif /* rot64 */ + +static __inline bool is_power2(size_t x) { return (x & (x - 1)) == 0; } + +#undef roundup2 +static __inline size_t roundup2(size_t value, size_t granularity) { + assert(is_power2(granularity)); + return (value + granularity - 1) & ~(granularity - 1); +} + +//----------------------------------------------------------------------------- + +static __inline void memory_barrier(void) { +#if __has_extension(c_atomic) || __has_extension(cxx_atomic) + __c11_atomic_thread_fence(__ATOMIC_SEQ_CST); +#elif defined(__ATOMIC_SEQ_CST) + __atomic_thread_fence(__ATOMIC_SEQ_CST); +#elif defined(__clang__) || defined(__GNUC__) + __sync_synchronize(); +#elif defined(_MSC_VER) + MemoryBarrier(); +#elif defined(__INTEL_COMPILER) /* LY: Intel Compiler may mimic GCC and MSC */ +#if defined(__ia64__) || defined(__ia64) || defined(_M_IA64) + __mf(); +#elif defined(__ia32__) + _mm_mfence(); +#else +#error "Unknown target for Intel Compiler, please report to us." +#endif +#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun) + __machine_rw_barrier(); +#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) && \ + (defined(HP_IA64) || defined(__ia64)) + _Asm_mf(); +#elif defined(_AIX) || defined(__ppc__) || defined(__powerpc__) || \ + defined(__ppc64__) || defined(__powerpc64__) + __lwsync(); +#else +#error "Could not guess the kind of compiler, please report to us." +#endif +} + +static __inline void cpu_relax() { +#if defined(__ia32__) + _mm_pause(); +#elif defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) || \ + defined(YieldProcessor) + YieldProcessor(); +#else +/* nope */ +#endif +} + +//----------------------------------------------------------------------------- + +struct simple_checksum { + uint64_t value; + + simple_checksum() : value(0) {} + + void push(const uint32_t &data) { + value += data * UINT64_C(9386433910765580089) + 1; + value ^= value >> 41; + value *= UINT64_C(0xBD9CACC22C6E9571); + } + + void push(const uint64_t &data) { + push((uint32_t)data); + push((uint32_t)(data >> 32)); + } + + void push(const bool data) { + push(data ? UINT32_C(0x780E) : UINT32_C(0xFA18E)); + } + + void push(const void *ptr, size_t bytes) { + const uint8_t *data = (const uint8_t *)ptr; + for (size_t i = 0; i < bytes; ++i) + push((uint32_t)data[i]); + } + + void push(const double &data) { push(&data, sizeof(double)); } + void push(const char *cstr) { push(cstr, strlen(cstr)); } + void push(const std::string &str) { push(str.data(), str.size()); } + + void push(unsigned salt, const MDBX_val &val) { + push(unsigned(val.iov_len)); + push(salt); + push(val.iov_base, val.iov_len); + } + +#if defined(_WIN32) || defined(_WIN64) || defined(_WINDOWS) + void push(const HANDLE &handle) { push(&handle, sizeof(handle)); } +#endif /* _WINDOWS */ +}; + +std::string data2hex(const void *ptr, size_t bytes, simple_checksum &checksum); +bool hex2data(const char *hex_begin, const char *hex_end, void *ptr, + size_t bytes, simple_checksum &checksum); +bool is_samedata(const MDBX_val *a, const MDBX_val *b); +std::string format(const char *fmt, ...); + +uint64_t entropy_ticks(void); +uint64_t entropy_white(void); +static inline uint64_t bleach64(uint64_t v) { + // Tommy Ettinger, https://www.blogger.com/profile/04953541827437796598 + // http://mostlymangling.blogspot.com/2019/01/better-stronger-mixer-and-test-procedure.html + v ^= rot64(v, 25) ^ rot64(v, 50); + v *= UINT64_C(0xA24BAED4963EE407); + v ^= rot64(v, 24) ^ rot64(v, 49); + v *= UINT64_C(0x9FB21C651E98DF25); + return v ^ v >> 28; +} + +static inline uint32_t bleach32(uint32_t x) { + // https://github.com/skeeto/hash-prospector + // exact bias: 0.17353355999581582 + x ^= x >> 16; + x *= UINT32_C(0x7feb352d); + x ^= 0x3027C563 ^ (x >> 15); + x *= UINT32_C(0x846ca68b); + x ^= x >> 16; + return x; +} + +static inline uint64_t prng64_map1_careless(uint64_t state) { + return state * UINT64_C(6364136223846793005) + 1; +} + +static inline uint64_t prng64_map2_careless(uint64_t state) { + return (state + UINT64_C(1442695040888963407)) * + UINT64_C(6364136223846793005); +} + +static inline uint64_t prng64_map1_white(uint64_t state) { + return bleach64(prng64_map1_careless(state)); +} + +static inline uint64_t prng64_map2_white(uint64_t state) { + return bleach64(prng64_map2_careless(state)); +} + +static inline uint64_t prng64_careless(uint64_t &state) { + state = prng64_map1_careless(state); + return state; +} + +static inline double u64_to_double1(uint64_t v) { + union { + uint64_t u64; + double d; + } casting; + + casting.u64 = UINT64_C(0x3ff) << 52 | (v >> 12); + assert(casting.d >= 1.0 && casting.d < 2.0); + return casting.d - 1.0; +} + +uint64_t prng64_white(uint64_t &state); +uint32_t prng32(uint64_t &state); +void prng_fill(uint64_t &state, void *ptr, size_t bytes); + +void prng_seed(uint64_t seed); +uint32_t prng32(void); +uint64_t prng64(void); +void prng_fill(void *ptr, size_t bytes); + +bool flipcoin(); +bool flipcoin_x2(); +bool flipcoin_x3(); +bool flipcoin_x4(); +bool jitter(unsigned probability_percent); +void jitter_delay(bool extra = false); diff --git a/contrib/db/libmdbx/test/valgrind_suppress.txt b/contrib/db/libmdbx/test/valgrind_suppress.txt new file mode 100644 index 00000000..98309ceb --- /dev/null +++ b/contrib/db/libmdbx/test/valgrind_suppress.txt @@ -0,0 +1,24 @@ +{ + msync-whole-mmap-1 + Memcheck:Param + msync(start) + fun:msync + ... + fun:mdbx_sync_locked +} +{ + msync-whole-mmap-2 + Memcheck:Param + msync(start) + fun:msync + ... + fun:mdbx_env_sync_ex +} +{ + pwrite-page-flush + Memcheck:Param + pwritev(vector[...]) + fun:pwritev + ... + fun:mdbx_page_flush +} From 62cd5f25a4b08dd60b90bbfa2ac3e3ff9737a7b1 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 25 Apr 2022 15:14:35 +0200 Subject: [PATCH 0177/1271] fixed mdbx issue(related to detach from git) --- contrib/db/libmdbx/src/elements/version.c.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/db/libmdbx/src/elements/version.c.in b/contrib/db/libmdbx/src/elements/version.c.in index 2854bd5d..356b1857 100644 --- a/contrib/db/libmdbx/src/elements/version.c.in +++ b/contrib/db/libmdbx/src/elements/version.c.in @@ -3,10 +3,10 @@ #include "internals.h" -#if MDBX_VERSION_MAJOR != ${MDBX_VERSION_MAJOR} || \ - MDBX_VERSION_MINOR != ${MDBX_VERSION_MINOR} -#error "API version mismatch! Had `git fetch --tags` done?" -#endif +//#if MDBX_VERSION_MAJOR != ${MDBX_VERSION_MAJOR} || \ +// MDBX_VERSION_MINOR != ${MDBX_VERSION_MINOR} +//#error "API version mismatch! Had `git fetch --tags` done?" +//#endif static const char sourcery[] = STRINGIFY(MDBX_BUILD_SOURCERY); From 82c4a47a2198d67d5417398983c3c117a1af3b77 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 25 Apr 2022 16:37:44 +0200 Subject: [PATCH 0178/1271] functional tests: crypto test runner minor fix --- tests/functional_tests/crypto_tests.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index e4e0c63e..e52b7033 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -15,6 +15,7 @@ #include "crypto/crypto-sugar.h" #include "crypto/range_proofs.h" +#include "../core_tests/random_helper.h" using namespace crypto; @@ -1908,7 +1909,7 @@ int crypto_tests(const std::string& cmd_line_param) epee::log_space::log_singletone::get_default_log_folder().c_str()); - + size_t filtered_tests_count = 0; std::vector failed_tests; for (size_t i = 0; i < g_tests.size(); ++i) { @@ -1917,6 +1918,8 @@ int crypto_tests(const std::string& cmd_line_param) if (!wildcard_match(cmd_line_param.c_str(), test.first.c_str())) continue; + ++filtered_tests_count; + LOG_PRINT(" " << std::setw(40) << std::left << test.first << " >", LOG_LEVEL_0); TIME_MEASURE_START(runtime); bool r = false; @@ -1946,10 +1949,16 @@ int crypto_tests(const std::string& cmd_line_param) } } + if (filtered_tests_count == 0) + { + LOG_PRINT_YELLOW(ENDL << ENDL << "No tests were selected, check the filter mask", LOG_LEVEL_0); + return 1; + } + if (failed_tests.empty()) { LOG_PRINT_GREEN(ENDL, LOG_LEVEL_0); - LOG_PRINT_GREEN("All tests passed okay", LOG_LEVEL_0); + LOG_PRINT_GREEN(filtered_tests_count << " tests passed okay", LOG_LEVEL_0); return 0; } From 8a69f4969e070381e3e5b3335e7852327fe124c7 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 25 Apr 2022 16:39:00 +0200 Subject: [PATCH 0179/1271] readme: added link to the OpenSSL win binaries downloading --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9d46345..c6ad2f56 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Be sure to clone the repository properly:\ | [XCode](https://developer.apple.com/downloads/) (macOS) | 9.2 | 12.3 | 12.3 | | [CMake](https://cmake.org/download/) | 2.8.6 | 3.15.5 | 3.20 | | [Boost](https://www.boost.org/users/download/) | 1.70 | 1.70 | 1.76 | -| [OpenSSL](https://www.openssl.org/source/) | - | 1.1.1n | 1.1.1n | +| [OpenSSL](https://www.openssl.org/source/) [(win)](https://slproweb.com/products/Win32OpenSSL.html) | - | 1.1.1n | 1.1.1n | | [Qt](https://download.qt.io/archive/qt/) (*only for GUI*) | 5.8.0 | 5.11.2 | 5.15.2 | Note:\ From ae48cf0c7aa786d211923e9bf75d88a1ea190764 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 25 Apr 2022 20:03:07 +0200 Subject: [PATCH 0180/1271] moved ui to latest commit --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 709a1e6a..5e73fb7c 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 709a1e6a3eb5ae7af48bbadf6b4c512b637b75b3 +Subproject commit 5e73fb7cc8903a24ac5e9662f89f2c95c53ba357 From f6f18491020a16afae942f9f4f2777ac026e7fcb Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Tue, 26 Apr 2022 16:43:50 +0200 Subject: [PATCH 0181/1271] moved ui to latest commits --- src/gui/qt-daemon/layout | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 5e73fb7c..23f9dac8 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 5e73fb7cc8903a24ac5e9662f89f2c95c53ba357 +Subproject commit 23f9dac883355d97f546acfe7607c229600d5c7a From a51057753cdb40953cc6e8159b2120395bc1d927 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Wed, 27 Apr 2022 16:40:32 +0200 Subject: [PATCH 0182/1271] pulled latest commits from UI --- src/gui/qt-daemon/layout | 2 +- utils/test_api_files/get_alias_details.json | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 utils/test_api_files/get_alias_details.json diff --git a/src/gui/qt-daemon/layout b/src/gui/qt-daemon/layout index 23f9dac8..b091d45a 160000 --- a/src/gui/qt-daemon/layout +++ b/src/gui/qt-daemon/layout @@ -1 +1 @@ -Subproject commit 23f9dac883355d97f546acfe7607c229600d5c7a +Subproject commit b091d45ad697db2d35e94de41be3f175bad0f71d diff --git a/utils/test_api_files/get_alias_details.json b/utils/test_api_files/get_alias_details.json new file mode 100644 index 00000000..c89f7e97 --- /dev/null +++ b/utils/test_api_files/get_alias_details.json @@ -0,0 +1 @@ +{"method": "get_alias_details","params": {"alias": "zoidb"}} \ No newline at end of file From 2b1d890b746f21c07c7cc2ebcd8d3ef28ddbbae1 Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 1 May 2022 21:03:27 +0200 Subject: [PATCH 0183/1271] wallet & coretests: minor error-logging improvements --- src/wallet/wallet_errors.h | 5 ++++- tests/core_tests/chaingen_main.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h index f836c8a8..46c93f30 100644 --- a/src/wallet/wallet_errors.h +++ b/src/wallet/wallet_errors.h @@ -70,7 +70,10 @@ namespace tools std::string to_string() const { std::ostringstream ss; - ss << m_loc << ':' << typeid(*this).name() << "[" << m_error_code << "]: " << Base::what(); + ss << m_loc << ':' << typeid(*this).name(); + if (!m_error_code.empty()) + ss << "[" << m_error_code << "]"; + ss << ": " << Base::what(); return ss.str(); } diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index f19180f1..d09d1558 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -106,7 +106,7 @@ bool generate_and_play(const char* const genclass_name) std::vector events; bool generated = false; bool result = true; - std::cout << concolor::bright_white << "#TEST# " << genclass_name << concolor::normal << std::endl; + std::cout << ENDL << concolor::bright_white << "#TEST# " << genclass_name << concolor::normal << ENDL << ENDL; LOG_PRINT2("get_object_blobsize.log", "#TEST# " << genclass_name, LOG_LEVEL_3); if (!clean_data_directory()) From bbdd6595bfaa8af8b269fc764e50c47d9641edbe Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 1 May 2022 21:07:17 +0200 Subject: [PATCH 0184/1271] wallet: minor htlc error-handling improvement --- src/wallet/wallet2.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 92bdd848..26778182 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -404,8 +404,9 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t if (it != m_active_htlcs.end()) { transfer_details& td = m_transfers[it->second]; - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong index in m_transfers"); - WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target.type() == typeid(txout_htlc), "Internal error: wrong index in m_transfers"); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(td.m_ptx_wallet_info->m_tx.vout.size() > td.m_internal_output_index, "Internal error: wrong td.m_internal_output_index: " << td.m_internal_output_index); + const boost::typeindex::type_info& ti = td.m_ptx_wallet_info->m_tx.vout[td.m_internal_output_index].target.type(); + WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(ti == typeid(txout_htlc), "Internal error: wrong type of output's target: " << ti.name()); //input spend active htlc m_transfers[it->second].m_spent_height = height; transfer_details_extra_option_htlc_info& tdeohi = get_or_add_field_to_variant_vector(td.varian_options); From e569dd80238d5a767b71e69d9801d80ddaf26e3e Mon Sep 17 00:00:00 2001 From: sowle Date: Sat, 7 May 2022 22:59:57 +0200 Subject: [PATCH 0185/1271] predownload bumped up to 1569k --- src/common/pre_download.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/pre_download.h b/src/common/pre_download.h index b6d4a74d..3a3e8666 100644 --- a/src/common/pre_download.h +++ b/src/common/pre_download.h @@ -21,8 +21,8 @@ namespace tools }; #ifndef TESTNET - static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_1480000.pak", "2b664de02450cc0082efb6c75824d33ffe694b9b17b21fc7966bcb9be9ac31f7", 1570460883, 3221176320 }; - static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_1480000.pak", "67770faa7db22dfe97982611d7471ba5673145589e81e02ed99832644ae328f6", 2042582638, 2968252416 }; + static constexpr pre_download_entry c_pre_download_mdbx = { "http://95.217.42.247/pre-download/zano_mdbx_95_1569000.pak", "c2aff6fb65c2d7a40492738c75d1d04f2f35388d3c1a664aeb77420f2d90d644", 1740147926, 3221176320 }; + static constexpr pre_download_entry c_pre_download_lmdb = { "http://95.217.42.247/pre-download/zano_lmdb_95_1569000.pak", "f673636638b666b36c976168a3c64b2fd1fcf071c41b5d9cf01ed58a5924f80a", 2244905636, 3216977920 }; #else static constexpr pre_download_entry c_pre_download_mdbx = { "", "", 0, 0 }; static constexpr pre_download_entry c_pre_download_lmdb = { "", "", 0, 0 }; From 1235ab6434d2a3b166e5bf8657b946c8c9923ddd Mon Sep 17 00:00:00 2001 From: sowle Date: Sun, 8 May 2022 03:45:43 +0200 Subject: [PATCH 0186/1271] version bump: 1.4.1.142 -> 1.5.0.143 --- src/version.h.in | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/version.h.in b/src/version.h.in index a9c7f0d9..a6d24549 100644 --- a/src/version.h.in +++ b/src/version.h.in @@ -4,10 +4,10 @@ #define BUILD_COMMIT_ID "@VERSION@" #define PROJECT_MAJOR_VERSION "1" -#define PROJECT_MINOR_VERSION "4" -#define PROJECT_REVISION "1" +#define PROJECT_MINOR_VERSION "5" +#define PROJECT_REVISION "0" #define PROJECT_VERSION PROJECT_MAJOR_VERSION "." PROJECT_MINOR_VERSION "." PROJECT_REVISION -#define PROJECT_VERSION_BUILD_NO 142 +#define PROJECT_VERSION_BUILD_NO 143 #define PROJECT_VERSION_BUILD_NO_STR STRINGIFY_EXPAND(PROJECT_VERSION_BUILD_NO) #define PROJECT_VERSION_LONG PROJECT_VERSION "." PROJECT_VERSION_BUILD_NO_STR "[" BUILD_COMMIT_ID "]" From 7e2d0fcb6bb98593ae16b53a46fb7880a598a8ab Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 9 May 2022 01:44:18 +0200 Subject: [PATCH 0187/1271] Dockerfile updated, as for official image at sowle/zano-full-node --- utils/docker/Dockerfile | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/utils/docker/Dockerfile b/utils/docker/Dockerfile index 08f2ffe1..2fe77a64 100644 --- a/utils/docker/Dockerfile +++ b/utils/docker/Dockerfile @@ -44,18 +44,37 @@ RUN curl https://github.com/Kitware/CMake/releases/download/v3.15.5/cmake-3.15.5 cmake --version &&\ rm cmake-3.15.5-Linux-x86_64.sh -# Boost 1.68 -RUN curl https://boostorg.jfrog.io/artifactory/main/release/1.68.0/source/boost_1_68_0.tar.bz2 -OL &&\ - echo '7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7 boost_1_68_0.tar.bz2' | sha256sum -c - &&\ - tar -xjf boost_1_68_0.tar.bz2 &&\ - rm boost_1_68_0.tar.bz2 &&\ - cd boost_1_68_0 &&\ - ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer &&\ +# Boost 1.70 + +RUN curl https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.bz2 -OL &&\ + echo '430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778 boost_1_70_0.tar.bz2' | sha256sum -c - &&\ + tar -xjf boost_1_70_0.tar.bz2 &&\ + rm boost_1_70_0.tar.bz2 &&\ + cd boost_1_70_0 &&\ + ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer,log &&\ ./b2 &&\ cd .. -ENV BOOST_ROOT=/root/boost_1_68_0 +ENV BOOST_ROOT=/root/boost_1_70_0 + + +# OpenSSL 1.1.1n + +RUN curl https://www.openssl.org/source/openssl-1.1.1n.tar.gz -OL &&\ + echo '40dceb51a4f6a5275bde0e6bf20ef4b91bfc32ed57c0552e2e8e15463372b17a openssl-1.1.1n.tar.gz' | sha256sum -c - &&\ + tar xaf openssl-1.1.1n.tar.gz &&\ + rm openssl-1.1.1n.tar.gz &&\ + cd openssl-1.1.1n &&\ + ./config --prefix=/root/openssl --openssldir=/root/openssl shared zlib &&\ + make &&\ + make test &&\ + make install &&\ + cd .. &&\ + rm -rf openssl-1.1.1n + +ENV OPENSSL_ROOT_DIR=/root/openssl + # Zano @@ -63,9 +82,8 @@ RUN pwd && mem_avail_gb=$(( $(getconf _AVPHYS_PAGES) * $(getconf PAGE_SIZE) / (1 make_job_slots=$(( $mem_avail_gb < 4 ? 1 : $mem_avail_gb / 4)) &&\ echo make_job_slots=$make_job_slots &&\ set -x &&\ - git clone --single-branch https://github.com/hyle-team/zano.git &&\ + git clone --single-branch --recursive https://github.com/hyle-team/zano.git &&\ cd zano &&\ - git submodule update --init --recursive &&\ mkdir build && cd build &&\ cmake -D STATIC=TRUE .. &&\ make -j $make_job_slots daemon simplewallet From 116011fdcf584115e3198ca36e6b6015e9183d93 Mon Sep 17 00:00:00 2001 From: sowle Date: Mon, 9 May 2022 17:29:25 +0200 Subject: [PATCH 0188/1271] tx_out_zarcanum --- src/currency_core/currency_basic.h | 27 +++++++++++++++++++ .../currency_boost_serialization.h | 9 ++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index dc973a66..99cd0f96 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -294,6 +294,31 @@ namespace currency }; +#pragma pack(push, 1) + struct tx_out_zarcanum + { + tx_out_zarcanum() {} + + // Boost's Assignable concept + tx_out_zarcanum(const tx_out_zarcanum&) = default; + tx_out_zarcanum& operator=(const tx_out_zarcanum&) = default; + + crypto::public_key stealth_address; + crypto::public_key concealing_point; + crypto::public_key commitment; + uint64_t encrypted_amount; + + BEGIN_SERIALIZE_OBJECT() + FIELD(stealth_address) + FIELD(concealing_point) + FIELD(commitment) + FIELD(encrypted_amount) + END_SERIALIZE() + }; +#pragma pack(pop) + + typedef boost::variant tx_out_v; + struct tx_comment { @@ -814,6 +839,8 @@ SET_VARIANT_TAGS(currency::extra_alias_entry, 33, "alias_entry2"); SET_VARIANT_TAGS(currency::txin_htlc, 34, "txin_htlc"); SET_VARIANT_TAGS(currency::txout_htlc, 35, "txout_htlc"); +// Zarcanum +SET_VARIANT_TAGS(currency::tx_out_zarcanum, 36, "tx_out_zarcanum"); #undef SET_VARIANT_TAGS diff --git a/src/currency_core/currency_boost_serialization.h b/src/currency_core/currency_boost_serialization.h index 96f95bdc..9b241bec 100644 --- a/src/currency_core/currency_boost_serialization.h +++ b/src/currency_core/currency_boost_serialization.h @@ -110,7 +110,14 @@ namespace boost a & x.target; } - + template + inline void serialize(Archive &a, currency::tx_out_zarcanum &x, const boost::serialization::version_type ver) + { + a & x.stealth_address; + a & x.concealing_point; + a & x.commitment; + a & x.encrypted_amount; + } template inline void serialize(Archive &a, currency::tx_comment &x, const boost::serialization::version_type ver) From c53da67e1a0c8ed5e113b1708dcf5132cbe06323 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 9 May 2022 20:28:13 +0200 Subject: [PATCH 0189/1271] added BEGIN_BOOST_SERIALIZATION macros --- src/common/boost_serialization_helper.h | 1 + src/common/boost_serialization_maps.h | 32 +++++++++++++++++++++++ src/currency_core/currency_basic.h | 2 +- src/currency_core/currency_config.h | 1 + src/currency_core/currency_format_utils.h | 2 ++ src/wallet/wallet2.h | 1 + 6 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/common/boost_serialization_maps.h diff --git a/src/common/boost_serialization_helper.h b/src/common/boost_serialization_helper.h index 5ad68a05..1f2297a4 100644 --- a/src/common/boost_serialization_helper.h +++ b/src/common/boost_serialization_helper.h @@ -16,6 +16,7 @@ #define CHECK_PROJECT_NAME() std::string project_name = CURRENCY_NAME; ar & project_name; if(!(project_name == CURRENCY_NAME) ) {throw std::runtime_error(std::string("wrong storage file: project name in file: ") + project_name + ", expected: " + CURRENCY_NAME );} + namespace tools { template diff --git a/src/common/boost_serialization_maps.h b/src/common/boost_serialization_maps.h new file mode 100644 index 00000000..bd8436d9 --- /dev/null +++ b/src/common/boost_serialization_maps.h @@ -0,0 +1,32 @@ +// Copyright (c) 2014-2022 Zano Project +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#define BEGIN_BOOST_SERIALIZATION() template inline void serialize(t_archive &_arch, const unsigned int ver) { + +#define BOOST_SERIALIZE(x) _arch & x; + +#define END_BOOST_SERIALIZATION() } + + +/* + example of use: + + struct tx_extra_info + { + crypto::public_key m_tx_pub_key; + extra_alias_entry m_alias; + std::string m_user_data_blob; + extra_attachment_info m_attachment_info; + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(m_tx_pub_key) + BOOST_SERIALIZE(m_alias) + if(ver < xxx) return; + BOOST_SERIALIZE(m_user_data_blob) + BOOST_SERIALIZE(m_attachment_info) + END_BOOST_SERIALIZATION() + }; +*/ diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 99cd0f96..776074e4 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -602,7 +602,7 @@ namespace currency //extra std::vector extra; std::vector vin; - std::vector vout; + std::vector vout_;//std::vector vout; BEGIN_SERIALIZE() VARINT_FIELD(version) diff --git a/src/currency_core/currency_config.h b/src/currency_core/currency_config.h index 18e38c67..5cd97470 100644 --- a/src/currency_core/currency_config.h +++ b/src/currency_core/currency_config.h @@ -27,6 +27,7 @@ #define CURRENCY_PUBLIC_AUDITABLE_INTEG_ADDRESS_BASE58_PREFIX 0x8a49 // auditable integrated addresses start with 'aiZX' #define CURRENCY_MINED_MONEY_UNLOCK_WINDOW 10 #define CURRENT_TRANSACTION_VERSION 1 +#define CURRENT_TRANSACTION_VERSION_HF4 2 #define HF1_BLOCK_MAJOR_VERSION 1 #define CURRENT_BLOCK_MAJOR_VERSION 2 diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 725b39e1..8c67c8c6 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -156,6 +156,7 @@ namespace currency std::vector prepared_destinations; uint64_t expiration_time; crypto::public_key spend_pub_key; // only for validations + uint64_t tx_version; BEGIN_SERIALIZE_OBJECT() FIELD(unlock_time) @@ -171,6 +172,7 @@ namespace currency FIELD(prepared_destinations) FIELD(expiration_time) FIELD(spend_pub_key) + FIELD(tx_version) END_SERIALIZE() }; diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index e57ff79a..5539a778 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -697,6 +697,7 @@ namespace tools uint64_t get_top_block_height() const { return m_chain.get_top_block_height(); } + template inline void serialize(t_archive &a, const unsigned int ver) { From 60c5cc24bce2b4b0ad7bcd61eda69bf0ff810609 Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 9 May 2022 20:32:36 +0200 Subject: [PATCH 0190/1271] undo global changes --- src/currency_core/currency_basic.h | 3 ++- src/currency_core/currency_format_utils.cpp | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 776074e4..9a7fff76 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -602,12 +602,13 @@ namespace currency //extra std::vector extra; std::vector vin; - std::vector vout_;//std::vector vout; + std::vector vout;//std::vector vout; BEGIN_SERIALIZE() VARINT_FIELD(version) if(CURRENT_TRANSACTION_VERSION < version) return false; FIELD(vin) + if(version <= 1) FIELD(vout) FIELD(extra) END_SERIALIZE() diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index 7019ce41..65b591be 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -1209,6 +1209,7 @@ namespace currency { //extra copy operation, but creating transaction is not sensitive to this finalize_tx_param ftp = AUTO_VAL_INIT(ftp); + ftp.tx_version = CURRENT_TRANSACTION_VERSION; ftp.sources = sources; ftp.prepared_destinations = destinations; ftp.extra = extra; @@ -1264,7 +1265,7 @@ namespace currency tx.signatures.clear(); tx.extra = extra; - tx.version = CURRENT_TRANSACTION_VERSION; + tx.version = ftp.tx_version; if (unlock_time != 0) set_tx_unlock_time(tx, unlock_time); From 2707d1515d13eb8d39e6f405959d982beb6ff6ca Mon Sep 17 00:00:00 2001 From: cryptozoidberg Date: Mon, 9 May 2022 20:33:59 +0200 Subject: [PATCH 0191/1271] undo global changes2 --- src/currency_core/currency_basic.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 9a7fff76..676d4f4d 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -608,7 +608,7 @@ namespace currency VARINT_FIELD(version) if(CURRENT_TRANSACTION_VERSION < version) return false; FIELD(vin) - if(version <= 1) + //if(version <= 1) FIELD(vout) FIELD(extra) END_SERIALIZE() From 4066de04a1c6356e9874434a95dba46290512ed2 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 10 May 2022 17:29:42 +0200 Subject: [PATCH 0192/1271] epee: clean up the mess with namespace misc_utils::parse + string tools --- .../storages/portable_storage_from_json.h | 21 +++++++++---------- contrib/epee/include/string_tools.h | 16 ++------------ 2 files changed, 12 insertions(+), 25 deletions(-) diff --git a/contrib/epee/include/storages/portable_storage_from_json.h b/contrib/epee/include/storages/portable_storage_from_json.h index 4e74fb7a..fa76cc9b 100644 --- a/contrib/epee/include/storages/portable_storage_from_json.h +++ b/contrib/epee/include/storages/portable_storage_from_json.h @@ -30,7 +30,6 @@ namespace epee { - using namespace misc_utils::parse; namespace serialization { namespace json @@ -86,7 +85,7 @@ namespace epee switch(*it) { case '"': - match_string2(it, buf_end, name); + misc_utils::parse::match_string2(it, buf_end, name); state = match_state_waiting_separator; break; case '}': @@ -107,7 +106,7 @@ namespace epee if(*it == '"') {//just a named string value started std::string val; - match_string2(it, buf_end, val); + misc_utils::parse::match_string2(it, buf_end, val); //insert text value stg.set_value(name, val, current_section); state = match_state_wonder_after_value; @@ -115,7 +114,7 @@ namespace epee {//just a named number value started std::string val; bool is_v_float = false;bool is_signed = false; - match_number2(it, buf_end, val, is_v_float, is_signed); + misc_utils::parse::match_number2(it, buf_end, val, is_v_float, is_signed); if(!is_v_float) { if(is_signed) @@ -136,7 +135,7 @@ namespace epee }else if(isalpha(*it) ) {// could be null, true or false std::string word; - match_word2(it, buf_end, word); + misc_utils::parse::match_word2(it, buf_end, word); if(boost::iequals(word, "null")) { state = match_state_wonder_after_value; @@ -191,7 +190,7 @@ namespace epee { //mean array of strings std::string val; - match_string2(it, buf_end, val); + misc_utils::parse::match_string2(it, buf_end, val); h_array = stg.insert_first_value(name, val, current_section); CHECK_AND_ASSERT_THROW_MES(h_array, " failed to insert values entry"); state = match_state_array_after_value; @@ -200,7 +199,7 @@ namespace epee {//array of numbers value started std::string val; bool is_v_float = false;bool is_signed_val = false; - match_number2(it, buf_end, val, is_v_float, is_signed_val); + misc_utils::parse::match_number2(it, buf_end, val, is_v_float, is_signed_val); if(!is_v_float) { int64_t nval = boost::lexical_cast(val);//bool res = string_tools::string_to_num_fast(val, nval); @@ -222,7 +221,7 @@ namespace epee }else if(isalpha(*it) ) {// array of booleans std::string word; - match_word2(it, buf_end, word); + misc_utils::parse::match_word2(it, buf_end, word); if(boost::iequals(word, "true")) { h_array = stg.insert_first_value(name, true, current_section); @@ -266,7 +265,7 @@ namespace epee if(*it == '"') { std::string val; - match_string2(it, buf_end, val); + misc_utils::parse::match_string2(it, buf_end, val); bool res = stg.insert_next_value(h_array, val); CHECK_AND_ASSERT_THROW_MES(res, "failed to insert values"); state = match_state_array_after_value; @@ -277,7 +276,7 @@ namespace epee {//array of numbers value started std::string val; bool is_v_float = false;bool is_signed_val = false; - match_number2(it, buf_end, val, is_v_float, is_signed_val); + misc_utils::parse::match_number2(it, buf_end, val, is_v_float, is_signed_val); bool insert_res = false; if(!is_v_float) { @@ -299,7 +298,7 @@ namespace epee if(isalpha(*it) ) {// array of booleans std::string word; - match_word2(it, buf_end, word); + misc_utils::parse::match_word2(it, buf_end, word); if(boost::iequals(word, "true")) { bool r = stg.insert_next_value(h_array, true); diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index 95a5d88e..f1af2c65 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -23,16 +23,11 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // - - - #ifndef _STRING_TOOLS_H_ #define _STRING_TOOLS_H_ -//#include #include #include -//#include #include #include #include @@ -322,13 +317,6 @@ POP_GCC_WARNINGS return true; } -/* template - bool get_xparam_from_command_line(const std::map& res, const std::basic_string & key, t_type& val) - { - - } - */ - template bool get_xparam_from_command_line(const std::map& res, const t_string & key, t_type& val) { @@ -803,6 +791,6 @@ POP_GCC_WARNINGS return buff; } #endif -} -} +} // namespace stringtools +} // namwspace epee #endif //_STRING_TOOLS_H_ From 50970e3d544172c0b434e03de0a2f32e27ddf2de Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 10 May 2022 18:48:18 +0200 Subject: [PATCH 0193/1271] removed that crazy annoying Boost warning (a bug in Boost 1.70, already fixed) "The use of BOOST_*_ENDIAN and BOOST_BYTE_ORDER is deprecated." --- contrib/eos_portable_archive/eos/portable_iarchive.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/contrib/eos_portable_archive/eos/portable_iarchive.hpp b/contrib/eos_portable_archive/eos/portable_iarchive.hpp index cd087f34..f46507f4 100644 --- a/contrib/eos_portable_archive/eos/portable_iarchive.hpp +++ b/contrib/eos_portable_archive/eos/portable_iarchive.hpp @@ -88,6 +88,13 @@ // basic headers #include + +// The following four lines disable annoying message in Boost 1.70 (The use of BOOST_*_ENDIAN and BOOST_BYTE_ORDER is deprecated.) +#if BOOST_VERSION == 107000 +# define BOOST_PREDEF_DETAIL_ENDIAN_COMPAT_H +# include +#endif + #include #include #include From b30bc2f781fc3c53400fb587b2b5606aaa465bd6 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 10 May 2022 19:59:58 +0200 Subject: [PATCH 0194/1271] eos portable archive fixed (endians defines) --- .../eos/portable_iarchive.hpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/contrib/eos_portable_archive/eos/portable_iarchive.hpp b/contrib/eos_portable_archive/eos/portable_iarchive.hpp index f46507f4..d5a39e08 100644 --- a/contrib/eos_portable_archive/eos/portable_iarchive.hpp +++ b/contrib/eos_portable_archive/eos/portable_iarchive.hpp @@ -89,10 +89,22 @@ // basic headers #include -// The following four lines disable annoying message in Boost 1.70 (The use of BOOST_*_ENDIAN and BOOST_BYTE_ORDER is deprecated.) -#if BOOST_VERSION == 107000 +// The following sixteen lines disable annoying message in Boost 1.70 (The use of BOOST_*_ENDIAN and BOOST_BYTE_ORDER is deprecated.) +#if BOOST_VERSION == 107000 && !defined(BOOST_PREDEF_DETAIL_ENDIAN_COMPAT_H) # define BOOST_PREDEF_DETAIL_ENDIAN_COMPAT_H # include +#if BOOST_ENDIAN_BIG_BYTE +# define BOOST_BIG_ENDIAN +# define BOOST_BYTE_ORDER 4321 +#endif +#if BOOST_ENDIAN_LITTLE_BYTE +# define BOOST_LITTLE_ENDIAN +# define BOOST_BYTE_ORDER 1234 +#endif +#if BOOST_ENDIAN_LITTLE_WORD +# define BOOST_PDP_ENDIAN +# define BOOST_BYTE_ORDER 2134 +#endif #endif #include From aa90e50f421aed49675bfaa82aa037c8bc4c2b6f Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 10 May 2022 20:42:54 +0200 Subject: [PATCH 0195/1271] fix for Argument-Dependent Lookup issue with parse_tpod_from_hex_string() --- contrib/epee/include/string_tools.h | 2 +- contrib/epee/include/syncobj.h | 2 +- src/crypto/crypto-sugar.h | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/epee/include/string_tools.h b/contrib/epee/include/string_tools.h index f1af2c65..0d50b4e1 100644 --- a/contrib/epee/include/string_tools.h +++ b/contrib/epee/include/string_tools.h @@ -207,7 +207,7 @@ namespace string_tools t_pod_type parse_tpod_from_hex_string(const std::string& str_hash) { t_pod_type t_pod = AUTO_VAL_INIT(t_pod); - parse_tpod_from_hex_string(str_hash, t_pod); + epee::string_tools::parse_tpod_from_hex_string(str_hash, t_pod); // using fully qualified name to avoid Argument-Dependent Lookup issues return t_pod; } //---------------------------------------------------------------------------- diff --git a/contrib/epee/include/syncobj.h b/contrib/epee/include/syncobj.h index 07c0a484..9971ef12 100644 --- a/contrib/epee/include/syncobj.h +++ b/contrib/epee/include/syncobj.h @@ -711,4 +711,4 @@ namespace epee #define EXCLUSIVE_CRITICAL_REGION_BEGIN(x) { EXCLUSIVE_CRITICAL_REGION_LOCAL(x) #define EXCLUSIVE_CRITICAL_REGION_END() } -} +} // namespace epee diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index a2681642..4363e5ae 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -1,5 +1,5 @@ -// Copyright (c) 2020-2021 Zano Project -// Copyright (c) 2020-2021 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2020-2022 Zano Project +// Copyright (c) 2020-2022 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // @@ -125,7 +125,7 @@ namespace crypto t_pod_type parse_tpod_from_hex_string(const std::string& hex_str) { t_pod_type t_pod = AUTO_VAL_INIT(t_pod); - parse_tpod_from_hex_string(hex_str, t_pod); + crypto::parse_tpod_from_hex_string(hex_str, t_pod); // using fully qualified name to avoid Argument-Dependent Lookup issues return t_pod; } From 10ed5dc433c7d627e727a27d59549ee72c9e6448 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 10 May 2022 21:05:15 +0200 Subject: [PATCH 0196/1271] getting rig of "using namespace epee" in headers --- src/currency_core/blockchain_storage.h | 6 +++--- src/currency_core/currency_core.h | 8 ++++---- src/currency_core/miner.h | 10 +++++----- src/currency_core/tx_pool.cpp | 2 ++ src/currency_core/tx_pool.h | 1 - src/stratum/stratum_server.cpp | 2 +- src/wallet/wallets_manager.h | 2 -- 7 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index cad2ca91..2ca28c2e 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -531,9 +531,9 @@ namespace currency - mutable critical_section m_invalid_blocks_lock; + mutable epee::critical_section m_invalid_blocks_lock; blocks_ext_by_hash m_invalid_blocks; // crypto::hash -> block_extended_info - mutable critical_section m_alternative_chains_lock; + mutable epee::critical_section m_alternative_chains_lock; alt_chain_container m_alternative_chains; // crypto::hash -> alt_block_extended_info std::unordered_map m_alternative_chains_txs; // tx_id -> how many alt blocks it related to (always >= 1) std::unordered_map> m_altblocks_keyimages; // key image -> list of alt blocks hashes where it appears in inputs @@ -557,7 +557,7 @@ namespace currency mutable wide_difficulty_type m_cached_next_pow_difficulty; mutable wide_difficulty_type m_cached_next_pos_difficulty; - mutable critical_section m_targetdata_cache_lock; + mutable epee::critical_section m_targetdata_cache_lock; mutable std::list > m_pos_targetdata_cache; mutable std::list > m_pow_targetdata_cache; //work like a cache to avoid recalculation on read operations diff --git a/src/currency_core/currency_core.h b/src/currency_core/currency_core.h index 354fd772..383ae47e 100644 --- a/src/currency_core/currency_core.h +++ b/src/currency_core/currency_core.h @@ -139,18 +139,18 @@ namespace currency tx_memory_pool m_mempool; i_currency_protocol* m_pprotocol; i_critical_error_handler* m_critical_error_handler; - critical_section m_incoming_tx_lock; + epee::critical_section m_incoming_tx_lock; miner m_miner; account_public_address m_miner_address; std::string m_config_folder; uint64_t m_stop_after_height; currency_protocol_stub m_protocol_stub; - math_helper::once_a_time_seconds<60*60*12, false> m_prune_alt_blocks_interval; - math_helper::once_a_time_seconds<60, true> m_check_free_space_interval; + epee::math_helper::once_a_time_seconds<60*60*12, false> m_prune_alt_blocks_interval; + epee::math_helper::once_a_time_seconds<60, true> m_check_free_space_interval; friend class tx_validate_inputs; std::atomic m_starter_message_showed; - critical_section m_blockchain_update_listeners_lock; + epee::critical_section m_blockchain_update_listeners_lock; std::vector m_blockchain_update_listeners; }; } diff --git a/src/currency_core/miner.h b/src/currency_core/miner.h index 69a4bdc7..ec011801 100644 --- a/src/currency_core/miner.h +++ b/src/currency_core/miner.h @@ -92,7 +92,7 @@ namespace currency volatile uint32_t m_stop; - ::critical_section m_template_lock; + epee::critical_section m_template_lock; block m_template; std::atomic m_template_no; std::atomic m_starter_nonce; @@ -101,15 +101,15 @@ namespace currency volatile uint32_t m_thread_index; volatile uint32_t m_threads_total; std::atomic m_pausers_count; - ::critical_section m_miners_count_lock; + epee::critical_section m_miners_count_lock; std::list m_threads; - ::critical_section m_threads_lock; + epee::critical_section m_threads_lock; i_miner_handler* m_phandler; //blockchain_storage& m_bc; account_public_address m_mine_address; - math_helper::once_a_time_seconds<5> m_update_block_template_interval; - math_helper::once_a_time_seconds<2> m_update_merge_hr_interval; + epee::math_helper::once_a_time_seconds<5> m_update_block_template_interval; + epee::math_helper::once_a_time_seconds<2> m_update_merge_hr_interval; std::vector m_extra_messages; miner_config m_config; std::string m_config_folder; diff --git a/src/currency_core/tx_pool.cpp b/src/currency_core/tx_pool.cpp index 581dfdb8..7f4d4495 100644 --- a/src/currency_core/tx_pool.cpp +++ b/src/currency_core/tx_pool.cpp @@ -40,6 +40,8 @@ DISABLE_VS_WARNINGS(4244 4345 4503) //'boost::foreach_detail_::or_' : decorated #define LOG_DEFAULT_CHANNEL "tx_pool" ENABLE_CHANNEL_BY_DEFAULT("tx_pool"); +using namespace epee; + namespace currency { //--------------------------------------------------------------------------------- diff --git a/src/currency_core/tx_pool.h b/src/currency_core/tx_pool.h index 4e660a68..c9de4748 100644 --- a/src/currency_core/tx_pool.h +++ b/src/currency_core/tx_pool.h @@ -6,7 +6,6 @@ #pragma once #include "include_base_utils.h" -using namespace epee; #include diff --git a/src/stratum/stratum_server.cpp b/src/stratum/stratum_server.cpp index f952cd7a..8ec41088 100644 --- a/src/stratum/stratum_server.cpp +++ b/src/stratum/stratum_server.cpp @@ -368,7 +368,7 @@ namespace void block_template_update_thread() { - log_space::log_singletone::set_thread_log_prefix("[ST]"); + epee::log_space::log_singletone::set_thread_log_prefix("[ST]"); while (!m_stop_flag) { if (is_core_syncronized() && epee::misc_utils::get_tick_count() - m_block_template_update_ts >= m_block_template_update_pediod_ms) diff --git a/src/wallet/wallets_manager.h b/src/wallet/wallets_manager.h index 77656785..45dec851 100644 --- a/src/wallet/wallets_manager.h +++ b/src/wallet/wallets_manager.h @@ -15,8 +15,6 @@ DISABLE_VS_WARNINGS(4503) #include "include_base_utils.h" #include "version.h" -using namespace epee; - #include "console_handler.h" #include "p2p/net_node.h" #include "currency_core/checkpoints_create.h" From d692c83d1453c4b97fdbd4cb170cd1ce222a7e14 Mon Sep 17 00:00:00 2001 From: sowle Date: Tue, 10 May 2022 21:10:33 +0200 Subject: [PATCH 0197/1271] crypto serialization code re-arranged, bpp_signature_serialized introduced --- ...serialization.h => crypto_serialization.h} | 68 +++++++++++++++++-- src/currency_core/bc_offers_serialization.h | 2 +- src/currency_core/currency_basic.h | 3 +- .../currency_boost_serialization.h | 2 +- .../maintainers_info_boost_serialization.h | 2 +- src/serialization/crypto.h | 63 ----------------- 6 files changed, 66 insertions(+), 74 deletions(-) rename src/common/{crypto_boost_serialization.h => crypto_serialization.h} (56%) delete mode 100644 src/serialization/crypto.h diff --git a/src/common/crypto_boost_serialization.h b/src/common/crypto_serialization.h similarity index 56% rename from src/common/crypto_boost_serialization.h rename to src/common/crypto_serialization.h index 310c8e92..23b4c143 100644 --- a/src/common/crypto_boost_serialization.h +++ b/src/common/crypto_serialization.h @@ -1,11 +1,9 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2022 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. - #pragma once - #include #include #include @@ -13,7 +11,65 @@ #include #include #include + +#include "serialization/serialization.h" +#include "serialization/debug_archive.h" +#include "crypto/chacha8.h" #include "crypto/crypto.h" +#include "crypto/hash.h" +#include "crypto/range_proofs.h" +#include "boost_serialization_maps.h" + +// +// binary serialization +// + +namespace crypto +{ + struct bpp_signature_serialized : public crypto::bppe_signature + { + BEGIN_SERIALIZE_OBJECT() + FIELD(L) + FIELD(R) + FIELD(A0) + FIELD(A) + FIELD(B) + FIELD(r) + FIELD(s) + FIELD(delta) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(L) + BOOST_SERIALIZE(R) + BOOST_SERIALIZE(A0) + BOOST_SERIALIZE(A) + BOOST_SERIALIZE(B) + BOOST_SERIALIZE(r) + BOOST_SERIALIZE(s) + BOOST_SERIALIZE(delta) + END_BOOST_SERIALIZATION() + }; +} + +BLOB_SERIALIZER(crypto::chacha8_iv); +BLOB_SERIALIZER(crypto::hash); +BLOB_SERIALIZER(crypto::public_key); +BLOB_SERIALIZER(crypto::secret_key); +BLOB_SERIALIZER(crypto::key_derivation); +BLOB_SERIALIZER(crypto::key_image); +BLOB_SERIALIZER(crypto::signature); +VARIANT_TAG(debug_archive, crypto::hash, "hash"); +VARIANT_TAG(debug_archive, crypto::public_key, "public_key"); +VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key"); +VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation"); +VARIANT_TAG(debug_archive, crypto::key_image, "key_image"); +VARIANT_TAG(debug_archive, crypto::signature, "signature"); + + +// +// Boost serialization +// namespace boost { @@ -51,7 +107,5 @@ namespace boost { a & reinterpret_cast(x); } - } -} - -//} + } // namespace serialization +} // namespace boost diff --git a/src/currency_core/bc_offers_serialization.h b/src/currency_core/bc_offers_serialization.h index cee1e274..f786133f 100644 --- a/src/currency_core/bc_offers_serialization.h +++ b/src/currency_core/bc_offers_serialization.h @@ -13,7 +13,7 @@ #include #include #include "common/unordered_containers_boost_serialization.h" -#include "common/crypto_boost_serialization.h" +#include "common/crypto_serialization.h" #include "offers_service_basics.h" #include "offers_services_helpers.h" diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index 676d4f4d..37c97a58 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -26,7 +26,6 @@ #include "include_base_utils.h" #include "serialization/binary_archive.h" -#include "serialization/crypto.h" #include "serialization/stl_containers.h" #include "serialization/serialization.h" #include "serialization/variant.h" @@ -37,9 +36,11 @@ #include "currency_config.h" #include "crypto/crypto.h" #include "crypto/hash.h" +#include "crypto/range_proofs.h" #include "misc_language.h" #include "block_flags.h" #include "etc_custom_serialization.h" +#include "common/crypto_serialization.h" namespace currency { diff --git a/src/currency_core/currency_boost_serialization.h b/src/currency_core/currency_boost_serialization.h index 9b241bec..72d2bd4d 100644 --- a/src/currency_core/currency_boost_serialization.h +++ b/src/currency_core/currency_boost_serialization.h @@ -15,7 +15,7 @@ #include #include "currency_basic.h" #include "common/unordered_containers_boost_serialization.h" -#include "common/crypto_boost_serialization.h" +#include "common/crypto_serialization.h" #include "offers_services_helpers.h" #define CURRENT_BLOCK_ARCHIVE_VER 2 diff --git a/src/p2p/maintainers_info_boost_serialization.h b/src/p2p/maintainers_info_boost_serialization.h index fafc659e..b8b5df48 100644 --- a/src/p2p/maintainers_info_boost_serialization.h +++ b/src/p2p/maintainers_info_boost_serialization.h @@ -6,7 +6,7 @@ #pragma once #include "p2p_protocol_defs.h" -#include "common/crypto_boost_serialization.h" +#include "common/crypto_serialization.h" namespace boost { diff --git a/src/serialization/crypto.h b/src/serialization/crypto.h deleted file mode 100644 index c436b466..00000000 --- a/src/serialization/crypto.h +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2014-2017 The The Louisdor Project -// Copyright (c) 2012-2013 The Cryptonote developers -// Distributed under the MIT/X11 software license, see the accompanying -// file COPYING or http://www.opensource.org/licenses/mit-license.php. - -#include "serialization.h" -#include "debug_archive.h" -#include "crypto/chacha8.h" -#include "crypto/crypto.h" -#include "crypto/hash.h" - -/*// read -template

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�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