diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 52bf31e94..f7a5e5197 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -1,259 +1,277 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_INTERFACES_NODE_H #define BITCOIN_INTERFACES_NODE_H #include <consensus/amount.h> #include <net.h> // For CConnman::NumConnections #include <net_types.h> // For banmap_t #include <netaddress.h> // For Network #include <support/allocators/secure.h> // For SecureString +#include <util/settings.h> // For util::SettingsValue #include <util/translation.h> #include <cstddef> #include <cstdint> #include <functional> #include <memory> #include <string> #include <tuple> #include <vector> class BanMan; class CCoinControl; class CFeeRate; struct CNodeStateStats; struct CNodeStats; class Coin; class Config; class HTTPRPCRequestProcessor; class proxyType; class RPCServer; class RPCTimerInterface; enum class SynchronizationState; class UniValue; struct bilingual_str; namespace node { struct NodeContext; } // namespace node namespace interfaces { class Handler; class WalletClient; struct BlockTip; //! Block and header tip information struct BlockAndHeaderTipInfo { int block_height; int64_t block_time; int header_height; int64_t header_time; double verification_progress; }; //! Top-level interface for a bitcoin node (bitcoind process). class Node { public: virtual ~Node() {} //! Init logging. virtual void initLogging() = 0; //! Init parameter interaction. virtual void initParameterInteraction() = 0; //! Get warnings. virtual bilingual_str getWarnings() = 0; //! Initialize app dependencies. virtual bool baseInitialize(Config &config) = 0; //! Start node. virtual bool appInitMain(Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor, interfaces::BlockAndHeaderTipInfo *tip_info = nullptr) = 0; //! Stop node. virtual void appShutdown() = 0; //! Start shutdown. virtual void startShutdown() = 0; //! Return whether shutdown was requested. virtual bool shutdownRequested() = 0; + //! Return whether a particular setting in <datadir>/settings.json + //! would be ignored because it is also specified in the command line. + virtual bool isPersistentSettingIgnored(const std::string &name) = 0; + + //! Return setting value from <datadir>/settings.json or bitcoin.conf. + virtual util::SettingsValue + getPersistentSetting(const std::string &name) = 0; + + //! Update a setting in <datadir>/settings.json. + virtual void updateRwSetting(const std::string &name, + const util::SettingsValue &value) = 0; + + //! Force a setting value to be applied, overriding any other configuration + //! source, but not being persisted. + virtual void forceSetting(const std::string &name, + const util::SettingsValue &value) = 0; + //! Map port. virtual void mapPort(bool use_upnp, bool use_natpmp) = 0; //! Get proxy. virtual bool getProxy(Network net, proxyType &proxy_info) = 0; //! Get number of connections. virtual size_t getNodeCount(CConnman::NumConnections flags) = 0; //! Get stats for connected nodes. using NodesStats = std::vector<std::tuple<CNodeStats, bool, CNodeStateStats>>; virtual bool getNodesStats(NodesStats &stats) = 0; //! Get ban map entries. virtual bool getBanned(banmap_t &banmap) = 0; //! Ban node. virtual bool ban(const CNetAddr &net_addr, int64_t ban_time_offset) = 0; //! Unban node. virtual bool unban(const CSubNet &ip) = 0; //! Disconnect node by address. virtual bool disconnectByAddress(const CNetAddr &net_addr) = 0; //! Disconnect node by id. virtual bool disconnectById(NodeId id) = 0; //! Get total bytes recv. virtual int64_t getTotalBytesRecv() = 0; //! Get total bytes sent. virtual int64_t getTotalBytesSent() = 0; //! Get mempool size. virtual size_t getMempoolSize() = 0; //! Get mempool dynamic usage. virtual size_t getMempoolDynamicUsage() = 0; //! Get header tip height and time. virtual bool getHeaderTip(int &height, int64_t &block_time) = 0; //! Get num blocks. virtual int getNumBlocks() = 0; //! Get best block hash. virtual BlockHash getBestBlockHash() = 0; //! Get last block time. virtual int64_t getLastBlockTime() = 0; //! Get verification progress. virtual double getVerificationProgress() = 0; //! Is initial block download. virtual bool isInitialBlockDownload() = 0; //! Get reindex. virtual bool getReindex() = 0; //! Get importing. virtual bool getImporting() = 0; //! Set network active. virtual void setNetworkActive(bool active) = 0; //! Get network active. virtual bool getNetworkActive() = 0; //! Get dust relay fee. virtual CFeeRate getDustRelayFee() = 0; //! Execute rpc command. virtual UniValue executeRpc(const Config &config, const std::string &command, const UniValue ¶ms, const std::string &uri) = 0; //! List rpc commands. virtual std::vector<std::string> listRpcCommands() = 0; //! Set RPC timer interface if unset. virtual void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) = 0; //! Unset RPC timer interface. virtual void rpcUnsetTimerInterface(RPCTimerInterface *iface) = 0; //! Get unspent outputs associated with a transaction. virtual bool getUnspentOutput(const COutPoint &output, Coin &coin) = 0; //! Get wallet client. virtual WalletClient &walletClient() = 0; //! Register handler for init messages. using InitMessageFn = std::function<void(const std::string &message)>; virtual std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) = 0; //! Register handler for message box messages. using MessageBoxFn = std::function<bool(const bilingual_str &message, const std::string &caption, unsigned int style)>; virtual std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) = 0; //! Register handler for question messages. using QuestionFn = std::function<bool(const bilingual_str &message, const std::string &non_interactive_message, const std::string &caption, unsigned int style)>; virtual std::unique_ptr<Handler> handleQuestion(QuestionFn fn) = 0; //! Register handler for progress messages. using ShowProgressFn = std::function<void( const std::string &title, int progress, bool resume_possible)>; virtual std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) = 0; //! Register handler for number of connections changed messages. using NotifyNumConnectionsChangedFn = std::function<void(int new_num_connections)>; virtual std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) = 0; //! Register handler for network active messages. using NotifyNetworkActiveChangedFn = std::function<void(bool network_active)>; virtual std::unique_ptr<Handler> handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) = 0; //! Register handler for notify alert messages. using NotifyAlertChangedFn = std::function<void()>; virtual std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) = 0; //! Register handler for ban list messages. using BannedListChangedFn = std::function<void()>; virtual std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) = 0; //! Register handler for block tip messages. using NotifyBlockTipFn = std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>; virtual std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) = 0; //! Register handler for header tip messages. using NotifyHeaderTipFn = std::function<void(SynchronizationState, interfaces::BlockTip tip, double verification_progress)>; virtual std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) = 0; //! Get and set internal node context. Useful for testing, but not //! accessible across processes. virtual node::NodeContext *context() { return nullptr; } virtual void setContext(node::NodeContext *context) {} }; //! Return implementation of Node interface. std::unique_ptr<Node> MakeNode(node::NodeContext *context = nullptr); //! Block tip (could be a header or not, depends on the subscribed signal). struct BlockTip { int block_height; int64_t block_time; BlockHash block_hash; }; } // namespace interfaces #endif // BITCOIN_INTERFACES_NODE_H diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index ab6e2f0c0..3b257708b 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -1,728 +1,763 @@ // Copyright (c) 2018 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <addrdb.h> #include <banman.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <init.h> #include <interfaces/chain.h> #include <interfaces/handler.h> #include <interfaces/node.h> #include <interfaces/wallet.h> #include <mapport.h> #include <net.h> #include <net_processing.h> #include <netaddress.h> #include <netbase.h> #include <node/blockstorage.h> #include <node/coin.h> #include <node/context.h> #include <node/transaction.h> #include <node/ui_interface.h> #include <policy/settings.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <rpc/protocol.h> #include <rpc/server.h> #include <shutdown.h> #include <sync.h> #include <timedata.h> #include <txmempool.h> #include <uint256.h> #include <util/check.h> #include <util/system.h> #include <util/translation.h> #include <validation.h> #include <validationinterface.h> #include <warnings.h> #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #include <univalue.h> #include <boost/signals2/signal.hpp> #include <memory> #include <utility> class HTTPRPCRequestProcessor; using interfaces::BlockTip; using interfaces::Chain; using interfaces::FoundBlock; using interfaces::Handler; using interfaces::MakeHandler; using interfaces::Node; using interfaces::WalletClient; namespace node { namespace { class NodeImpl : public Node { private: ChainstateManager &chainman() { return *Assert(m_context->chainman); } public: explicit NodeImpl(NodeContext *context) { setContext(context); } void initLogging() override { InitLogging(*Assert(m_context->args)); } void initParameterInteraction() override { InitParameterInteraction(*Assert(m_context->args)); } bilingual_str getWarnings() override { return GetWarnings(true); } bool baseInitialize(Config &config) override { return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(config, gArgs) && AppInitSanityChecks() && AppInitLockDataDirectory() && AppInitInterfaces(*m_context); } bool appInitMain(Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor, interfaces::BlockAndHeaderTipInfo *tip_info) override { return AppInitMain(config, rpcServer, httpRPCRequestProcessor, *m_context, tip_info); } void appShutdown() override { Interrupt(*m_context); Shutdown(*m_context); } void startShutdown() override { StartShutdown(); // Stop RPC for clean shutdown if any of waitfor* commands is // executed. if (gArgs.GetBoolArg("-server", false)) { InterruptRPC(); StopRPC(); } } bool shutdownRequested() override { return ShutdownRequested(); } + bool isPersistentSettingIgnored(const std::string &name) override { + bool ignored = false; + gArgs.LockSettings([&](util::Settings &settings) { + if (auto *options = + util::FindKey(settings.command_line_options, name)) { + ignored = !options->empty(); + } + }); + return ignored; + } + util::SettingsValue + getPersistentSetting(const std::string &name) override { + return gArgs.GetPersistentSetting(name); + } + void updateRwSetting(const std::string &name, + const util::SettingsValue &value) override { + gArgs.LockSettings([&](util::Settings &settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + gArgs.WriteSettingsFile(); + } + void forceSetting(const std::string &name, + const util::SettingsValue &value) override { + gArgs.LockSettings([&](util::Settings &settings) { + if (value.isNull()) { + settings.forced_settings.erase(name); + } else { + settings.forced_settings[name] = value; + } + }); + } void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } bool getProxy(Network net, proxyType &proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; } bool getNodesStats(NodesStats &stats) override { stats.clear(); if (m_context->connman) { std::vector<CNodeStats> stats_temp; m_context->connman->GetNodeStats(stats_temp); stats.reserve(stats_temp.size()); for (auto &node_stats_temp : stats_temp) { stats.emplace_back(std::move(node_stats_temp), false, CNodeStateStats()); } // Try to retrieve the CNodeStateStats for each node. if (m_context->peerman) { TRY_LOCK(::cs_main, lockMain); if (lockMain) { for (auto &node_stats : stats) { std::get<1>(node_stats) = m_context->peerman->GetNodeStateStats( std::get<0>(node_stats).nodeid, std::get<2>(node_stats)); } } } return true; } return false; } bool getBanned(banmap_t &banmap) override { if (m_context->banman) { m_context->banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr &net_addr, int64_t ban_time_offset) override { if (m_context->banman) { m_context->banman->Ban(net_addr, ban_time_offset); return true; } return false; } bool unban(const CSubNet &ip) override { if (m_context->banman) { m_context->banman->Unban(ip); return true; } return false; } bool disconnectByAddress(const CNetAddr &net_addr) override { if (m_context->connman) { return m_context->connman->DisconnectNode(net_addr); } return false; } bool disconnectById(NodeId id) override { if (m_context->connman) { return m_context->connman->DisconnectNode(id); } return false; } int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; } int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; } size_t getMempoolDynamicUsage() override { return m_context->mempool ? m_context->mempool->DynamicMemoryUsage() : 0; } bool getHeaderTip(int &height, int64_t &block_time) override { LOCK(::cs_main); auto best_header = chainman().m_best_header; if (best_header) { height = best_header->nHeight; block_time = best_header->GetBlockTime(); return true; } return false; } int getNumBlocks() override { LOCK(::cs_main); return chainman().ActiveChain().Height(); } BlockHash getBestBlockHash() override { const CBlockIndex *tip = WITH_LOCK(::cs_main, return chainman().ActiveTip()); return tip ? tip->GetBlockHash() : chainman().GetParams().GenesisBlock().GetHash(); } int64_t getLastBlockTime() override { LOCK(::cs_main); if (chainman().ActiveChain().Tip()) { return chainman().ActiveChain().Tip()->GetBlockTime(); } // Genesis block's time of current network return chainman().GetParams().GenesisBlock().GetBlockTime(); } double getVerificationProgress() override { const CBlockIndex *tip; { LOCK(::cs_main); tip = chainman().ActiveChain().Tip(); } return GuessVerificationProgress(chainman().GetParams().TxData(), tip); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool getReindex() override { return node::fReindex; } bool getImporting() override { return node::fImporting; } void setNetworkActive(bool active) override { if (m_context->connman) { m_context->connman->SetNetworkActive(active); } } bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); } CFeeRate getDustRelayFee() override { return ::dustRelayFee; } UniValue executeRpc(const Config &config, const std::string &command, const UniValue ¶ms, const std::string &uri) override { JSONRPCRequest req; req.context = m_context; req.params = params; req.strMethod = command; req.URI = uri; return ::tableRPC.execute(config, req); } std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); } void rpcSetTimerInterfaceIfUnset(RPCTimerInterface *iface) override { RPCSetTimerInterfaceIfUnset(iface); } void rpcUnsetTimerInterface(RPCTimerInterface *iface) override { RPCUnsetTimerInterface(iface); } bool getUnspentOutput(const COutPoint &output, Coin &coin) override { LOCK(::cs_main); return chainman().ActiveChainstate().CoinsTip().GetCoin(output, coin); } WalletClient &walletClient() override { return *Assert(m_context->wallet_client); } std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage_connect(fn)); } std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) override { return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn)); } std::unique_ptr<Handler> handleQuestion(QuestionFn fn) override { return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn)); } std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override { return MakeHandler(::uiInterface.ShowProgress_connect(fn)); } std::unique_ptr<Handler> handleNotifyNumConnectionsChanged( NotifyNumConnectionsChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNumConnectionsChanged_connect(fn)); } std::unique_ptr<Handler> handleNotifyNetworkActiveChanged( NotifyNetworkActiveChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNetworkActiveChanged_connect(fn)); } std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) override { return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn)); } std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) override { return MakeHandler(::uiInterface.BannedListChanged_connect(fn)); } std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) override { return MakeHandler(::uiInterface.NotifyBlockTip_connect( [fn](SynchronizationState sync_state, const CBlockIndex *block) { fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()}, GuessVerificationProgress(Params().TxData(), block)); })); } std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override { /* verification progress is unused when a header was received */ return MakeHandler(::uiInterface.NotifyHeaderTip_connect( [fn](SynchronizationState sync_state, const CBlockIndex *block) { fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()}, 0); })); } NodeContext *context() override { return m_context; } void setContext(NodeContext *context) override { m_context = context; } NodeContext *m_context{nullptr}; }; bool FillBlock(const CBlockIndex *index, const FoundBlock &block, UniqueLock<RecursiveMutex> &lock, const CChain &active) { if (!index) { return false; } if (block.m_hash) { *block.m_hash = index->GetBlockHash(); } if (block.m_height) { *block.m_height = index->nHeight; } if (block.m_time) { *block.m_time = index->GetBlockTime(); } if (block.m_max_time) { *block.m_max_time = index->GetBlockTimeMax(); } if (block.m_mtp_time) { *block.m_mtp_time = index->GetMedianTimePast(); } if (block.m_in_active_chain) { *block.m_in_active_chain = active[index->nHeight] == index; } if (block.m_next_block) { FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active); } if (block.m_data) { REVERSE_LOCK(lock); if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) { block.m_data->SetNull(); } } return true; } class NotificationsProxy : public CValidationInterface { public: explicit NotificationsProxy( std::shared_ptr<Chain::Notifications> notifications) : m_notifications(std::move(notifications)) {} virtual ~NotificationsProxy() = default; void TransactionAddedToMempool(const CTransactionRef &tx, std::shared_ptr<const std::vector<Coin>>, uint64_t mempool_sequence) override { m_notifications->transactionAddedToMempool(tx, mempool_sequence); } void TransactionRemovedFromMempool(const CTransactionRef &tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override { m_notifications->transactionRemovedFromMempool(tx, reason, mempool_sequence); } void BlockConnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex *index) override { m_notifications->blockConnected(*block, index->nHeight); } void BlockDisconnected(const std::shared_ptr<const CBlock> &block, const CBlockIndex *index) override { m_notifications->blockDisconnected(*block, index->nHeight); } void UpdatedBlockTip(const CBlockIndex *index, const CBlockIndex *fork_index, bool is_ibd) override { m_notifications->updatedBlockTip(); } void ChainStateFlushed(const CBlockLocator &locator) override { m_notifications->chainStateFlushed(locator); } std::shared_ptr<Chain::Notifications> m_notifications; }; class NotificationsHandlerImpl : public Handler { public: explicit NotificationsHandlerImpl( std::shared_ptr<Chain::Notifications> notifications) : m_proxy(std::make_shared<NotificationsProxy>( std::move(notifications))) { RegisterSharedValidationInterface(m_proxy); } ~NotificationsHandlerImpl() override { disconnect(); } void disconnect() override { if (m_proxy) { UnregisterSharedValidationInterface(m_proxy); m_proxy.reset(); } } std::shared_ptr<NotificationsProxy> m_proxy; }; class RpcHandlerImpl : public Handler { public: explicit RpcHandlerImpl(const CRPCCommand &command) : m_command(command), m_wrapped_command(&command) { m_command.actor = [this](const Config &config, const JSONRPCRequest &request, UniValue &result, bool last_handler) { if (!m_wrapped_command) { return false; } try { return m_wrapped_command->actor(config, request, result, last_handler); } catch (const UniValue &e) { // If this is not the last handler and a wallet not found // exception was thrown, return false so the next handler // can try to handle the request. Otherwise, reraise the // exception. if (!last_handler) { const UniValue &code = e["code"]; if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { return false; } } throw; } }; ::tableRPC.appendCommand(m_command.name, &m_command); } void disconnect() final { if (m_wrapped_command) { m_wrapped_command = nullptr; ::tableRPC.removeCommand(m_command.name, &m_command); } } ~RpcHandlerImpl() override { disconnect(); } CRPCCommand m_command; const CRPCCommand *m_wrapped_command; }; class ChainImpl : public Chain { private: ChainstateManager &chainman() { return *Assert(m_node.chainman); } public: explicit ChainImpl(NodeContext &node, const CChainParams ¶ms) : m_node(node), m_params(params) {} std::optional<int> getHeight() override { LOCK(::cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); int height = active.Height(); if (height >= 0) { return height; } return std::nullopt; } BlockHash getBlockHash(int height) override { LOCK(::cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); CBlockIndex *block = active[height]; assert(block); return block->GetBlockHash(); } bool haveBlockOnDisk(int height) override { LOCK(cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); CBlockIndex *block = active[height]; return block && (block->nStatus.hasData() != 0) && block->nTx > 0; } CBlockLocator getTipLocator() override { LOCK(cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); return active.GetLocator(); } std::optional<int> findLocatorFork(const CBlockLocator &locator) override { LOCK(cs_main); const Chainstate &active = Assert(m_node.chainman)->ActiveChainstate(); if (const CBlockIndex *fork = active.FindForkInGlobalIndex(locator)) { return fork->nHeight; } return std::nullopt; } bool findBlock(const BlockHash &hash, const FoundBlock &block) override { WAIT_LOCK(cs_main, lock); const CChain &active = Assert(m_node.chainman)->ActiveChain(); return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); } bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock &block) override { WAIT_LOCK(cs_main, lock); const CChain &active = Assert(m_node.chainman)->ActiveChain(); return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active); } bool findAncestorByHeight(const BlockHash &block_hash, int ancestor_height, const FoundBlock &ancestor_out) override { WAIT_LOCK(cs_main, lock); const CChain &active = Assert(m_node.chainman)->ActiveChain(); if (const CBlockIndex *block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { if (const CBlockIndex *ancestor = block->GetAncestor(ancestor_height)) { return FillBlock(ancestor, ancestor_out, lock, active); } } return FillBlock(nullptr, ancestor_out, lock, active); } bool findAncestorByHash(const BlockHash &block_hash, const BlockHash &ancestor_hash, const FoundBlock &ancestor_out) override { WAIT_LOCK(cs_main, lock); const CChain &active = Assert(m_node.chainman)->ActiveChain(); const CBlockIndex *block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); const CBlockIndex *ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) { ancestor = nullptr; } return FillBlock(ancestor, ancestor_out, lock, active); } bool findCommonAncestor(const BlockHash &block_hash1, const BlockHash &block_hash2, const FoundBlock &ancestor_out, const FoundBlock &block1_out, const FoundBlock &block2_out) override { WAIT_LOCK(cs_main, lock); const CChain &active = Assert(m_node.chainman)->ActiveChain(); const CBlockIndex *block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); const CBlockIndex *block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); const CBlockIndex *ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; // Using & instead of && below to avoid short circuiting and leaving // output uninitialized. Cast bool to int to avoid // -Wbitwise-instead-of-logical compiler warnings. return int{FillBlock(ancestor, ancestor_out, lock, active)} & int{FillBlock(block1, block1_out, lock, active)} & int{FillBlock(block2, block2_out, lock, active)}; } void findCoins(std::map<COutPoint, Coin> &coins) override { return FindCoins(m_node, coins); } double guessVerificationProgress(const BlockHash &block_hash) override { LOCK(cs_main); return GuessVerificationProgress( chainman().GetParams().TxData(), chainman().m_blockman.LookupBlockIndex(block_hash)); } bool hasBlocks(const BlockHash &block_hash, int min_height, std::optional<int> max_height) override { // hasBlocks returns true if all ancestors of block_hash in // specified range have block data (are not pruned), false if any // ancestors in specified range are missing data. // // For simplicity and robustness, min_height and max_height are only // used to limit the range, and passing min_height that's too low or // max_height that's too high will not crash or change the result. LOCK(::cs_main); if (const CBlockIndex *block = chainman().m_blockman.LookupBlockIndex(block_hash)) { if (max_height && block->nHeight >= *max_height) { block = block->GetAncestor(*max_height); } for (; block->nStatus.hasData(); block = block->pprev) { // Check pprev to not segfault if min_height is too low if (block->nHeight <= min_height || !block->pprev) { return true; } } } return false; } bool broadcastTransaction(const Config &config, const CTransactionRef &tx, const Amount &max_tx_fee, bool relay, std::string &err_string) override { const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback=*/false); // Chain clients only care about failures to accept the tx to the // mempool. Disregard non-mempool related failures. Note: this will // need to be updated if BroadcastTransactions() is updated to // return other non-mempool failures that Chain clients do not need // to know about. return err == TransactionError::OK; } CFeeRate estimateFee() const override { if (!m_node.mempool) { return {}; } return m_node.mempool->estimateFee(); } CFeeRate relayMinFee() override { return ::minRelayTxFee; } CFeeRate relayDustFee() override { return ::dustRelayFee; } bool havePruned() override { LOCK(cs_main); return m_node.chainman->m_blockman.m_have_pruned; } bool isReadyToBroadcast() override { return !chainman().m_blockman.LoadingBlocks() && !isInitialBlockDownload(); } bool isInitialBlockDownload() override { return chainman().ActiveChainstate().IsInitialBlockDownload(); } bool shutdownRequested() override { return ShutdownRequested(); } int64_t getAdjustedTime() override { return GetAdjustedTime(); } void initMessage(const std::string &message) override { ::uiInterface.InitMessage(message); } void initWarning(const bilingual_str &message) override { InitWarning(message); } void initError(const bilingual_str &message) override { InitError(message); } void showProgress(const std::string &title, int progress, bool resume_possible) override { ::uiInterface.ShowProgress(title, progress, resume_possible); } std::unique_ptr<Handler> handleNotifications( std::shared_ptr<Notifications> notifications) override { return std::make_unique<NotificationsHandlerImpl>( std::move(notifications)); } void waitForNotificationsIfTipChanged(const BlockHash &old_tip) override { if (!old_tip.IsNull()) { LOCK(::cs_main); const CChain &active = Assert(m_node.chainman)->ActiveChain(); if (old_tip == active.Tip()->GetBlockHash()) { return; } } SyncWithValidationInterfaceQueue(); } std::unique_ptr<Handler> handleRpc(const CRPCCommand &command) override { return std::make_unique<RpcHandlerImpl>(command); } bool rpcEnableDeprecated(const std::string &method) override { return IsDeprecatedRPCEnabled(gArgs, method); } void rpcRunLater(const std::string &name, std::function<void()> fn, int64_t seconds) override { RPCRunLater(name, std::move(fn), seconds); } int rpcSerializationFlags() override { return RPCSerializationFlags(); } util::SettingsValue getRwSetting(const std::string &name) override { util::SettingsValue result; gArgs.LockSettings([&](const util::Settings &settings) { if (const util::SettingsValue *value = util::FindKey(settings.rw_settings, name)) { result = *value; } }); return result; } bool updateRwSetting(const std::string &name, const util::SettingsValue &value) override { gArgs.LockSettings([&](util::Settings &settings) { if (value.isNull()) { settings.rw_settings.erase(name); } else { settings.rw_settings[name] = value; } }); return gArgs.WriteSettingsFile(); } void requestMempoolTransactions(Notifications ¬ifications) override { if (!m_node.mempool) { return; } LOCK2(::cs_main, m_node.mempool->cs); for (const CTxMemPoolEntry &entry : m_node.mempool->mapTx) { notifications.transactionAddedToMempool(entry.GetSharedTx(), /*mempool_sequence=*/0); } } const CChainParams ¶ms() const override { return m_params; } NodeContext &m_node; const CChainParams &m_params; }; } // namespace } // namespace node namespace interfaces { std::unique_ptr<Node> MakeNode(node::NodeContext *context) { return std::make_unique<node::NodeImpl>(context); } std::unique_ptr<Chain> MakeChain(node::NodeContext &node, const CChainParams ¶ms) { return std::make_unique<node::ChainImpl>(node, params); } } // namespace interfaces diff --git a/src/test/settings_tests.cpp b/src/test/settings_tests.cpp index c2cecb23f..b4fbb6c07 100644 --- a/src/test/settings_tests.cpp +++ b/src/test/settings_tests.cpp @@ -1,300 +1,310 @@ // Copyright (c) 2011-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/settings.h> #include <fs.h> #include <test/util/setup_common.h> #include <test/util/str.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> #include <univalue.h> #include <boost/test/unit_test.hpp> #include <fstream> #include <map> #include <string> #include <system_error> #include <vector> inline bool operator==(const util::SettingsValue &a, const util::SettingsValue &b) { return a.write() == b.write(); } inline std::ostream &operator<<(std::ostream &os, const util::SettingsValue &value) { os << value.write(); return os; } inline std::ostream & operator<<(std::ostream &os, const std::pair<std::string, util::SettingsValue> &kv) { util::SettingsValue out(util::SettingsValue::VOBJ); out.__pushKV(kv.first, kv.second); os << out.write(); return os; } inline void WriteText(const fs::path &path, const std::string &text) { std::ofstream file; file.open(path); file << text; } BOOST_FIXTURE_TEST_SUITE(settings_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(ReadWrite) { fs::path path = m_args.GetDataDirBase() / "settings.json"; WriteText(path, R"({ "string": "string", "num": 5, "bool": true, "null": null })"); std::map<std::string, util::SettingsValue> expected{ {"string", "string"}, {"num", 5}, {"bool", true}, {"null", {}}, }; // Check file read. std::map<std::string, util::SettingsValue> values; std::vector<std::string> errors; BOOST_CHECK(util::ReadSettings(path, values, errors)); BOOST_CHECK_EQUAL_COLLECTIONS(values.begin(), values.end(), expected.begin(), expected.end()); BOOST_CHECK(errors.empty()); // Check no errors if file doesn't exist. fs::remove(path); BOOST_CHECK(util::ReadSettings(path, values, errors)); BOOST_CHECK(values.empty()); BOOST_CHECK(errors.empty()); // Check duplicate keys not allowed WriteText(path, R"({ "dupe": "string", "dupe": "dupe" })"); BOOST_CHECK(!util::ReadSettings(path, values, errors)); std::vector<std::string> dup_keys = { strprintf("Found duplicate key dupe in settings file %s", fs::PathToString(path))}; BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), dup_keys.begin(), dup_keys.end()); // Check non-kv json files not allowed WriteText(path, R"("non-kv")"); BOOST_CHECK(!util::ReadSettings(path, values, errors)); std::vector<std::string> non_kv = { strprintf("Found non-object value \"non-kv\" in settings file %s", fs::PathToString(path))}; BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), non_kv.begin(), non_kv.end()); // Check invalid json not allowed WriteText(path, R"(invalid json)"); BOOST_CHECK(!util::ReadSettings(path, values, errors)); std::vector<std::string> fail_parse = { strprintf("Unable to parse settings file %s", fs::PathToString(path))}; BOOST_CHECK_EQUAL_COLLECTIONS(errors.begin(), errors.end(), fail_parse.begin(), fail_parse.end()); } //! Check settings struct contents against expected json strings. static void CheckValues(const util::Settings &settings, const std::string &single_val, const std::string &list_val) { - util::SettingsValue single_value = - GetSetting(settings, "section", "name", false, false); + util::SettingsValue single_value = GetSetting( + settings, "section", "name", /*ignore_default_section_config=*/false, + /*ignore_nonpersistent=*/false, /*get_chain_name=*/false); util::SettingsValue list_value(util::SettingsValue::VARR); for (const auto &item : - GetSettingsList(settings, "section", "name", false)) { + GetSettingsList(settings, "section", "name", + /*ignore_default_section_config=*/false)) { list_value.push_back(item); } BOOST_CHECK_EQUAL(single_value.write().c_str(), single_val); BOOST_CHECK_EQUAL(list_value.write().c_str(), list_val); }; // Simple settings merge test case. BOOST_AUTO_TEST_CASE(Simple) { util::Settings settings; settings.command_line_options["name"].push_back("val1"); settings.command_line_options["name"].push_back("val2"); settings.ro_config["section"]["name"].push_back(2); // The last given arg takes precedence when specified via commandline. CheckValues(settings, R"("val2")", R"(["val1","val2",2])"); util::Settings settings2; settings2.ro_config["section"]["name"].push_back("val2"); settings2.ro_config["section"]["name"].push_back("val3"); // The first given arg takes precedence when specified via config file. CheckValues(settings2, R"("val2")", R"(["val2","val3"])"); } // Confirm that a high priority setting overrides a lower priority setting even // if the high priority setting is null. This behavior is useful for a high // priority setting source to be able to effectively reset any setting back to // its default value. BOOST_AUTO_TEST_CASE(NullOverride) { util::Settings settings; settings.command_line_options["name"].push_back("value"); - BOOST_CHECK_EQUAL( - R"("value")", - GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"("value")", + GetSetting(settings, "section", "name", + /*ignore_default_section_config=*/false, + /*ignore_nonpersistent=*/false, + /*get_chain_name=*/false) + .write() + .c_str()); settings.forced_settings["name"] = {}; - BOOST_CHECK_EQUAL( - R"(null)", - GetSetting(settings, "section", "name", false, false).write().c_str()); + BOOST_CHECK_EQUAL(R"(null)", + GetSetting(settings, "section", "name", + /*ignore_default_section_config=*/false, + /*ignore_nonpersistent=*/false, + /*get_chain_name=*/false) + .write() + .c_str()); } // Test different ways settings can be merged, and verify results. This test can // be used to confirm that updates to settings code don't change behavior // unintentionally. struct MergeTestingSetup : public BasicTestingSetup { //! Max number of actions to sequence together. Can decrease this when //! debugging to make test results easier to understand. static constexpr int MAX_ACTIONS = 3; enum Action { END, SET, NEGATE, SECTION_SET, SECTION_NEGATE }; using ActionList = Action[MAX_ACTIONS]; //! Enumerate all possible test configurations. template <typename Fn> void ForEachMergeSetup(Fn &&fn) { ActionList arg_actions = {}; // command_line_options do not have sections. Only iterate over SET and // NEGATE ForEachNoDup(arg_actions, SET, NEGATE, [&] { ActionList conf_actions = {}; ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] { for (bool force_set : {false, true}) { for (bool ignore_default_section_config : {false, true}) { fn(arg_actions, conf_actions, force_set, ignore_default_section_config); } } }); }); } }; // Regression test covering different ways config settings can be merged. The // test parses and merges settings, representing the results as strings that get // compared against an expected hash. To debug, the result strings can be dumped // to a file (see comments below). BOOST_FIXTURE_TEST_CASE(Merge, MergeTestingSetup) { CHash256 out_sha; FILE *out_file = nullptr; if (const char *out_path = getenv("SETTINGS_MERGE_TEST_OUT")) { out_file = fsbridge::fopen(out_path, "w"); if (!out_file) { throw std::system_error(errno, std::generic_category(), "fopen failed"); } } const std::string &network = CBaseChainParams::MAIN; ForEachMergeSetup([&](const ActionList &arg_actions, const ActionList &conf_actions, bool force_set, bool ignore_default_section_config) { std::string desc; int value_suffix = 0; util::Settings settings; const std::string &name = ignore_default_section_config ? "wallet" : "server"; auto push_values = [&](Action action, const char *value_prefix, const std::string &name_prefix, std::vector<util::SettingsValue> &dest) { if (action == SET || action == SECTION_SET) { for (int i = 0; i < 2; ++i) { dest.push_back(value_prefix + ToString(++value_suffix)); desc += " " + name_prefix + name + "=" + dest.back().get_str(); } } else if (action == NEGATE || action == SECTION_NEGATE) { dest.push_back(false); desc += " " + name_prefix + "no" + name; } }; if (force_set) { settings.forced_settings[name] = "forced"; desc += " " + name + "=forced"; } for (Action arg_action : arg_actions) { push_values(arg_action, "a", "-", settings.command_line_options[name]); } for (Action conf_action : conf_actions) { bool use_section = conf_action == SECTION_SET || conf_action == SECTION_NEGATE; push_values(conf_action, "c", use_section ? network + "." : "", settings.ro_config[use_section ? network : ""][name]); } desc += " || "; desc += GetSetting(settings, network, name, ignore_default_section_config, - /* get_chain_name= */ false) + /*ignore_nonpersistent=*/false, /*get_chain_name=*/false) .write(); desc += " |"; for (const auto &s : GetSettingsList(settings, network, name, ignore_default_section_config)) { desc += " "; desc += s.write(); } desc += " |"; if (OnlyHasDefaultSectionSetting(settings, network, name)) { desc += " ignored"; } desc += "\n"; out_sha.Write(MakeUCharSpan(desc)); if (out_file) { BOOST_REQUIRE(fwrite(desc.data(), 1, desc.size(), out_file) == desc.size()); } }); if (out_file) { if (fclose(out_file)) { throw std::system_error(errno, std::generic_category(), "fclose failed"); } out_file = nullptr; } uint8_t out_sha_bytes[CSHA256::OUTPUT_SIZE]; out_sha.Finalize(out_sha_bytes); std::string out_sha_hex = HexStr(out_sha_bytes); // If check below fails, should manually dump the results with: // // SETTINGS_MERGE_TEST_OUT=results.txt ./test_bitcoin // --run_test=settings_tests/Merge // // And verify diff against previous results to make sure the changes are // expected. // // Results file is formatted like: // // <input> || GetSetting() | GetSettingsList() | // OnlyHasDefaultSectionSetting() BOOST_CHECK_EQUAL( out_sha_hex, "79db02d74e3e193196541b67c068b40ebd0c124a24b3ecbe9cbf7e85b1c4ba7a"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/settings.cpp b/src/util/settings.cpp index fde3a808b..d15b7a724 100644 --- a/src/util/settings.cpp +++ b/src/util/settings.cpp @@ -1,300 +1,306 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <fs.h> #include <util/settings.h> #include <tinyformat.h> #include <univalue.h> #include <fstream> #include <map> #include <string> #include <vector> namespace util { namespace { enum class Source { FORCED, COMMAND_LINE, RW_SETTINGS, CONFIG_FILE_NETWORK_SECTION, CONFIG_FILE_DEFAULT_SECTION }; //! Merge settings from multiple sources in precedence order: //! Forced config > command line > read-write settings file > config file //! network-specific section > config file default section //! //! This function is provided with a callback function fn that contains //! specific logic for how to merge the sources. template <typename Fn> static void MergeSettings(const Settings &settings, const std::string §ion, const std::string &name, Fn &&fn) { // Merge in the forced settings if (auto *value = FindKey(settings.forced_settings, name)) { fn(SettingsSpan(*value), Source::FORCED); } // Merge in the command-line options if (auto *values = FindKey(settings.command_line_options, name)) { fn(SettingsSpan(*values), Source::COMMAND_LINE); } // Merge in the read-write settings if (const SettingsValue *value = FindKey(settings.rw_settings, name)) { fn(SettingsSpan(*value), Source::RW_SETTINGS); } // Merge in the network-specific section of the config file if (!section.empty()) { if (auto *map = FindKey(settings.ro_config, section)) { if (auto *values = FindKey(*map, name)) { fn(SettingsSpan(*values), Source::CONFIG_FILE_NETWORK_SECTION); } } } // Merge in the default section of the config file if (auto *map = FindKey(settings.ro_config, "")) { if (auto *values = FindKey(*map, name)) { fn(SettingsSpan(*values), Source::CONFIG_FILE_DEFAULT_SECTION); } } } } // namespace bool ReadSettings(const fs::path &path, std::map<std::string, SettingsValue> &values, std::vector<std::string> &errors) { values.clear(); errors.clear(); // Ok for file to not exist if (!fs::exists(path)) { return true; } std::ifstream file; file.open(path); if (!file.is_open()) { errors.emplace_back( strprintf("%s. Please check permissions.", fs::PathToString(path))); return false; } SettingsValue in; if (!in.read(std::string{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()})) { errors.emplace_back(strprintf("Unable to parse settings file %s", fs::PathToString(path))); return false; } if (file.fail()) { errors.emplace_back(strprintf("Failed reading settings file %s", fs::PathToString(path))); return false; } // Done with file descriptor. Release while copying data. file.close(); if (!in.isObject()) { errors.emplace_back( strprintf("Found non-object value %s in settings file %s", in.write(), fs::PathToString(path))); return false; } const std::vector<std::string> &in_keys = in.getKeys(); const std::vector<SettingsValue> &in_values = in.getValues(); for (size_t i = 0; i < in_keys.size(); ++i) { auto inserted = values.emplace(in_keys[i], in_values[i]); if (!inserted.second) { errors.emplace_back( strprintf("Found duplicate key %s in settings file %s", in_keys[i], fs::PathToString(path))); } } return errors.empty(); } bool WriteSettings(const fs::path &path, const std::map<std::string, SettingsValue> &values, std::vector<std::string> &errors) { SettingsValue out(SettingsValue::VOBJ); for (const auto &value : values) { out.__pushKV(value.first, value.second); } std::ofstream file; file.open(path); if (file.fail()) { errors.emplace_back( strprintf("Error: Unable to open settings file %s for writing", fs::PathToString(path))); return false; } file << out.write(/* prettyIndent= */ 1, /* indentLevel= */ 4) << std::endl; file.close(); return true; } SettingsValue GetSetting(const Settings &settings, const std::string §ion, const std::string &name, bool ignore_default_section_config, - bool get_chain_name) { + bool ignore_nonpersistent, bool get_chain_name) { SettingsValue result; // Done merging any more settings sources. bool done = false; MergeSettings( settings, section, name, [&](SettingsSpan span, Source source) { // Weird behavior preserved for backwards compatibility: Apply // negated setting even if non-negated setting would be ignored. A // negated value in the default section is applied to network // specific options, even though normal non-negated values there // would be ignored. const bool never_ignore_negated_setting = span.last_negated(); // Weird behavior preserved for backwards compatibility: Take first // assigned value instead of last. In general, later settings take // precedence over early settings, but for backwards compatibility // in the config file the precedence is reversed for all settings // except chain name settings. const bool reverse_precedence = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !get_chain_name; // Weird behavior preserved for backwards compatibility: Negated // -regtest and -testnet arguments which you would expect to // override values set in the configuration file are currently // accepted but silently ignored. It would be better to apply these // just like other negated values, or at least warn they are // ignored. const bool skip_negated_command_line = get_chain_name; if (done) { return; } // Ignore settings in default config section if requested. if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION && !never_ignore_negated_setting) { return; } + // Ignore nonpersistent settings if requested. + if (ignore_nonpersistent && + (source == Source::COMMAND_LINE || source == Source::FORCED)) { + return; + } + // Skip negated command line settings. if (skip_negated_command_line && span.last_negated()) { return; } if (!span.empty()) { result = reverse_precedence ? span.begin()[0] : span.end()[-1]; done = true; } else if (span.last_negated()) { result = false; done = true; } }); return result; } std::vector<SettingsValue> GetSettingsList(const Settings &settings, const std::string §ion, const std::string &name, bool ignore_default_section_config) { std::vector<SettingsValue> result; // Done merging any more settings sources. bool done = false; bool prev_negated_empty = false; MergeSettings( settings, section, name, [&](SettingsSpan span, Source source) { // Weird behavior preserved for backwards compatibility: Apply // config file settings even if negated on command line. Negating a // setting on command line will ignore earlier settings on the // command line and ignore settings in the config file, unless the // negated command line value is followed by non-negated value, in // which case config file settings will be brought back from the // dead (but earlier command line settings will still be ignored). const bool add_zombie_config_values = (source == Source::CONFIG_FILE_NETWORK_SECTION || source == Source::CONFIG_FILE_DEFAULT_SECTION) && !prev_negated_empty; // Ignore settings in default config section if requested. if (ignore_default_section_config && source == Source::CONFIG_FILE_DEFAULT_SECTION) { return; } // Add new settings to the result if isn't already complete, or if // the values are zombies. if (!done || add_zombie_config_values) { for (const auto &value : span) { if (value.isArray()) { result.insert(result.end(), value.getValues().begin(), value.getValues().end()); } else { result.push_back(value); } } } // If a setting was negated, or if a setting was forced, set done to // true to ignore any later lower priority settings. done |= span.negated() > 0 || source == Source::FORCED; // Update the negated and empty state used for the zombie values // check. prev_negated_empty |= span.last_negated() && result.empty(); }); return result; } bool OnlyHasDefaultSectionSetting(const Settings &settings, const std::string §ion, const std::string &name) { bool has_default_section_setting = false; bool has_other_setting = false; MergeSettings( settings, section, name, [&](SettingsSpan span, Source source) { if (span.empty()) { return; } else if (source == Source::CONFIG_FILE_DEFAULT_SECTION) { has_default_section_setting = true; } else { has_other_setting = true; } }); // If a value is set in the default section and not explicitly overwritten // by the user on the command line or in a different section, then we want // to enable warnings about the value being ignored. return has_default_section_setting && !has_other_setting; } SettingsSpan::SettingsSpan(const std::vector<SettingsValue> &vec) noexcept : SettingsSpan(vec.data(), vec.size()) {} const SettingsValue *SettingsSpan::begin() const { return data + negated(); } const SettingsValue *SettingsSpan::end() const { return data + size; } bool SettingsSpan::empty() const { return size == 0 || last_negated(); } bool SettingsSpan::last_negated() const { return size > 0 && data[size - 1].isFalse(); } size_t SettingsSpan::negated() const { for (size_t i = size; i > 0; --i) { if (data[i - 1].isFalse()) { // Return number of negated values (position of last false value) return i; } } return 0; } } // namespace util diff --git a/src/util/settings.h b/src/util/settings.h index e695728a3..87351ba5d 100644 --- a/src/util/settings.h +++ b/src/util/settings.h @@ -1,117 +1,122 @@ // Copyright (c) 2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_UTIL_SETTINGS_H #define BITCOIN_UTIL_SETTINGS_H #include <fs.h> #include <map> #include <string> #include <vector> class UniValue; namespace util { //! Settings value type (string/integer/boolean/null variant). //! //! @note UniValue is used here for convenience and because it can be easily //! serialized in a readable format. But any other variant type that can //! be assigned strings, int64_t, and bool values and has get_str(), //! get_int64(), get_bool(), isNum(), isBool(), isFalse(), isTrue() and //! isNull() methods can be substituted if there's a need to move away //! from UniValue. (An implementation with boost::variant was posted at //! https://github.com/bitcoin/bitcoin/pull/15934/files#r337691812) using SettingsValue = UniValue; //! Stored settings. This struct combines settings from the command line, a //! read-only configuration file, and a read-write runtime settings file. struct Settings { //! Map of setting name to forced setting value. std::map<std::string, SettingsValue> forced_settings; //! Map of setting name to list of command line values. std::map<std::string, std::vector<SettingsValue>> command_line_options; //! Map of setting name to read-write file setting value. std::map<std::string, SettingsValue> rw_settings; //! Map of config section name and setting name to list of config file //! values. std::map<std::string, std::map<std::string, std::vector<SettingsValue>>> ro_config; }; //! Read settings file. bool ReadSettings(const fs::path &path, std::map<std::string, SettingsValue> &values, std::vector<std::string> &errors); //! Write settings file. bool WriteSettings(const fs::path &path, const std::map<std::string, SettingsValue> &values, std::vector<std::string> &errors); //! Get settings value from combined sources: forced settings, command line //! arguments, runtime read-write settings, and the read-only config file. //! //! @param ignore_default_section_config - ignore values in the default section //! of the config file (part before any //! [section] keywords) +//! @param ignore_nonpersistent - ignore non-persistent settings values (forced +//! settings values and values specified on the +//! command line). Only return settings in the +//! read-only config and read-write settings +//! files. //! @param get_chain_name - enable special backwards compatible behavior //! for GetChainName SettingsValue GetSetting(const Settings &settings, const std::string §ion, const std::string &name, bool ignore_default_section_config, - bool get_chain_name); + bool ignore_nonpersistent, bool get_chain_name); //! Get combined setting value similar to GetSetting(), except if setting was //! specified multiple times, return a list of all the values specified. std::vector<SettingsValue> GetSettingsList(const Settings &settings, const std::string §ion, const std::string &name, bool ignore_default_section_config); //! Return true if a setting is set in the default config file section, and not //! overridden by a higher priority command-line or network section value. //! //! This is used to provide user warnings about values that might be getting //! ignored unintentionally. bool OnlyHasDefaultSectionSetting(const Settings &settings, const std::string §ion, const std::string &name); //! Accessor for list of settings that skips negated values when iterated over. //! The last boolean `false` value in the list and all earlier values are //! considered negated. struct SettingsSpan { explicit SettingsSpan() = default; explicit SettingsSpan(const SettingsValue &value) noexcept : SettingsSpan(&value, 1) {} explicit SettingsSpan(const SettingsValue *dataIn, size_t sizeIn) noexcept : data(dataIn), size(sizeIn) {} explicit SettingsSpan(const std::vector<SettingsValue> &vec) noexcept; //! Pointer to first non-negated value. const SettingsValue *begin() const; //! Pointer to end of values. const SettingsValue *end() const; //! True if there are any non-negated values. bool empty() const; //! True if the last value is negated. bool last_negated() const; //! Number of negated values. size_t negated() const; const SettingsValue *data = nullptr; size_t size = 0; }; //! Map lookup helper. template <typename Map, typename Key> auto FindKey(Map &&map, Key &&key) -> decltype(&map.at(key)) { auto it = map.find(key); return it == map.end() ? nullptr : &it->second; } } // namespace util #endif // BITCOIN_UTIL_SETTINGS_H diff --git a/src/util/system.cpp b/src/util/system.cpp index 5a5620dca..2cacd8685 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,1418 +1,1441 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <util/system.h> #include <chainparamsbase.h> #include <fs.h> #include <sync.h> #include <util/getuniquepath.h> #include <util/strencodings.h> #include <util/string.h> #include <util/translation.h> #include <univalue.h> #include <fstream> #include <memory> #include <optional> #include <string> #include <thread> #include <typeinfo> #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) #include <pthread.h> #include <pthread_np.h> #endif #ifndef WIN32 // for posix_fallocate, in config/CMakeLists.txt we check if it is present after // this #ifdef __linux__ #ifdef _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L #endif // __linux__ #include <algorithm> #include <cassert> #include <fcntl.h> #include <sched.h> #include <sys/resource.h> #include <sys/stat.h> #else #ifdef _MSC_VER #pragma warning(disable : 4786) #pragma warning(disable : 4804) #pragma warning(disable : 4805) #pragma warning(disable : 4717) #endif #ifndef NOMINMAX #define NOMINMAX #endif #include <codecvt> #include <io.h> /* for _commit */ #include <shellapi.h> #include <shlobj.h> #endif #ifdef HAVE_MALLOPT_ARENA_MAX #include <malloc.h> #endif // Application startup time (used for uptime calculation) const int64_t nStartupTime = GetTime(); const char *const BITCOIN_CONF_FILENAME = "bitcoin.conf"; const char *const BITCOIN_SETTINGS_FILENAME = "settings.json"; ArgsManager gArgs; /** Mutex to protect dir_locks. */ static GlobalMutex cs_dir_locks; /** * A map that contains all the currently held directory locks. After successful * locking, these will be held here until the global destructor cleans them up * and thus automatically unlocks them, or ReleaseDirectoryLocks is called. */ static std::map<std::string, std::unique_ptr<fsbridge::FileLock>> dir_locks GUARDED_BY(cs_dir_locks); bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only) { LOCK(cs_dir_locks); fs::path pathLockFile = directory / lockfile_name; // If a lock for this directory already exists in the map, don't try to // re-lock it if (dir_locks.count(fs::PathToString(pathLockFile))) { return true; } // Create empty lock file if it doesn't exist. FILE *file = fsbridge::fopen(pathLockFile, "a"); if (file) { fclose(file); } auto lock = std::make_unique<fsbridge::FileLock>(pathLockFile); if (!lock->TryLock()) { return error("Error while attempting to lock directory %s: %s", fs::PathToString(directory), lock->GetReason()); } if (!probe_only) { // Lock successful and we're not just probing, put it into the map dir_locks.emplace(fs::PathToString(pathLockFile), std::move(lock)); } return true; } void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name) { LOCK(cs_dir_locks); dir_locks.erase(fs::PathToString(directory / lockfile_name)); } void ReleaseDirectoryLocks() { LOCK(cs_dir_locks); dir_locks.clear(); } bool DirIsWritable(const fs::path &directory) { fs::path tmpFile = GetUniquePath(directory); FILE *file = fsbridge::fopen(tmpFile, "a"); if (!file) { return false; } fclose(file); remove(tmpFile); return true; } bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes) { // 50 MiB constexpr uint64_t min_disk_space = 52428800; uint64_t free_bytes_available = fs::space(dir).available; return free_bytes_available >= min_disk_space + additional_bytes; } std::streampos GetFileSize(const char *path, std::streamsize max) { std::ifstream file{path, std::ios::binary}; file.ignore(max); return file.gcount(); } /** * Interpret a string argument as a boolean. * * The definition of atoi() requires that non-numeric string values like "foo", * return 0. This means that if a user unintentionally supplies a non-integer * argument here, the return value is always false. This means that -foo=false * does what the user probably expects, but -foo=true is well defined but does * not do what they probably expected. * * The return value of atoi() is undefined when given input not representable as * an int. On most systems this means string value between "-2147483648" and * "2147483647" are well defined (this method will return true). Setting * -txindex=2147483648 on most systems, however, is probably undefined. * * For a more extensive discussion of this topic (and a wide range of opinions * on the Right Way to change this code), see PR12713. */ static bool InterpretBool(const std::string &strValue) { if (strValue.empty()) { return true; } return (atoi(strValue) != 0); } static std::string SettingName(const std::string &arg) { return arg.size() > 0 && arg[0] == '-' ? arg.substr(1) : arg; } /** * Interpret -nofoo as if the user supplied -foo=0. * * This method also tracks when the -no form was supplied, and if so, checks * whether there was a double-negative (-nofoo=0 -> -foo=1). * * If there was not a double negative, it removes the "no" from the key * and returns false. * * If there was a double negative, it removes "no" from the key, and * returns true. * * If there was no "no", it returns the string value untouched. * * Where an option was negated can be later checked using the IsArgNegated() * method. One use case for this is to have a way to disable options that are * not normally boolean (e.g. using -nodebuglogfile to request that debug log * output is not sent to any file at all). */ static util::SettingsValue InterpretOption(std::string §ion, std::string &key, const std::string &value) { // Split section name from key name for keys like "testnet.foo" or // "regtest.bar" size_t option_index = key.find('.'); if (option_index != std::string::npos) { section = key.substr(0, option_index); key.erase(0, option_index + 1); } if (key.substr(0, 2) == "no") { key.erase(0, 2); // Double negatives like -nofoo=0 are supported (but discouraged) if (!InterpretBool(value)) { LogPrintf("Warning: parsed potentially confusing double-negative " "-%s=%s\n", key, value); return true; } return false; } return value; } /** * Check settings value validity according to flags. * * TODO: Add more meaningful error checks here in the future * See "here's how the flags are meant to behave" in * https://github.com/bitcoin/bitcoin/pull/16097#issuecomment-514627823 */ static bool CheckValid(const std::string &key, const util::SettingsValue &val, unsigned int flags, std::string &error) { if (val.isBool() && !(flags & ArgsManager::ALLOW_BOOL)) { error = strprintf( "Negating of -%s is meaningless and therefore forbidden", key); return false; } return true; } // Define default constructor and destructor that are not inline, so code // instantiating this class doesn't need to #include class definitions for all // members. For example, m_settings has an internal dependency on univalue. ArgsManager::ArgsManager() {} ArgsManager::~ArgsManager() {} const std::set<std::string> ArgsManager::GetUnsuitableSectionOnlyArgs() const { std::set<std::string> unsuitables; LOCK(cs_args); // if there's no section selected, don't worry if (m_network.empty()) { return std::set<std::string>{}; } // if it's okay to use the default section for this network, don't worry if (m_network == CBaseChainParams::MAIN) { return std::set<std::string>{}; } for (const auto &arg : m_network_only_args) { if (OnlyHasDefaultSectionSetting(m_settings, m_network, SettingName(arg))) { unsuitables.insert(arg); } } return unsuitables; } const std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const { // Section names to be recognized in the config file. static const std::set<std::string> available_sections{ CBaseChainParams::REGTEST, CBaseChainParams::TESTNET, CBaseChainParams::MAIN}; LOCK(cs_args); std::list<SectionInfo> unrecognized = m_config_sections; unrecognized.remove_if([](const SectionInfo &appeared) { return available_sections.find(appeared.m_name) != available_sections.end(); }); return unrecognized; } void ArgsManager::SelectConfigNetwork(const std::string &network) { LOCK(cs_args); m_network = network; } bool ParseKeyValue(std::string &key, std::string &val) { size_t is_index = key.find('='); if (is_index != std::string::npos) { val = key.substr(is_index + 1); key.erase(is_index); } #ifdef WIN32 key = ToLower(key); if (key[0] == '/') { key[0] = '-'; } #endif if (key[0] != '-') { return false; } // Transform --foo to -foo if (key.length() > 1 && key[1] == '-') { key.erase(0, 1); } return true; } bool ArgsManager::ParseParameters(int argc, const char *const argv[], std::string &error) { LOCK(cs_args); m_settings.command_line_options.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); #ifdef MAC_OSX // At the first time when a user gets the "App downloaded from the // internet" warning, and clicks the Open button, macOS passes // a unique process serial number (PSN) as -psn_... command-line // argument, which we filter out. if (key.substr(0, 5) == "-psn_") { continue; } #endif if (key == "-") { // bitcoin-tx using stdin break; } std::string val; if (!ParseKeyValue(key, val)) { break; } // Transform -foo to foo key.erase(0, 1); std::string section; util::SettingsValue value = InterpretOption(section, key, val); std::optional<unsigned int> flags = GetArgFlags('-' + key); // Unknown command line options and command line options with dot // characters (which are returned from InterpretOption with nonempty // section strings) are not valid. if (!flags || !section.empty()) { error = strprintf("Invalid parameter %s", argv[i]); return false; } if (!CheckValid(key, value, *flags, error)) { return false; } m_settings.command_line_options[key].push_back(value); } // we do not allow -includeconf from command line bool success = true; if (auto *includes = util::FindKey(m_settings.command_line_options, "includeconf")) { for (const auto &include : util::SettingsSpan(*includes)) { error += "-includeconf cannot be used from commandline; -includeconf=" + include.get_str() + "\n"; success = false; } } return success; } std::optional<unsigned int> ArgsManager::GetArgFlags(const std::string &name) const { LOCK(cs_args); for (const auto &arg_map : m_available_args) { const auto search = arg_map.second.find(name); if (search != arg_map.second.end()) { return search->second.m_flags; } } return std::nullopt; } fs::path ArgsManager::GetPathArg(std::string arg, const fs::path &default_value) const { if (IsArgNegated(arg)) { return fs::path{}; } std::string path_str = GetArg(arg, ""); if (path_str.empty()) { return default_value; } fs::path result = fs::PathFromString(path_str).lexically_normal(); // Remove trailing slash, if present. return result.has_filename() ? result : result.parent_path(); } const fs::path &ArgsManager::GetBlocksDirPath() const { LOCK(cs_args); fs::path &path = m_cached_blocks_path; // Cache the path to avoid calling fs::create_directories on every call of // this function if (!path.empty()) { return path; } if (IsArgSet("-blocksdir")) { path = fs::absolute(GetPathArg("-blocksdir")); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDataDirBase(); } path /= fs::PathFromString(BaseParams().DataDir()); path /= "blocks"; fs::create_directories(path); return path; } const fs::path &ArgsManager::GetDataDir(bool net_specific) const { LOCK(cs_args); fs::path &path = net_specific ? m_cached_network_datadir_path : m_cached_datadir_path; // Cache the path to avoid calling fs::create_directories on every call of // this function if (!path.empty()) { return path; } const fs::path datadir{GetPathArg("-datadir")}; if (!datadir.empty()) { path = fs::absolute(datadir); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDefaultDataDir(); } if (!fs::exists(path)) { fs::create_directories(path / "wallets"); } if (net_specific && !BaseParams().DataDir().empty()) { path /= fs::PathFromString(BaseParams().DataDir()); if (!fs::exists(path)) { fs::create_directories(path / "wallets"); } } return path; } void ArgsManager::ClearPathCache() { LOCK(cs_args); m_cached_datadir_path = fs::path(); m_cached_network_datadir_path = fs::path(); m_cached_blocks_path = fs::path(); } std::vector<std::string> ArgsManager::GetArgs(const std::string &strArg) const { std::vector<std::string> result; for (const util::SettingsValue &value : GetSettingsList(strArg)) { result.push_back(value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str()); } return result; } bool ArgsManager::IsArgSet(const std::string &strArg) const { return !GetSetting(strArg).isNull(); } bool ArgsManager::InitSettings(std::string &error) { if (!GetSettingsPath()) { return true; // Do nothing if settings file disabled. } std::vector<std::string> errors; if (!ReadSettingsFile(&errors)) { error = strprintf("Failed loading settings file:\n- %s\n", Join(errors, "\n- ")); return false; } if (!WriteSettingsFile(&errors)) { error = strprintf("Failed saving settings file:\n- %s\n", Join(errors, "\n- ")); return false; } return true; } bool ArgsManager::GetSettingsPath(fs::path *filepath, bool temp) const { fs::path settings = GetPathArg("-settings", fs::path{BITCOIN_SETTINGS_FILENAME}); if (settings.empty()) { return false; } if (filepath) { *filepath = fsbridge::AbsPathJoin(GetDataDirNet(), temp ? settings + ".tmp" : settings); } return true; } static void SaveErrors(const std::vector<std::string> errors, std::vector<std::string> *error_out) { for (const auto &error : errors) { if (error_out) { error_out->emplace_back(error); } else { LogPrintf("%s\n", error); } } } bool ArgsManager::ReadSettingsFile(std::vector<std::string> *errors) { fs::path path; if (!GetSettingsPath(&path, /* temp= */ false)) { return true; // Do nothing if settings file disabled. } LOCK(cs_args); m_settings.rw_settings.clear(); std::vector<std::string> read_errors; if (!util::ReadSettings(path, m_settings.rw_settings, read_errors)) { SaveErrors(read_errors, errors); return false; } for (const auto &setting : m_settings.rw_settings) { std::string section; std::string key = setting.first; // Split setting key into section and argname (void)InterpretOption(section, key, /* value */ {}); if (!GetArgFlags('-' + key)) { LogPrintf("Ignoring unknown rw_settings value %s\n", setting.first); } } return true; } bool ArgsManager::WriteSettingsFile(std::vector<std::string> *errors) const { fs::path path, path_tmp; if (!GetSettingsPath(&path, /* temp= */ false) || !GetSettingsPath(&path_tmp, /* temp= */ true)) { throw std::logic_error("Attempt to write settings file when dynamic " "settings are disabled."); } LOCK(cs_args); std::vector<std::string> write_errors; if (!util::WriteSettings(path_tmp, m_settings.rw_settings, write_errors)) { SaveErrors(write_errors, errors); return false; } if (!RenameOver(path_tmp, path)) { SaveErrors( {strprintf("Failed renaming settings file %s to %s\n", fs::PathToString(path_tmp), fs::PathToString(path))}, errors); return false; } return true; } +util::SettingsValue +ArgsManager::GetPersistentSetting(const std::string &name) const { + LOCK(cs_args); + return util::GetSetting( + m_settings, m_network, name, !UseDefaultSection("-" + name), + /*ignore_nonpersistent=*/true, /*get_chain_name=*/false); +} + bool ArgsManager::IsArgNegated(const std::string &strArg) const { return GetSetting(strArg).isFalse(); } std::string ArgsManager::GetArg(const std::string &strArg, const std::string &strDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToString(value, strDefault); +} + +std::string SettingToString(const util::SettingsValue &value, + const std::string &strDefault) { return value.isNull() ? strDefault : value.isFalse() ? "0" : value.isTrue() ? "1" : value.get_str(); } int64_t ArgsManager::GetIntArg(const std::string &strArg, int64_t nDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToInt(value, nDefault); +} + +int64_t SettingToInt(const util::SettingsValue &value, int64_t nDefault) { return value.isNull() ? nDefault : value.isFalse() ? 0 : value.isTrue() ? 1 : value.isNum() ? value.get_int64() : atoi64(value.get_str()); } bool ArgsManager::GetBoolArg(const std::string &strArg, bool fDefault) const { const util::SettingsValue value = GetSetting(strArg); + return SettingToBool(value, fDefault); +} + +bool SettingToBool(const util::SettingsValue &value, bool fDefault) { return value.isNull() ? fDefault : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); } bool ArgsManager::SoftSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); if (IsArgSet(strArg)) { return false; } ForceSetArg(strArg, strValue); return true; } bool ArgsManager::SoftSetBoolArg(const std::string &strArg, bool fValue) { if (fValue) { return SoftSetArg(strArg, std::string("1")); } else { return SoftSetArg(strArg, std::string("0")); } } void ArgsManager::ForceSetArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); m_settings.forced_settings[SettingName(strArg)] = strValue; } /** * This function is only used for testing purpose so * so we should not worry about element uniqueness and * integrity of the data structure */ void ArgsManager::ForceSetMultiArg(const std::string &strArg, const std::vector<std::string> &values) { LOCK(cs_args); util::SettingsValue value; value.setArray(); for (const std::string &s : values) { value.push_back(s); } m_settings.forced_settings[SettingName(strArg)] = value; } void ArgsManager::AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat) { // Split arg name from its help param size_t eq_index = name.find('='); if (eq_index == std::string::npos) { eq_index = name.size(); } std::string arg_name = name.substr(0, eq_index); LOCK(cs_args); std::map<std::string, Arg> &arg_map = m_available_args[cat]; auto ret = arg_map.emplace( arg_name, Arg{name.substr(eq_index, name.size() - eq_index), help, flags}); // Make sure an insertion actually happened. assert(ret.second); if (flags & ArgsManager::NETWORK_ONLY) { m_network_only_args.emplace(arg_name); } } void ArgsManager::AddHiddenArgs(const std::vector<std::string> &names) { for (const std::string &name : names) { AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN); } } void ArgsManager::ClearForcedArg(const std::string &strArg) { LOCK(cs_args); m_settings.forced_settings.erase(SettingName(strArg)); } std::string ArgsManager::GetHelpMessage() const { const bool show_debug = GetBoolArg("-help-debug", false); std::string usage = ""; LOCK(cs_args); for (const auto &arg_map : m_available_args) { switch (arg_map.first) { case OptionsCategory::OPTIONS: usage += HelpMessageGroup("Options:"); break; case OptionsCategory::CONNECTION: usage += HelpMessageGroup("Connection options:"); break; case OptionsCategory::ZMQ: usage += HelpMessageGroup("ZeroMQ notification options:"); break; case OptionsCategory::DEBUG_TEST: usage += HelpMessageGroup("Debugging/Testing options:"); break; case OptionsCategory::NODE_RELAY: usage += HelpMessageGroup("Node relay options:"); break; case OptionsCategory::BLOCK_CREATION: usage += HelpMessageGroup("Block creation options:"); break; case OptionsCategory::RPC: usage += HelpMessageGroup("RPC server options:"); break; case OptionsCategory::WALLET: usage += HelpMessageGroup("Wallet options:"); break; case OptionsCategory::WALLET_DEBUG_TEST: if (show_debug) { usage += HelpMessageGroup("Wallet debugging/testing options:"); } break; case OptionsCategory::CHAINPARAMS: usage += HelpMessageGroup("Chain selection options:"); break; case OptionsCategory::GUI: usage += HelpMessageGroup("UI Options:"); break; case OptionsCategory::COMMANDS: usage += HelpMessageGroup("Commands:"); break; case OptionsCategory::REGISTER_COMMANDS: usage += HelpMessageGroup("Register Commands:"); break; case OptionsCategory::AVALANCHE: usage += HelpMessageGroup("Avalanche options:"); break; case OptionsCategory::CHRONIK: usage += HelpMessageGroup("Chronik options:"); break; default: break; } // When we get to the hidden options, stop if (arg_map.first == OptionsCategory::HIDDEN) { break; } for (const auto &arg : arg_map.second) { if (show_debug || !(arg.second.m_flags & ArgsManager::DEBUG_ONLY)) { std::string name; if (arg.second.m_help_param.empty()) { name = arg.first; } else { name = arg.first + arg.second.m_help_param; } usage += HelpMessageOpt(name, arg.second.m_help_text); } } } return usage; } bool HelpRequested(const ArgsManager &args) { return args.IsArgSet("-?") || args.IsArgSet("-h") || args.IsArgSet("-help") || args.IsArgSet("-help-debug"); } void SetupHelpOptions(ArgsManager &args) { args.AddArg("-?", "Print this help message and exit", false, OptionsCategory::OPTIONS); args.AddHiddenArgs({"-h", "-help"}); } static const int screenWidth = 79; static const int optIndent = 2; static const int msgIndent = 7; std::string HelpMessageGroup(const std::string &message) { return std::string(message) + std::string("\n\n"); } std::string HelpMessageOpt(const std::string &option, const std::string &message) { return std::string(optIndent, ' ') + std::string(option) + std::string("\n") + std::string(msgIndent, ' ') + FormatParagraph(message, screenWidth - msgIndent, msgIndent) + std::string("\n\n"); } static std::string FormatException(const std::exception *pex, const char *pszThread) { #ifdef WIN32 char pszModule[MAX_PATH] = ""; GetModuleFileNameA(nullptr, pszModule, sizeof(pszModule)); #else const char *pszModule = "bitcoin"; #endif if (pex) { return strprintf("EXCEPTION: %s \n%s \n%s in %s \n", typeid(*pex).name(), pex->what(), pszModule, pszThread); } else { return strprintf("UNKNOWN EXCEPTION \n%s in %s \n", pszModule, pszThread); } } void PrintExceptionContinue(const std::exception *pex, const char *pszThread) { std::string message = FormatException(pex, pszThread); LogPrintf("\n\n************************\n%s\n", message); tfm::format(std::cerr, "\n\n************************\n%s\n", message); } fs::path GetDefaultDataDir() { // Windows: C:\Users\Username\AppData\Roaming\Bitcoin // macOS: ~/Library/Application Support/Bitcoin // Unix-like: ~/.bitcoin #ifdef WIN32 // Windows return GetSpecialFolderPath(CSIDL_APPDATA) / "Bitcoin"; #else fs::path pathRet; char *pszHome = getenv("HOME"); if (pszHome == nullptr || strlen(pszHome) == 0) { pathRet = fs::path("/"); } else { pathRet = fs::path(pszHome); } #ifdef MAC_OSX // macOS return pathRet / "Library/Application Support/Bitcoin"; #else // Unix-like return pathRet / ".bitcoin"; #endif #endif } bool CheckDataDirOption() { const fs::path datadir{gArgs.GetPathArg("-datadir")}; return datadir.empty() || fs::is_directory(fs::absolute(datadir)); } fs::path GetConfigFile(const std::string &confPath) { return AbsPathForConfigVal(fs::PathFromString(confPath), false); } static bool GetConfigOptions(std::istream &stream, const std::string &filepath, std::string &error, std::vector<std::pair<std::string, std::string>> &options, std::list<SectionInfo> §ions) { std::string str, prefix; std::string::size_type pos; int linenr = 1; while (std::getline(stream, str)) { bool used_hash = false; if ((pos = str.find('#')) != std::string::npos) { str = str.substr(0, pos); used_hash = true; } const static std::string pattern = " \t\r\n"; str = TrimString(str, pattern); if (!str.empty()) { if (*str.begin() == '[' && *str.rbegin() == ']') { const std::string section = str.substr(1, str.size() - 2); sections.emplace_back(SectionInfo{section, filepath, linenr}); prefix = section + '.'; } else if (*str.begin() == '-') { error = strprintf( "parse error on line %i: %s, options in configuration file " "must be specified without leading -", linenr, str); return false; } else if ((pos = str.find('=')) != std::string::npos) { std::string name = prefix + TrimString(str.substr(0, pos), pattern); std::string value = TrimString(str.substr(pos + 1), pattern); if (used_hash && name.find("rpcpassword") != std::string::npos) { error = strprintf( "parse error on line %i, using # in rpcpassword can be " "ambiguous and should be avoided", linenr); return false; } options.emplace_back(name, value); if ((pos = name.rfind('.')) != std::string::npos && prefix.length() <= pos) { sections.emplace_back( SectionInfo{name.substr(0, pos), filepath, linenr}); } } else { error = strprintf("parse error on line %i: %s", linenr, str); if (str.size() >= 2 && str.substr(0, 2) == "no") { error += strprintf(", if you intended to specify a negated " "option, use %s=1 instead", str); } return false; } } ++linenr; } return true; } bool ArgsManager::ReadConfigStream(std::istream &stream, const std::string &filepath, std::string &error, bool ignore_invalid_keys) { LOCK(cs_args); std::vector<std::pair<std::string, std::string>> options; if (!GetConfigOptions(stream, filepath, error, options, m_config_sections)) { return false; } for (const std::pair<std::string, std::string> &option : options) { std::string section; std::string key = option.first; util::SettingsValue value = InterpretOption(section, key, option.second); std::optional<unsigned int> flags = GetArgFlags('-' + key); if (flags) { if (!CheckValid(key, value, *flags, error)) { return false; } m_settings.ro_config[section][key].push_back(value); } else { if (ignore_invalid_keys) { LogPrintf("Ignoring unknown configuration value %s\n", option.first); } else { error = strprintf("Invalid configuration value %s", option.first.c_str()); return false; } } } return true; } bool ArgsManager::ReadConfigFiles(std::string &error, bool ignore_invalid_keys) { { LOCK(cs_args); m_settings.ro_config.clear(); m_config_sections.clear(); } const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); std::ifstream stream{GetConfigFile(confPath)}; // ok to not have a config file if (stream.good()) { if (!ReadConfigStream(stream, confPath, error, ignore_invalid_keys)) { return false; } // `-includeconf` cannot be included in the command line arguments // except as `-noincludeconf` (which indicates that no included conf // file should be used). bool use_conf_file{true}; { LOCK(cs_args); if (auto *includes = util::FindKey(m_settings.command_line_options, "includeconf")) { // ParseParameters() fails if a non-negated -includeconf is // passed on the command-line assert(util::SettingsSpan(*includes).last_negated()); use_conf_file = false; } } if (use_conf_file) { std::string chain_id = GetChainName(); std::vector<std::string> conf_file_names; auto add_includes = [&](const std::string &network, size_t skip = 0) { size_t num_values = 0; LOCK(cs_args); if (auto *section = util::FindKey(m_settings.ro_config, network)) { if (auto *values = util::FindKey(*section, "includeconf")) { for (size_t i = std::max( skip, util::SettingsSpan(*values).negated()); i < values->size(); ++i) { conf_file_names.push_back((*values)[i].get_str()); } num_values = values->size(); } } return num_values; }; // We haven't set m_network yet (that happens in SelectParams()), so // manually check for network.includeconf args. const size_t chain_includes = add_includes(chain_id); const size_t default_includes = add_includes({}); for (const std::string &conf_file_name : conf_file_names) { std::ifstream conf_file_stream{GetConfigFile(conf_file_name)}; if (conf_file_stream.good()) { if (!ReadConfigStream(conf_file_stream, conf_file_name, error, ignore_invalid_keys)) { return false; } LogPrintf("Included configuration file %s\n", conf_file_name); } else { error = "Failed to include configuration file " + conf_file_name; return false; } } // Warn about recursive -includeconf conf_file_names.clear(); add_includes(chain_id, /* skip= */ chain_includes); add_includes({}, /* skip= */ default_includes); std::string chain_id_final = GetChainName(); if (chain_id_final != chain_id) { // Also warn about recursive includeconf for the chain that was // specified in one of the includeconfs add_includes(chain_id_final); } for (const std::string &conf_file_name : conf_file_names) { tfm::format(std::cerr, "warning: -includeconf cannot be used from " "included files; ignoring -includeconf=%s\n", conf_file_name); } } } // If datadir is changed in .conf file: gArgs.ClearPathCache(); if (!CheckDataDirOption()) { error = strprintf("specified data directory \"%s\" does not exist.", GetArg("-datadir", "").c_str()); return false; } return true; } std::string ArgsManager::GetChainName() const { auto get_net = [&](const std::string &arg) { LOCK(cs_args); util::SettingsValue value = - util::GetSetting(m_settings, /* section= */ "", SettingName(arg), - /* ignore_default_section_config= */ false, - /* get_chain_name= */ true); + util::GetSetting(m_settings, /*section=*/"", SettingName(arg), + /*ignore_default_section_config=*/false, + /*ignore_nonpersistent=*/false, + /*get_chain_name=*/true); return value.isNull() ? false : value.isBool() ? value.get_bool() : InterpretBool(value.get_str()); }; const bool fRegTest = get_net("-regtest"); const bool fTestNet = get_net("-testnet"); const bool is_chain_arg_set = IsArgSet("-chain"); if (int(is_chain_arg_set) + int(fRegTest) + int(fTestNet) > 1) { throw std::runtime_error("Invalid combination of -regtest, -testnet " "and -chain. Can use at most one."); } if (fRegTest) { return CBaseChainParams::REGTEST; } if (fTestNet) { return CBaseChainParams::TESTNET; } return GetArg("-chain", CBaseChainParams::MAIN); } bool ArgsManager::UseDefaultSection(const std::string &arg) const { return m_network == CBaseChainParams::MAIN || m_network_only_args.count(arg) == 0; } util::SettingsValue ArgsManager::GetSetting(const std::string &arg) const { LOCK(cs_args); return util::GetSetting(m_settings, m_network, SettingName(arg), !UseDefaultSection(arg), - /* get_chain_name= */ false); + /*ignore_nonpersistent=*/false, + /*get_chain_name=*/false); } std::vector<util::SettingsValue> ArgsManager::GetSettingsList(const std::string &arg) const { LOCK(cs_args); return util::GetSettingsList(m_settings, m_network, SettingName(arg), !UseDefaultSection(arg)); } void ArgsManager::logArgsPrefix( const std::string &prefix, const std::string §ion, const std::map<std::string, std::vector<util::SettingsValue>> &args) const { std::string section_str = section.empty() ? "" : "[" + section + "] "; for (const auto &arg : args) { for (const auto &value : arg.second) { std::optional<unsigned int> flags = GetArgFlags('-' + arg.first); if (flags) { std::string value_str = (*flags & SENSITIVE) ? "****" : value.write(); LogPrintf("%s %s%s=%s\n", prefix, section_str, arg.first, value_str); } } } } void ArgsManager::LogArgs() const { LOCK(cs_args); for (const auto §ion : m_settings.ro_config) { logArgsPrefix("Config file arg:", section.first, section.second); } for (const auto &setting : m_settings.rw_settings) { LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write()); } logArgsPrefix("Command-line arg:", "", m_settings.command_line_options); } bool RenameOver(fs::path src, fs::path dest) { #ifdef WIN32 return MoveFileExW(src.wstring().c_str(), dest.wstring().c_str(), MOVEFILE_REPLACE_EXISTING) != 0; #else int rc = std::rename(src.c_str(), dest.c_str()); return (rc == 0); #endif /* WIN32 */ } /** * Ignores exceptions thrown by create_directories if the requested * directory exists. Specifically handles case where path p exists, but it * wasn't possible for the user to write to the parent directory. */ bool TryCreateDirectories(const fs::path &p) { try { return fs::create_directories(p); } catch (const fs::filesystem_error &) { if (!fs::exists(p) || !fs::is_directory(p)) { throw; } } // create_directory didn't create the directory, it had to have existed // already. return false; } bool FileCommit(FILE *file) { // harmless if redundantly called if (fflush(file) != 0) { LogPrintf("%s: fflush failed: %d\n", __func__, errno); return false; } #ifdef WIN32 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); if (FlushFileBuffers(hFile) == 0) { LogPrintf("%s: FlushFileBuffers failed: %d\n", __func__, GetLastError()); return false; } #else #if defined(HAVE_FDATASYNC) // Ignore EINVAL for filesystems that don't support sync if (fdatasync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fdatasync failed: %d\n", __func__, errno); return false; } #elif defined(MAC_OSX) && defined(F_FULLFSYNC) // Manpage says "value other than -1" is returned on success if (fcntl(fileno(file), F_FULLFSYNC, 0) == -1) { LogPrintf("%s: fcntl F_FULLFSYNC failed: %d\n", __func__, errno); return false; } #else if (fsync(fileno(file)) != 0 && errno != EINVAL) { LogPrintf("%s: fsync failed: %d\n", __func__, errno); return false; } #endif #endif return true; } bool TruncateFile(FILE *file, unsigned int length) { #if defined(WIN32) return _chsize(_fileno(file), length) == 0; #else return ftruncate(fileno(file), length) == 0; #endif } /** * This function tries to raise the file descriptor limit to the requested * number. It returns the actual file descriptor limit (which may be more or * less than nMinFD) */ int RaiseFileDescriptorLimit(int nMinFD) { #if defined(WIN32) return 8192; #else struct rlimit limitFD; if (getrlimit(RLIMIT_NOFILE, &limitFD) != -1) { if (limitFD.rlim_cur < (rlim_t)nMinFD) { limitFD.rlim_cur = nMinFD; if (limitFD.rlim_cur > limitFD.rlim_max) { limitFD.rlim_cur = limitFD.rlim_max; } setrlimit(RLIMIT_NOFILE, &limitFD); getrlimit(RLIMIT_NOFILE, &limitFD); } return limitFD.rlim_cur; } // getrlimit failed, assume it's fine. return nMinFD; #endif } /** * This function tries to make a particular range of a file allocated * (corresponding to disk space) it is advisory, and the range specified in the * arguments will never contain live data. */ void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length) { #if defined(WIN32) // Windows-specific version. HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(file)); LARGE_INTEGER nFileSize; int64_t nEndPos = (int64_t)offset + length; nFileSize.u.LowPart = nEndPos & 0xFFFFFFFF; nFileSize.u.HighPart = nEndPos >> 32; SetFilePointerEx(hFile, nFileSize, 0, FILE_BEGIN); SetEndOfFile(hFile); #elif defined(MAC_OSX) // OSX specific version // NOTE: Contrary to other OS versions, the OSX version assumes that // NOTE: offset is the size of the file. fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; // mac os fst_length takes the number of free bytes to allocate, // not the desired file size fst.fst_length = length; fst.fst_bytesalloc = 0; if (fcntl(fileno(file), F_PREALLOCATE, &fst) == -1) { fst.fst_flags = F_ALLOCATEALL; fcntl(fileno(file), F_PREALLOCATE, &fst); } ftruncate(fileno(file), static_cast<off_t>(offset) + length); #elif defined(HAVE_POSIX_FALLOCATE) // Version using posix_fallocate off_t nEndPos = (off_t)offset + length; posix_fallocate(fileno(file), 0, nEndPos); #else // Fallback version // TODO: just write one byte per block static const char buf[65536] = {}; if (fseek(file, offset, SEEK_SET)) { return; } while (length > 0) { unsigned int now = 65536; if (length < now) { now = length; } // Allowed to fail; this function is advisory anyway. fwrite(buf, 1, now, file); length -= now; } #endif } #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate) { WCHAR pszPath[MAX_PATH] = L""; if (SHGetSpecialFolderPathW(nullptr, pszPath, nFolder, fCreate)) { return fs::path(pszPath); } LogPrintf( "SHGetSpecialFolderPathW() failed, could not obtain requested path.\n"); return fs::path(""); } #endif #ifndef WIN32 std::string ShellEscape(const std::string &arg) { std::string escaped = arg; ReplaceAll(escaped, "'", "'\"'\"'"); return "'" + escaped + "'"; } #endif #if defined(HAVE_SYSTEM) void runCommand(const std::string &strCommand) { if (strCommand.empty()) { return; } #ifndef WIN32 int nErr = ::system(strCommand.c_str()); #else int nErr = ::_wsystem( std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t>() .from_bytes(strCommand) .c_str()); #endif if (nErr) { LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); } } #endif void SetupEnvironment() { #ifdef HAVE_MALLOPT_ARENA_MAX // glibc-specific: On 32-bit systems set the number of arenas to 1. By // default, since glibc 2.10, the C library will create up to two heap // arenas per core. This is known to cause excessive virtual address space // usage in our usage. Work around it by setting the maximum number of // arenas to 1. if (sizeof(void *) == 4) { mallopt(M_ARENA_MAX, 1); } #endif // On most POSIX systems (e.g. Linux, but not BSD) the environment's locale may // be invalid, in which case the "C.UTF-8" locale is used as fallback. #if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && \ !defined(__OpenBSD__) try { // Raises a runtime error if current locale is invalid. std::locale(""); } catch (const std::runtime_error &) { setenv("LC_ALL", "C.UTF-8", 1); } #elif defined(WIN32) // Set the default input/output charset is utf-8 SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); #endif } bool SetupNetworking() { #ifdef WIN32 // Initialize Windows Sockets. WSADATA wsadata; int ret = WSAStartup(MAKEWORD(2, 2), &wsadata); if (ret != NO_ERROR || LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wVersion) != 2) { return false; } #endif return true; } int GetNumCores() { return std::thread::hardware_concurrency(); } std::string CopyrightHolders(const std::string &strPrefix) { return strPrefix + strprintf(_(COPYRIGHT_HOLDERS).translated, COPYRIGHT_HOLDERS_SUBSTITUTION); } // Obtain the application startup time (used for uptime calculation) int64_t GetStartupTime() { return nStartupTime; } fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific) { if (path.is_absolute()) { return path; } return fsbridge::AbsPathJoin( net_specific ? gArgs.GetDataDirNet() : gArgs.GetDataDirBase(), path); } void ScheduleBatchPriority() { #ifdef SCHED_BATCH const static sched_param param{}; const int rc = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m); if (rc != 0) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(rc)); } #endif } namespace util { #ifdef WIN32 WinCmdLineArgs::WinCmdLineArgs() { wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> utf8_cvt; argv = new char *[argc]; args.resize(argc); for (int i = 0; i < argc; i++) { args[i] = utf8_cvt.to_bytes(wargv[i]); argv[i] = &*args[i].begin(); } LocalFree(wargv); } WinCmdLineArgs::~WinCmdLineArgs() { delete[] argv; } std::pair<int, char **> WinCmdLineArgs::get() { return std::make_pair(argc, argv); } #endif } // namespace util diff --git a/src/util/system.h b/src/util/system.h index be7ea92e5..d8fe59867 100644 --- a/src/util/system.h +++ b/src/util/system.h @@ -1,541 +1,551 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. /** * Server/client environment: argument handling, config file parsing, * thread wrappers, startup time */ #ifndef BITCOIN_UTIL_SYSTEM_H #define BITCOIN_UTIL_SYSTEM_H #if defined(HAVE_CONFIG_H) #include <config/bitcoin-config.h> #endif #include <compat.h> #include <compat/assumptions.h> #include <fs.h> #include <logging.h> #include <sync.h> #include <tinyformat.h> #include <util/settings.h> #include <util/time.h> #include <any> #include <cstdint> #include <exception> #include <map> #include <optional> #include <set> #include <string> #include <utility> #include <vector> // Application startup time (used for uptime calculation) int64_t GetStartupTime(); extern const char *const BITCOIN_CONF_FILENAME; extern const char *const BITCOIN_SETTINGS_FILENAME; void SetupEnvironment(); bool SetupNetworking(); template <typename... Args> bool error(const char *fmt, const Args &...args) { LogPrintf("ERROR: %s\n", tfm::format(fmt, args...)); return false; } void PrintExceptionContinue(const std::exception *pex, const char *pszThread); bool FileCommit(FILE *file); bool TruncateFile(FILE *file, unsigned int length); int RaiseFileDescriptorLimit(int nMinFD); void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length); [[nodiscard]] bool RenameOver(fs::path src, fs::path dest); bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only = false); void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name); bool DirIsWritable(const fs::path &directory); bool CheckDiskSpace(const fs::path &dir, uint64_t additional_bytes = 0); /** * Get the size of a file by scanning it. * * @param[in] path The file path * @param[in] max Stop seeking beyond this limit * @return The file size or max */ std::streampos GetFileSize(const char *path, std::streamsize max = std::numeric_limits<std::streamsize>::max()); /** * Release all directory locks. This is used for unit testing only, at runtime * the global destructor will take care of the locks. */ void ReleaseDirectoryLocks(); bool TryCreateDirectories(const fs::path &p); fs::path GetDefaultDataDir(); // Return true if -datadir option points to a valid directory or is not // specified. bool CheckDataDirOption(); fs::path GetConfigFile(const std::string &confPath); #ifdef WIN32 fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true); #endif #ifndef WIN32 std::string ShellEscape(const std::string &arg); #endif #if defined(HAVE_SYSTEM) void runCommand(const std::string &strCommand); #endif [[nodiscard]] bool ParseKeyValue(std::string &key, std::string &val); /** * Most paths passed as configuration arguments are treated as relative to * the datadir if they are not absolute. * * @param path The path to be conditionally prefixed with datadir. * @param net_specific Use network specific datadir variant * @return The normalized path. */ fs::path AbsPathForConfigVal(const fs::path &path, bool net_specific = true); inline bool IsSwitchChar(char c) { #ifdef WIN32 return c == '-' || c == '/'; #else return c == '-'; #endif } enum class OptionsCategory { OPTIONS, CONNECTION, WALLET, WALLET_DEBUG_TEST, ZMQ, DEBUG_TEST, CHAINPARAMS, NODE_RELAY, BLOCK_CREATION, RPC, GUI, COMMANDS, REGISTER_COMMANDS, AVALANCHE, // Always the last option to avoid printing these in the help HIDDEN, // Hide Chronik for now CHRONIK, }; struct SectionInfo { std::string m_name; std::string m_file; int m_line; }; +std::string SettingToString(const util::SettingsValue &, const std::string &); +int64_t SettingToInt(const util::SettingsValue &, int64_t); +bool SettingToBool(const util::SettingsValue &, bool); + class ArgsManager { public: enum Flags { // Boolean options can accept negation syntax -noOPTION or -noOPTION=1 ALLOW_BOOL = 0x01, ALLOW_INT = 0x02, ALLOW_STRING = 0x04, ALLOW_ANY = ALLOW_BOOL | ALLOW_INT | ALLOW_STRING, DEBUG_ONLY = 0x100, /* Some options would cause cross-contamination if values for * mainnet were used while running on regtest/testnet (or vice-versa). * Setting them as NETWORK_ONLY ensures that sharing a config file * between mainnet and regtest/testnet won't cause problems due to these * parameters by accident. */ NETWORK_ONLY = 0x200, // This argument's value is sensitive (such as a password). SENSITIVE = 0x400, }; protected: struct Arg { std::string m_help_param; std::string m_help_text; unsigned int m_flags; }; mutable RecursiveMutex cs_args; util::Settings m_settings GUARDED_BY(cs_args); std::string m_network GUARDED_BY(cs_args); std::set<std::string> m_network_only_args GUARDED_BY(cs_args); std::map<OptionsCategory, std::map<std::string, Arg>> m_available_args GUARDED_BY(cs_args); std::list<SectionInfo> m_config_sections GUARDED_BY(cs_args); mutable fs::path m_cached_blocks_path GUARDED_BY(cs_args); mutable fs::path m_cached_datadir_path GUARDED_BY(cs_args); mutable fs::path m_cached_network_datadir_path GUARDED_BY(cs_args); [[nodiscard]] bool ReadConfigStream(std::istream &stream, const std::string &filepath, std::string &error, bool ignore_invalid_keys = false); /** * Returns true if settings values from the default section should be used, * depending on the current network and whether the setting is * network-specific. */ bool UseDefaultSection(const std::string &arg) const EXCLUSIVE_LOCKS_REQUIRED(cs_args); /** * Get setting value. * * Result will be null if setting was unset, true if "-setting" argument was * passed false if "-nosetting" argument was passed, and a string if a * "-setting=value" argument was passed. */ util::SettingsValue GetSetting(const std::string &arg) const; /** * Get list of setting values. */ std::vector<util::SettingsValue> GetSettingsList(const std::string &arg) const; public: ArgsManager(); ~ArgsManager(); /** * Select the network in use */ void SelectConfigNetwork(const std::string &network); [[nodiscard]] bool ParseParameters(int argc, const char *const argv[], std::string &error); [[nodiscard]] bool ReadConfigFiles(std::string &error, bool ignore_invalid_keys = false); /** * Log warnings for options in m_section_only_args when they are specified * in the default section but not overridden on the command line or in a * network-specific section in the config file. */ const std::set<std::string> GetUnsuitableSectionOnlyArgs() const; /** * Log warnings for unrecognized section names in the config file. */ const std::list<SectionInfo> GetUnrecognizedSections() const; /** * Get blocks directory path * * @return Blocks path which is network specific */ const fs::path &GetBlocksDirPath() const; /** * Get data directory path * * @return Absolute path on success, otherwise an empty path when a * non-directory path would be returned * @post Returned directory path is created unless it is empty */ const fs::path &GetDataDirBase() const { return GetDataDir(false); } /** * Get data directory path with appended network identifier * * @return Absolute path on success, otherwise an empty path when a * non-directory path would be returned * @post Returned directory path is created unless it is empty */ const fs::path &GetDataDirNet() const { return GetDataDir(true); } /** * Clear cached directory paths */ void ClearPathCache(); /** * Return a vector of strings of the given argument * * @param strArg Argument to get (e.g. "-foo") * @return command-line arguments */ std::vector<std::string> GetArgs(const std::string &strArg) const; /** * Return true if the given argument has been manually set. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument has been set */ bool IsArgSet(const std::string &strArg) const; /** * Return true if the argument was originally passed as a negated option, * i.e. -nofoo. * * @param strArg Argument to get (e.g. "-foo") * @return true if the argument was passed negated */ bool IsArgNegated(const std::string &strArg) const; /** * Return string argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param strDefault (e.g. "1") * @return command-line argument or default value */ std::string GetArg(const std::string &strArg, const std::string &strDefault) const; /** * Return path argument or default value. * * @param arg Argument to get a path from (e.g., "-datadir", "-blocksdir" * or "-walletdir") * @param default_value Optional default value to return instead of the * empty path. * @return normalized path if argument is set, with redundant "." and ".." * path components and trailing separators removed (see patharg unit * test for examples or implementation for details). If argument is * empty or not set, default_value is returned unchanged. */ fs::path GetPathArg(std::string arg, const fs::path &default_value = {}) const; /** * Return integer argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param nDefault (e.g. 1) * @return command-line argument (0 if invalid number) or default value */ int64_t GetIntArg(const std::string &strArg, int64_t nDefault) const; /** * Return boolean argument or default value. * * @param strArg Argument to get (e.g. "-foo") * @param fDefault (true or false) * @return command-line argument or default value */ bool GetBoolArg(const std::string &strArg, bool fDefault) const; /** * Set an argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param strValue Value (e.g. "1") * @return true if argument gets set, false if it already had a value */ bool SoftSetArg(const std::string &strArg, const std::string &strValue); /** * Set a boolean argument if it doesn't already have a value. * * @param strArg Argument to set (e.g. "-foo") * @param fValue Value (e.g. false) * @return true if argument gets set, false if it already had a value */ bool SoftSetBoolArg(const std::string &strArg, bool fValue); // Forces an arg setting. Called by SoftSetArg() if the arg hasn't already // been set. Also called directly in testing. void ForceSetArg(const std::string &strArg, const std::string &strValue); // Forces a multi arg setting, used only in testing void ForceSetMultiArg(const std::string &strArg, const std::vector<std::string> &values); /** * Looks for -regtest, -testnet and returns the appropriate BIP70 chain * name. * @return CBaseChainParams::MAIN by default; raises runtime error if an * invalid combination is given. */ std::string GetChainName() const; /** * Add argument */ void AddArg(const std::string &name, const std::string &help, unsigned int flags, const OptionsCategory &cat); /** * Remove a forced arg setting, used only in testing. */ void ClearForcedArg(const std::string &strArg); /** * Add many hidden arguments */ void AddHiddenArgs(const std::vector<std::string> &args); /** * Clear available arguments */ void ClearArgs() { LOCK(cs_args); m_available_args.clear(); m_network_only_args.clear(); } /** * Get the help string */ std::string GetHelpMessage() const; /** * Return Flags for known arg. * Return std::nullopt for unknown arg. */ std::optional<unsigned int> GetArgFlags(const std::string &name) const; /** * Read and update settings file with saved settings. This needs to be * called after SelectParams() because the settings file location is * network-specific. */ bool InitSettings(std::string &error); /** * Get settings file path, or return false if read-write settings were * disabled with -nosettings. */ bool GetSettingsPath(fs::path *filepath = nullptr, bool temp = false) const; /** * Read settings file. Push errors to vector, or log them if null. */ bool ReadSettingsFile(std::vector<std::string> *errors = nullptr); /** * Write settings file. Push errors to vector, or log them if null. */ bool WriteSettingsFile(std::vector<std::string> *errors = nullptr) const; + /** + * Get current setting from config file or read/write settings file, + * ignoring nonpersistent command line or forced settings values. + */ + util::SettingsValue GetPersistentSetting(const std::string &name) const; + /** * Access settings with lock held. */ template <typename Fn> void LockSettings(Fn &&fn) { LOCK(cs_args); fn(m_settings); } /** * Log the config file options and the command line arguments, * useful for troubleshooting. */ void LogArgs() const; private: /** * Get data directory path * * @param net_specific Append network identifier to the returned path * @return Absolute path on success, otherwise an empty path when a * non-directory path would be returned * @post Returned directory path is created unless it is empty */ const fs::path &GetDataDir(bool net_specific) const; // Helper function for LogArgs(). void logArgsPrefix(const std::string &prefix, const std::string §ion, const std::map<std::string, std::vector<util::SettingsValue>> &args) const; }; extern ArgsManager gArgs; /** * @return true if help has been requested via a command-line arg */ bool HelpRequested(const ArgsManager &args); /** Add help options to the args manager */ void SetupHelpOptions(ArgsManager &args); /** * Format a string to be used as group of options in help messages. * * @param message Group name (e.g. "RPC server options:") * @return the formatted string */ std::string HelpMessageGroup(const std::string &message); /** * Format a string to be used as option description in help messages. * * @param option Option message (e.g. "-rpcuser=<user>") * @param message Option description (e.g. "Username for JSON-RPC connections") * @return the formatted string */ std::string HelpMessageOpt(const std::string &option, const std::string &message); /** * Return the number of cores available on the current system. * @note This does count virtual cores, such as those provided by * HyperThreading. */ int GetNumCores(); std::string CopyrightHolders(const std::string &strPrefix); /** * On platforms that support it, tell the kernel the calling thread is * CPU-intensive and non-interactive. See SCHED_BATCH in sched(7) for details. * */ void ScheduleBatchPriority(); namespace util { //! Simplification of std insertion template <typename Tdst, typename Tsrc> inline void insert(Tdst &dst, const Tsrc &src) { dst.insert(dst.begin(), src.begin(), src.end()); } template <typename TsetT, typename Tsrc> inline void insert(std::set<TsetT> &dst, const Tsrc &src) { dst.insert(src.begin(), src.end()); } /** * Helper function to access the contained object of a std::any instance. * Returns a pointer to the object if passed instance has a value and the type * matches, nullptr otherwise. */ template <typename T> T *AnyPtr(const std::any &any) noexcept { T *const *ptr = std::any_cast<T *>(&any); return ptr ? *ptr : nullptr; } #ifdef WIN32 class WinCmdLineArgs { public: WinCmdLineArgs(); ~WinCmdLineArgs(); std::pair<int, char **> get(); private: int argc; char **argv; std::vector<std::string> args; }; #endif } // namespace util #endif // BITCOIN_UTIL_SYSTEM_H