diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index b523e3100..349026e25 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -1,233 +1,233 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-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. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::function G_TRANSLATION_FUN = nullptr; /* Introduction text for doxygen: */ /*! \mainpage Developer documentation * * \section intro_sec Introduction * * This is the developer documentation of Bitcoin ABC * (https://www.bitcoinabc.org/). Bitcoin ABC is a client for the digital * currency called Bitcoin Cash (https://www.bitcoincash.org/), which enables * instant payments to anyone, anywhere in the world. Bitcoin Cash uses * peer-to-peer technology to operate with no central authority: managing * transactions and issuing money are carried out collectively by the network. * * The software is a community-driven open source project, released under the * MIT license. * * \section Navigation * Use the buttons Namespaces, Classes or * Files at the top of the page to start navigating the code. */ static void WaitForShutdown() { while (!ShutdownRequested()) { MilliSleep(200); } Interrupt(); } ////////////////////////////////////////////////////////////////////////////// // // Start // static bool AppInit(int argc, char *argv[]) { // FIXME: Ideally, we'd like to build the config here, but that's currently // not possible as the whole application has too many global state. However, // this is a first step. auto &config = const_cast(GetConfig()); RPCServer rpcServer; HTTPRPCRequestProcessor httpRPCRequestProcessor(config, rpcServer); InitInterfaces interfaces; interfaces.chain = interfaces::MakeChain(); bool fRet = false; util::ThreadSetInternalName("init"); // // Parameters // // If Qt is used, parameters/bitcoin.conf are parsed in qt/bitcoin.cpp's // main() SetupServerArgs(); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { fprintf(stderr, "Error parsing command line arguments: %s\n", error.c_str()); return false; } // Process help and version before taking care about datadir if (HelpRequested(gArgs) || gArgs.IsArgSet("-version")) { std::string strUsage = PACKAGE_NAME " Daemon version " + FormatFullVersion() + "\n"; if (gArgs.IsArgSet("-version")) { strUsage += FormatParagraph(LicenseInfo()); } else { strUsage += "\nUsage: bitcoind [options] " "Start " PACKAGE_NAME " Daemon\n"; strUsage += "\n" + gArgs.GetHelpMessage(); } fprintf(stdout, "%s", strUsage.c_str()); return true; } try { if (!fs::is_directory(GetDataDir(false))) { fprintf(stderr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "").c_str()); return false; } - if (!gArgs.ReadConfigFiles(error)) { + if (!gArgs.ReadConfigFiles(error, true)) { fprintf(stderr, "Error reading configuration file: %s\n", error.c_str()); return false; } // Check for -testnet or -regtest parameter (Params() calls are only // valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception &e) { fprintf(stderr, "Error: %s\n", e.what()); return false; } // Make sure we create the net-specific data directory early on: if it // is new, this has a side effect of also creating // //wallets/. // // TODO: this should be removed once GetDataDir() no longer creates the // wallets/ subdirectory. // See more info at: // https://reviews.bitcoinabc.org/D3312 GetDataDir(true); // Error out when loose non-argument tokens are encountered on command // line for (int i = 1; i < argc; i++) { if (!IsSwitchChar(argv[i][0])) { fprintf(stderr, "Error: Command line contains unexpected token '%s', " "see bitcoind -h for a list of options.\n", argv[i]); return false; } } // -server defaults to true for bitcoind but not for the GUI so do this // here gArgs.SoftSetBoolArg("-server", true); // Set this early so that parameter interactions go to console InitLogging(); InitParameterInteraction(); if (!AppInitBasicSetup()) { // InitError will have been called with detailed error, which ends // up on console return false; } if (!AppInitParameterInteraction(config)) { // InitError will have been called with detailed error, which ends // up on console return false; } if (!AppInitSanityChecks()) { // InitError will have been called with detailed error, which ends // up on console return false; } if (gArgs.GetBoolArg("-daemon", false)) { #if HAVE_DECL_DAEMON #if defined(MAC_OSX) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #endif fprintf(stdout, "Bitcoin server starting\n"); // Daemonize if (daemon(1, 0)) { // don't chdir (1), do close FDs (0) fprintf(stderr, "Error: daemon() failed: %s\n", strerror(errno)); return false; } #if defined(MAC_OSX) #pragma GCC diagnostic pop #endif #else fprintf( stderr, "Error: -daemon is not supported on this operating system\n"); return false; #endif // HAVE_DECL_DAEMON } // Lock data directory after daemonization if (!AppInitLockDataDirectory()) { // If locking the data directory failed, exit immediately return false; } fRet = AppInitMain(config, rpcServer, httpRPCRequestProcessor, interfaces); } catch (const std::exception &e) { PrintExceptionContinue(&e, "AppInit()"); } catch (...) { PrintExceptionContinue(nullptr, "AppInit()"); } if (!fRet) { Interrupt(); } else { WaitForShutdown(); } Shutdown(interfaces); return fRet; } int main(int argc, char *argv[]) { #ifdef WIN32 util::WinCmdLineArgs winArgs; std::tie(argc, argv) = winArgs.get(); #endif SetupEnvironment(); // Connect bitcoind signal handlers noui_connect(); return (AppInit(argc, argv) ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index 936971d57..bbd8f663c 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -1,324 +1,324 @@ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(HAVE_CONFIG_H) #include #endif #include #include class HTTPRPCRequestProcessor; class CWallet; fs::path GetWalletDir(); std::vector ListWalletDir(); std::vector> GetWallets(); namespace interfaces { class Wallet; namespace { class NodeImpl : public Node { public: NodeImpl() { m_interfaces.chain = MakeChain(); } bool parseParameters(int argc, const char *const argv[], std::string &error) override { return gArgs.ParseParameters(argc, argv, error); } bool readConfigFiles(std::string &error) override { - return gArgs.ReadConfigFiles(error); + return gArgs.ReadConfigFiles(error, true); } bool softSetArg(const std::string &arg, const std::string &value) override { return gArgs.SoftSetArg(arg, value); } bool softSetBoolArg(const std::string &arg, bool value) override { return gArgs.SoftSetBoolArg(arg, value); } void selectParams(const std::string &network) override { SelectParams(network); } std::string getNetwork() override { return Params().NetworkIDString(); } void initLogging() override { InitLogging(); } void initParameterInteraction() override { InitParameterInteraction(); } std::string getWarnings(const std::string &type) override { return GetWarnings(type); } bool baseInitialize(Config &config) override { return AppInitBasicSetup() && AppInitParameterInteraction(config) && AppInitSanityChecks() && AppInitLockDataDirectory(); } bool appInitMain(Config &config, RPCServer &rpcServer, HTTPRPCRequestProcessor &httpRPCRequestProcessor) override { return AppInitMain(config, rpcServer, httpRPCRequestProcessor, m_interfaces); } void appShutdown() override { Interrupt(); Shutdown(m_interfaces); } void startShutdown() override { StartShutdown(); } bool shutdownRequested() override { return ShutdownRequested(); } void mapPort(bool use_upnp) override { if (use_upnp) { StartMapPort(); } else { InterruptMapPort(); StopMapPort(); } } void setupServerArgs() override { return SetupServerArgs(); } bool getProxy(Network net, proxyType &proxy_info) override { return GetProxy(net, proxy_info); } size_t getNodeCount(CConnman::NumConnections flags) override { return g_connman ? g_connman->GetNodeCount(flags) : 0; } bool getNodesStats(NodesStats &stats) override { stats.clear(); if (g_connman) { std::vector stats_temp; g_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. TRY_LOCK(::cs_main, lockMain); if (lockMain) { for (auto &node_stats : stats) { std::get<1>(node_stats) = GetNodeStateStats(std::get<0>(node_stats).nodeid, std::get<2>(node_stats)); } } return true; } return false; } bool getBanned(banmap_t &banmap) override { if (g_banman) { g_banman->GetBanned(banmap); return true; } return false; } bool ban(const CNetAddr &net_addr, BanReason reason, int64_t ban_time_offset) override { if (g_banman) { g_banman->Ban(net_addr, reason, ban_time_offset); return true; } return false; } bool unban(const CSubNet &ip) override { if (g_banman) { g_banman->Unban(ip); return true; } return false; } bool disconnect(const CNetAddr &net_addr) override { if (g_connman) { return g_connman->DisconnectNode(net_addr); } return false; } bool disconnect(NodeId id) override { if (g_connman) { return g_connman->DisconnectNode(id); } return false; } int64_t getTotalBytesRecv() override { return g_connman ? g_connman->GetTotalBytesRecv() : 0; } int64_t getTotalBytesSent() override { return g_connman ? g_connman->GetTotalBytesSent() : 0; } size_t getMempoolSize() override { return g_mempool.size(); } size_t getMempoolDynamicUsage() override { return g_mempool.DynamicMemoryUsage(); } bool getHeaderTip(int &height, int64_t &block_time) override { LOCK(::cs_main); if (::pindexBestHeader) { height = ::pindexBestHeader->nHeight; block_time = ::pindexBestHeader->GetBlockTime(); return true; } return false; } int getNumBlocks() override { LOCK(::cs_main); return ::ChainActive().Height(); } int64_t getLastBlockTime() override { LOCK(::cs_main); if (::ChainActive().Tip()) { return ::ChainActive().Tip()->GetBlockTime(); } // Genesis block's time of current network return Params().GenesisBlock().GetBlockTime(); } double getVerificationProgress() override { const CBlockIndex *tip; { LOCK(::cs_main); tip = ::ChainActive().Tip(); } return GuessVerificationProgress(Params().TxData(), tip); } bool isInitialBlockDownload() override { return IsInitialBlockDownload(); } bool getReindex() override { return ::fReindex; } bool getImporting() override { return ::fImporting; } void setNetworkActive(bool active) override { if (g_connman) { g_connman->SetNetworkActive(active); } } bool getNetworkActive() override { return g_connman && g_connman->GetNetworkActive(); } Amount getMaxTxFee() override { return ::maxTxFee; } CFeeRate estimateSmartFee() override { return g_mempool.estimateFee(); } CFeeRate getDustRelayFee() override { return ::dustRelayFee; } UniValue executeRpc(Config &config, const std::string &command, const UniValue ¶ms, const std::string &uri) override { JSONRPCRequest req; req.params = params; req.strMethod = command; req.URI = uri; return ::tableRPC.execute(config, req); } std::vector 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 ::pcoinsTip->GetCoin(output, coin); } std::string getWalletDir() override { return GetWalletDir().string(); } std::vector listWalletDir() override { std::vector paths; for (auto &path : ListWalletDir()) { paths.push_back(path.string()); } return paths; } std::vector> getWallets() override { std::vector> wallets; for (const std::shared_ptr &wallet : GetWallets()) { wallets.emplace_back(MakeWallet(wallet)); } return wallets; } std::unique_ptr handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage_connect(fn)); } std::unique_ptr handleMessageBox(MessageBoxFn fn) override { return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn)); } std::unique_ptr handleQuestion(QuestionFn fn) override { return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn)); } std::unique_ptr handleShowProgress(ShowProgressFn fn) override { return MakeHandler(::uiInterface.ShowProgress_connect(fn)); } std::unique_ptr handleLoadWallet(LoadWalletFn fn) override { return MakeHandler(::uiInterface.LoadWallet_connect( [fn](std::shared_ptr wallet) { fn(MakeWallet(wallet)); })); } std::unique_ptr handleNotifyNumConnectionsChanged( NotifyNumConnectionsChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNumConnectionsChanged_connect(fn)); } std::unique_ptr handleNotifyNetworkActiveChanged( NotifyNetworkActiveChangedFn fn) override { return MakeHandler( ::uiInterface.NotifyNetworkActiveChanged_connect(fn)); } std::unique_ptr handleNotifyAlertChanged(NotifyAlertChangedFn fn) override { return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn)); } std::unique_ptr handleBannedListChanged(BannedListChangedFn fn) override { return MakeHandler(::uiInterface.BannedListChanged_connect(fn)); } std::unique_ptr handleNotifyBlockTip(NotifyBlockTipFn fn) override { return MakeHandler(::uiInterface.NotifyBlockTip_connect( [fn](bool initial_download, const CBlockIndex *block) { fn(initial_download, block->nHeight, block->GetBlockTime(), GuessVerificationProgress(Params().TxData(), block)); })); } std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn fn) override { return MakeHandler(::uiInterface.NotifyHeaderTip_connect( [fn](bool initial_download, const CBlockIndex *block) { fn(initial_download, block->nHeight, block->GetBlockTime(), GuessVerificationProgress(Params().TxData(), block)); })); } InitInterfaces m_interfaces; }; } // namespace std::unique_ptr MakeNode() { return std::make_unique(); } } // namespace interfaces diff --git a/src/util/system.cpp b/src/util/system.cpp index 638ee2fd0..be95a5978 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -1,1371 +1,1376 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include #include #include #include #if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)) #include #include #endif #ifndef WIN32 // for posix_fallocate #ifdef __linux__ #ifdef _POSIX_C_SOURCE #undef _POSIX_C_SOURCE #endif #define _POSIX_C_SOURCE 200112L #endif // __linux__ #include #include #include #include #include #else #ifdef _MSC_VER #pragma warning(disable : 4786) #pragma warning(disable : 4804) #pragma warning(disable : 4805) #pragma warning(disable : 4717) #endif #ifdef _WIN32_WINNT #undef _WIN32_WINNT #endif #define _WIN32_WINNT 0x0501 #ifdef _WIN32_IE #undef _WIN32_IE #endif #define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX #endif #include #include /* for _commit */ #include #include #endif #ifdef HAVE_MALLOPT_ARENA_MAX #include #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_PID_FILENAME = "bitcoind.pid"; ArgsManager gArgs; /** * 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> dir_locks; /** Mutex to protect dir_locks. */ static std::mutex cs_dir_locks; bool LockDirectory(const fs::path &directory, const std::string lockfile_name, bool probe_only) { std::lock_guard ulock(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(pathLockFile.string())) { 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(pathLockFile); if (!lock->TryLock()) { return error("Error while attempting to lock directory %s: %s", directory.string(), lock->GetReason()); } if (!probe_only) { // Lock successful and we're not just probing, put it into the map dir_locks.emplace(pathLockFile.string(), std::move(lock)); } return true; } void UnlockDirectory(const fs::path &directory, const std::string &lockfile_name) { std::lock_guard lock(cs_dir_locks); dir_locks.erase((directory / lockfile_name).string()); } void ReleaseDirectoryLocks() { std::lock_guard ulock(cs_dir_locks); dir_locks.clear(); } bool DirIsWritable(const fs::path &directory) { fs::path tmpFile = directory / fs::unique_path(); 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; } /** * 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); } /** Internal helper functions for ArgsManager */ class ArgsManagerHelper { public: typedef std::map> MapArgs; /** Determine whether to use config settings in the default section, * See also comments around ArgsManager::ArgsManager() below. */ static inline bool UseDefaultSection(const ArgsManager &am, const std::string &arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) { return (am.m_network == CBaseChainParams::MAIN || am.m_network_only_args.count(arg) == 0); } /** Convert regular argument into the network-specific setting */ static inline std::string NetworkArg(const ArgsManager &am, const std::string &arg) { assert(arg.length() > 1 && arg[0] == '-'); return "-" + am.m_network + "." + arg.substr(1); } /** Find arguments in a map and add them to a vector */ static inline void AddArgs(std::vector &res, const MapArgs &map_args, const std::string &arg) { auto it = map_args.find(arg); if (it != map_args.end()) { res.insert(res.end(), it->second.begin(), it->second.end()); } } /** * Return true/false if an argument is set in a map, and also * return the first (or last) of the possibly multiple values it has */ static inline std::pair GetArgHelper(const MapArgs &map_args, const std::string &arg, bool getLast = false) { auto it = map_args.find(arg); if (it == map_args.end() || it->second.empty()) { return std::make_pair(false, std::string()); } if (getLast) { return std::make_pair(true, it->second.back()); } else { return std::make_pair(true, it->second.front()); } } /** * Get the string value of an argument, returning a pair of a boolean * indicating the argument was found, and the value for the argument * if it was found (or the empty string if not found). */ static inline std::pair GetArg(const ArgsManager &am, const std::string &arg) { LOCK(am.cs_args); std::pair found_result(false, std::string()); // We pass "true" to GetArgHelper in order to return the last // argument value seen from the command line (so "bitcoind -foo=bar // -foo=baz" gives GetArg(am,"foo")=={true,"baz"} found_result = GetArgHelper(am.m_override_args, arg, true); if (found_result.first) { return found_result; } // But in contrast we return the first argument seen in a config file, // so "foo=bar \n foo=baz" in the config file gives // GetArg(am,"foo")={true,"bar"} if (!am.m_network.empty()) { found_result = GetArgHelper(am.m_config_args, NetworkArg(am, arg)); if (found_result.first) { return found_result; } } if (UseDefaultSection(am, arg)) { found_result = GetArgHelper(am.m_config_args, arg); if (found_result.first) { return found_result; } } return found_result; } /* Special test for -testnet and -regtest args, because we don't want to be * confused by craziness like "[regtest] testnet=1" */ static inline bool GetNetBoolArg(const ArgsManager &am, const std::string &net_arg) EXCLUSIVE_LOCKS_REQUIRED(am.cs_args) { std::pair found_result(false, std::string()); found_result = GetArgHelper(am.m_override_args, net_arg, true); if (!found_result.first) { found_result = GetArgHelper(am.m_config_args, net_arg, true); if (!found_result.first) { // not set return false; } } // is set, so evaluate return InterpretBool(found_result.second); } }; /** * 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 true, indicating the caller should clear the args vector to indicate * a negated option. * * If there was a double negative, it removes "no" from the key, sets the value * to "1" and returns false. * * If there was no "no", it leaves key and value untouched and returns false. * * 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 bool InterpretNegatedOption(std::string &key, std::string &val) { assert(key[0] == '-'); size_t option_index = key.find('.'); if (option_index == std::string::npos) { option_index = 1; } else { ++option_index; } if (key.substr(option_index, 2) == "no") { bool bool_val = InterpretBool(val); key.erase(option_index, 2); if (!bool_val) { // Double negatives like -nofoo=0 are supported (but discouraged) LogPrintf( "Warning: parsed potentially confusing double-negative %s=%s\n", key, val); val = "1"; } else { return true; } } return false; } ArgsManager::ArgsManager() : /* These options would cause cross-contamination if values for mainnet * were used while running on regtest/testnet (or vice-versa). * Setting them as section_only_args ensures that sharing a config file * between mainnet and regtest/testnet won't cause problems due to these * parameters by accident. */ m_network_only_args{ "-addnode", "-connect", "-port", "-bind", "-rpcport", "-rpcbind", "-wallet", } { // nothing to do } void ArgsManager::WarnForSectionOnlyArgs() { LOCK(cs_args); // if there's no section selected, don't worry if (m_network.empty()) { return; } // if it's okay to use the default section for this network, don't worry if (m_network == CBaseChainParams::MAIN) { return; } for (const auto &arg : m_network_only_args) { std::pair found_result; // if this option is overridden it's fine found_result = ArgsManagerHelper::GetArgHelper(m_override_args, arg); if (found_result.first) { continue; } // if there's a network-specific value for this option, it's fine found_result = ArgsManagerHelper::GetArgHelper( m_config_args, ArgsManagerHelper::NetworkArg(*this, arg)); if (found_result.first) { continue; } // if there isn't a default value for this option, it's fine found_result = ArgsManagerHelper::GetArgHelper(m_config_args, arg); if (!found_result.first) { continue; } // otherwise, issue a warning LogPrintf("Warning: Config setting for %s only applied on %s network " "when in [%s] section.\n", arg, m_network, m_network); } } 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 std::transform(key.begin(), key.end(), key.begin(), ::tolower); 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_override_args.clear(); for (int i = 1; i < argc; i++) { std::string key(argv[i]); std::string val; if (!ParseKeyValue(key, val)) { break; } // Check for -nofoo if (InterpretNegatedOption(key, val)) { m_override_args[key].clear(); } else { m_override_args[key].push_back(val); } // Check that the arg is known if (!(IsSwitchChar(key[0]) && key.size() == 1)) { if (!IsArgKnown(key)) { error = strprintf("Invalid parameter %s", key.c_str()); return false; } } } // we do not allow -includeconf from command line, so we clear it here auto it = m_override_args.find("-includeconf"); if (it != m_override_args.end()) { if (it->second.size() > 0) { for (const auto &ic : it->second) { error += "-includeconf cannot be used from commandline; " "-includeconf=" + ic + "\n"; } return false; } } return true; } bool ArgsManager::IsArgKnown(const std::string &key) const { size_t option_index = key.find('.'); std::string arg_no_net; if (option_index == std::string::npos) { arg_no_net = key; } else { arg_no_net = std::string("-") + key.substr(option_index + 1, std::string::npos); } LOCK(cs_args); for (const auto &arg_map : m_available_args) { if (arg_map.second.count(arg_no_net)) { return true; } } return false; } std::vector ArgsManager::GetArgs(const std::string &strArg) const { std::vector result = {}; // special case if (IsArgNegated(strArg)) { return result; } LOCK(cs_args); ArgsManagerHelper::AddArgs(result, m_override_args, strArg); if (!m_network.empty()) { ArgsManagerHelper::AddArgs( result, m_config_args, ArgsManagerHelper::NetworkArg(*this, strArg)); } if (ArgsManagerHelper::UseDefaultSection(*this, strArg)) { ArgsManagerHelper::AddArgs(result, m_config_args, strArg); } return result; } bool ArgsManager::IsArgSet(const std::string &strArg) const { // special case if (IsArgNegated(strArg)) { return true; } return ArgsManagerHelper::GetArg(*this, strArg).first; } bool ArgsManager::IsArgNegated(const std::string &strArg) const { LOCK(cs_args); const auto &ov = m_override_args.find(strArg); if (ov != m_override_args.end()) { return ov->second.empty(); } if (!m_network.empty()) { const auto &cfs = m_config_args.find(ArgsManagerHelper::NetworkArg(*this, strArg)); if (cfs != m_config_args.end()) { return cfs->second.empty(); } } const auto &cf = m_config_args.find(strArg); if (cf != m_config_args.end()) { return cf->second.empty(); } return false; } std::string ArgsManager::GetArg(const std::string &strArg, const std::string &strDefault) const { if (IsArgNegated(strArg)) { return "0"; } std::pair found_res = ArgsManagerHelper::GetArg(*this, strArg); if (found_res.first) { return found_res.second; } return strDefault; } int64_t ArgsManager::GetArg(const std::string &strArg, int64_t nDefault) const { if (IsArgNegated(strArg)) { return 0; } std::pair found_res = ArgsManagerHelper::GetArg(*this, strArg); if (found_res.first) { return atoi64(found_res.second); } return nDefault; } bool ArgsManager::GetBoolArg(const std::string &strArg, bool fDefault) const { if (IsArgNegated(strArg)) { return false; } std::pair found_res = ArgsManagerHelper::GetArg(*this, strArg); if (found_res.first) { return InterpretBool(found_res.second); } return fDefault; } 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_override_args[strArg] = {strValue}; } /** * This function is only used for testing purpose so * so we should not worry about element uniqueness and * integrity of mapMultiArgs data structure */ void ArgsManager::ForceSetMultiArg(const std::string &strArg, const std::string &strValue) { LOCK(cs_args); m_override_args[strArg].push_back(strValue); } void ArgsManager::AddArg(const std::string &name, const std::string &help, const bool debug_only, 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(); } LOCK(cs_args); std::map &arg_map = m_available_args[cat]; auto ret = arg_map.emplace( name.substr(0, eq_index), Arg(name.substr(eq_index, name.size() - eq_index), help, debug_only)); // Make sure an insertion actually happened. assert(ret.second); } void ArgsManager::AddHiddenArgs(const std::vector &names) { for (const std::string &name : names) { AddArg(name, "", false, OptionsCategory::HIDDEN); } } void ArgsManager::ClearArg(const std::string &strArg) { LOCK(cs_args); m_override_args.erase(strArg); m_config_args.erase(strArg); } std::string ArgsManager::GetHelpMessage() const { const bool show_debug = gArgs.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; 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_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"); } 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); fprintf(stderr, "\n\n************************\n%s\n", message.c_str()); } fs::path GetDefaultDataDir() { // Windows < Vista: C:\Documents and Settings\Username\Application Data\Bitcoin // Windows >= Vista: C:\Users\Username\AppData\Roaming\Bitcoin // Mac: ~/Library/Application Support/Bitcoin // Unix: ~/.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 // Mac return pathRet / "Library/Application Support/Bitcoin"; #else // Unix return pathRet / ".bitcoin"; #endif #endif } static fs::path g_blocks_path_cache_net_specific; static fs::path pathCached; static fs::path pathCachedNetSpecific; static CCriticalSection csPathCached; const fs::path &GetBlocksDir() { LOCK(csPathCached); fs::path &path = g_blocks_path_cache_net_specific; // This can be called during exceptions by LogPrintf(), so we cache the // value so we don't have to do memory allocations after that. if (!path.empty()) { return path; } if (gArgs.IsArgSet("-blocksdir")) { path = fs::system_complete(gArgs.GetArg("-blocksdir", "")); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDataDir(false); } path /= BaseParams().DataDir(); path /= "blocks"; fs::create_directories(path); return path; } const fs::path &GetDataDir(bool fNetSpecific) { LOCK(csPathCached); fs::path &path = fNetSpecific ? pathCachedNetSpecific : pathCached; // This can be called during exceptions by LogPrintf(), so we cache the // value so we don't have to do memory allocations after that. if (!path.empty()) { return path; } if (gArgs.IsArgSet("-datadir")) { path = fs::system_complete(gArgs.GetArg("-datadir", "")); if (!fs::is_directory(path)) { path = ""; return path; } } else { path = GetDefaultDataDir(); } if (fNetSpecific) { path /= BaseParams().DataDir(); } if (fs::create_directories(path)) { // This is the first run, create wallets subdirectory too // // TODO: this is an ugly way to create the wallets/ directory and // really shouldn't be done here. Once this is fixed, please // also remove the corresponding line in bitcoind.cpp AppInit. // See more info at: // https://reviews.bitcoinabc.org/D3312 fs::create_directories(path / "wallets"); } return path; } void ClearDatadirCache() { LOCK(csPathCached); pathCached = fs::path(); pathCachedNetSpecific = fs::path(); g_blocks_path_cache_net_specific = fs::path(); } fs::path GetConfigFile(const std::string &confPath) { return AbsPathForConfigVal(fs::path(confPath), false); } static std::string TrimString(const std::string &str, const std::string &pattern) { std::string::size_type front = str.find_first_not_of(pattern); if (front == std::string::npos) { return std::string(); } std::string::size_type end = str.find_last_not_of(pattern); return str.substr(front, end - front + 1); } static bool GetConfigOptions(std::istream &stream, std::string &error, std::vector> &options) { 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() == ']') { prefix = str.substr(1, str.size() - 2) + '.'; } 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 == "rpcpassword") { 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); } 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, std::string &error, bool ignore_invalid_keys) { LOCK(cs_args); std::vector> options; if (!GetConfigOptions(stream, error, options)) { return false; } for (const std::pair &option : options) { std::string strKey = std::string("-") + option.first; std::string strValue = option.second; if (InterpretNegatedOption(strKey, strValue)) { m_config_args[strKey].clear(); } else { m_config_args[strKey].push_back(strValue); } // Check that the arg is known - if (!IsArgKnown(strKey) && !ignore_invalid_keys) { - error = strprintf("Invalid configuration value %s", - option.first.c_str()); - return false; + if (!IsArgKnown(strKey)) { + if (!ignore_invalid_keys) { + error = strprintf("Invalid configuration value %s", + option.first.c_str()); + return false; + } else { + LogPrintf("Ignoring unknown configuration value %s\n", + option.first); + } } } return true; } bool ArgsManager::ReadConfigFiles(std::string &error, bool ignore_invalid_keys) { { LOCK(cs_args); m_config_args.clear(); } const std::string confPath = GetArg("-conf", BITCOIN_CONF_FILENAME); fsbridge::ifstream stream(GetConfigFile(confPath)); // ok to not have a config file if (stream.good()) { if (!ReadConfigStream(stream, error, ignore_invalid_keys)) { return false; } // if there is an -includeconf in the override args, but it is empty, // that means the user passed '-noincludeconf' on the command line, in // which case we should not include anything bool emptyIncludeConf; { LOCK(cs_args); emptyIncludeConf = m_override_args.count("-includeconf") == 0; } if (emptyIncludeConf) { std::string chain_id = GetChainName(); std::vector includeconf(GetArgs("-includeconf")); { // We haven't set m_network yet (that happens in // SelectParams()), so manually check for network.includeconf // args. std::vector includeconf_net( GetArgs(std::string("-") + chain_id + ".includeconf")); includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); } // Remove -includeconf from configuration, so we can warn about // recursion later { LOCK(cs_args); m_config_args.erase("-includeconf"); m_config_args.erase(std::string("-") + chain_id + ".includeconf"); } for (const std::string &to_include : includeconf) { fsbridge::ifstream include_config(GetConfigFile(to_include)); if (include_config.good()) { if (!ReadConfigStream(include_config, error, ignore_invalid_keys)) { return false; } LogPrintf("Included configuration file %s\n", to_include.c_str()); } else { error = "Failed to include configuration file " + to_include; return false; } } // Warn about recursive -includeconf includeconf = GetArgs("-includeconf"); { std::vector includeconf_net( GetArgs(std::string("-") + chain_id + ".includeconf")); includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); 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 includeconf_net = GetArgs(std::string("-") + chain_id_final + ".includeconf"); includeconf.insert(includeconf.end(), includeconf_net.begin(), includeconf_net.end()); } } for (const std::string &to_include : includeconf) { fprintf(stderr, "warning: -includeconf cannot be used from included " "files; ignoring -includeconf=%s\n", to_include.c_str()); } } } // If datadir is changed in .conf file: ClearDatadirCache(); if (!fs::is_directory(GetDataDir(false))) { error = strprintf("specified data directory \"%s\" does not exist.", gArgs.GetArg("-datadir", "").c_str()); return false; } return true; } std::string ArgsManager::GetChainName() const { LOCK(cs_args); bool fRegTest = ArgsManagerHelper::GetNetBoolArg(*this, "-regtest"); bool fTestNet = ArgsManagerHelper::GetNetBoolArg(*this, "-testnet"); if (fTestNet && fRegTest) { throw std::runtime_error( "Invalid combination of -regtest and -testnet."); } if (fRegTest) { return CBaseChainParams::REGTEST; } if (fTestNet) { return CBaseChainParams::TESTNET; } return CBaseChainParams::MAIN; } #ifndef WIN32 fs::path GetPidFile() { return AbsPathForConfigVal( fs::path(gArgs.GetArg("-pid", BITCOIN_PID_FILENAME))); } void CreatePidFile(const fs::path &path, pid_t pid) { FILE *file = fsbridge::fopen(path, "w"); if (file) { fprintf(file, "%d\n", pid); fclose(file); } } #endif bool RenameOver(fs::path src, fs::path dest) { #ifdef WIN32 return MoveFileExA(src.string().c_str(), dest.string().c_str(), MOVEFILE_REPLACE_EXISTING) != 0; #else int rc = std::rename(src.string().c_str(), dest.string().c_str()); return (rc == 0); #endif /* WIN32 */ } /** * Ignores exceptions thrown by Boost's 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(__linux__) || defined(__NetBSD__) // 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 2048; #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. fstore_t fst; fst.fst_flags = F_ALLOCATECONTIG; fst.fst_posmode = F_PEOFPOSMODE; fst.fst_offset = 0; fst.fst_length = (off_t)offset + 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), fst.fst_length); #elif defined(__linux__) // 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) { char pszPath[MAX_PATH] = ""; if (SHGetSpecialFolderPathA(nullptr, pszPath, nFolder, fCreate)) { return fs::path(pszPath); } LogPrintf( "SHGetSpecialFolderPathA() failed, could not obtain requested path.\n"); return fs::path(""); } #endif 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, wchar_t>() .from_bytes(strCommand) .c_str()); #endif if (nErr) { LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr); } } 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" 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", 1); } #elif defined(WIN32) // Set the default input/output charset is utf-8 SetConsoleCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8); #endif // The path locale is lazy initialized and to avoid deinitialization errors // in multithreading environments, it is set explicitly by the main thread. // A dummy locale is used to extract the internal default locale, used by // fs::path, which is then used to explicitly imbue the path. std::locale loc = fs::path::imbue(std::locale::classic()); #ifndef WIN32 fs::path::imbue(loc); #else fs::path::imbue(std::locale(loc, new std::codecvt_utf8_utf16())); #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 boost::thread::physical_concurrency(); } std::string CopyrightHolders(const std::string &strPrefix) { return strPrefix + strprintf(_(COPYRIGHT_HOLDERS), _(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) { return fs::absolute(path, GetDataDir(net_specific)); } int ScheduleBatchPriority() { #ifdef SCHED_BATCH const static sched_param param{}; if (int ret = pthread_setschedparam(pthread_self(), SCHED_BATCH, ¶m)) { LogPrintf("Failed to pthread_setschedparam: %s\n", strerror(errno)); return ret; } return 0; #else return 1; #endif } namespace util { #ifdef WIN32 WinCmdLineArgs::WinCmdLineArgs() { wchar_t **wargv = CommandLineToArgvW(GetCommandLineW(), &argc); std::wstring_convert, 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 WinCmdLineArgs::get() { return std::make_pair(argc, argv); } #endif } // namespace util diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py index 78b4ad9a5..f75947faf 100755 --- a/test/functional/feature_includeconf.py +++ b/test/functional/feature_includeconf.py @@ -1,89 +1,92 @@ #!/usr/bin/env python3 # 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. """Tests the includeconf argument Verify that: 1. adding includeconf to the configuration file causes the includeconf file to be loaded in the correct order. 2. includeconf cannot be used as a command line argument. 3. includeconf cannot be used recursively (ie includeconf can only be used from the base config file). 4. multiple includeconf arguments can be specified in the main config file. """ import os from test_framework.test_framework import BitcoinTestFramework class IncludeConfTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 1 def setup_chain(self): super().setup_chain() # Create additional config files # - tmpdir/node0/relative.conf with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: f.write("uacomment=relative\n") # - tmpdir/node0/relative2.conf with open(os.path.join(self.options.tmpdir, "node0", "relative2.conf"), "w", encoding="utf8") as f: f.write("uacomment=relative2\n") with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f: f.write("uacomment=main\nincludeconf=relative.conf\n") def run_test(self): self.log.info( "-includeconf works from config file. subversion should end with 'main; relative)/'") subversion = self.nodes[0].getnetworkinfo()["subversion"] assert subversion.endswith("main; relative)/") self.log.info("-includeconf cannot be used as command-line arg") self.stop_node(0) self.nodes[0].assert_start_raises_init_error(extra_args=["-includeconf=relative2.conf"], expected_msg="Error parsing command line arguments: -includeconf cannot be used from commandline; -includeconf=relative2.conf") self.log.info( "-includeconf cannot be used recursively. subversion should end with 'main; relative)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f: f.write("includeconf=relative2.conf\n") self.start_node(0) subversion = self.nodes[0].getnetworkinfo()["subversion"] assert subversion.endswith("main; relative)/") self.stop_node( 0, expected_stderr="warning: -includeconf cannot be used from included files; ignoring -includeconf=relative2.conf") self.log.info("-includeconf cannot contain invalid arg") - with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: - f.write("foo=bar\n") - self.nodes[0].assert_start_raises_init_error( - expected_msg="Error reading configuration file: Invalid configuration value foo") + + # Commented out as long as we ignore invalid arguments in configuration files + # with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: + # f.write("foo=bar\n") + # self.nodes[0].assert_start_raises_init_error( + # expected_msg="Error reading configuration file: Invalid configuration value foo") + # See https://github.com/bitcoin/bitcoin/pull/13799 self.log.info("-includeconf cannot be invalid path") os.remove(os.path.join(self.options.tmpdir, "node0", "relative.conf")) self.nodes[0].assert_start_raises_init_error( expected_msg="Error reading configuration file: Failed to include configuration file relative.conf") self.log.info( "multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'") with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f: # Restore initial file contents f.write("uacomment=relative\n") with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f: f.write("includeconf=relative2.conf\n") self.start_node(0) subversion = self.nodes[0].getnetworkinfo()["subversion"] assert subversion.endswith("main; relative; relative2)/") if __name__ == '__main__': IncludeConfTest().main()