v31.1-roots.1 · Commit 7: [PATCH 07/17] feat(rpc): preserve Roots operator interfaces on v31
src/common/args.cpp
Before: src/common/args.cpp · After: src/common/args.cpp
File metadata
index 563015dc3977e35c1b240bfec96782aad781856f..8963ab589eb3804a64324c4a825799a293bb741e 100644
Download original patch · Release notes
Diff rows 1–282 of 282. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.
src/common/args.cpp
CHANGED
|
@@ -27,6 +27,7 @@
|
|
| 27 |
#include <cstdint>
|
| 28 |
#include <cstdlib>
|
| 29 |
#include <cstring>
|
|
|
|
| 30 |
#include <map>
|
| 31 |
#include <optional>
|
| 32 |
#include <stdexcept>
|
|
@@ -36,6 +37,7 @@
|
|
| 36 |
|
| 37 |
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
|
| 38 |
const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
|
|
|
|
| 39 |
|
| 40 |
ArgsManager gArgs;
|
| 41 |
|
|
@@ -531,6 +533,7 @@ std::optional<bool> SettingToBool(const common::SettingsValue& value)
|
|
| 531 |
{
|
| 532 |
if (value.isNull()) return std::nullopt;
|
| 533 |
if (value.isBool()) return value.get_bool();
|
|
|
|
| 534 |
return InterpretBool(value.get_str());
|
| 535 |
}
|
| 536 |
|
|
@@ -696,6 +699,9 @@ std::string ArgsManager::GetHelpMessage() const
|
|
| 696 |
case OptionsCategory::CLI_COMMANDS:
|
| 697 |
usage += HelpMessageGroup("CLI Commands:");
|
| 698 |
break;
|
|
|
|
|
|
|
|
|
|
| 699 |
default:
|
| 700 |
break;
|
| 701 |
}
|
|
@@ -809,6 +815,12 @@ void ArgsManager::SetConfigFilePath(fs::path path)
|
|
| 809 |
m_config_path = path;
|
| 810 |
}
|
| 811 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 812 |
ChainType ArgsManager::GetChainType() const
|
| 813 |
{
|
| 814 |
std::variant<ChainType, std::string> arg = GetChainArg();
|
|
@@ -900,5 +912,240 @@ void ArgsManager::LogArgs() const
|
|
| 900 |
for (const auto& setting : m_settings.rw_settings) {
|
| 901 |
LogInfo("Setting file arg: %s = %s\n", setting.first, setting.second.write());
|
| 902 |
}
|
|
|
|
| 903 |
logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
|
| 904 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
#include <cstdint>
|
| 28 |
#include <cstdlib>
|
| 29 |
#include <cstring>
|
| 30 |
+
#include <fstream>
|
| 31 |
#include <map>
|
| 32 |
#include <optional>
|
| 33 |
#include <stdexcept>
|
|
|
|
| 37 |
|
| 38 |
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
|
| 39 |
const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
|
| 40 |
+
const char * const BITCOIN_RW_CONF_FILENAME = "bitcoin_rw.conf";
|
| 41 |
|
| 42 |
ArgsManager gArgs;
|
| 43 |
|
|
|
|
| 533 |
{
|
| 534 |
if (value.isNull()) return std::nullopt;
|
| 535 |
if (value.isBool()) return value.get_bool();
|
| 536 |
+
if (value.isNum()) return InterpretBool(value.getValStr());
|
| 537 |
return InterpretBool(value.get_str());
|
| 538 |
}
|
| 539 |
|
|
|
|
| 699 |
case OptionsCategory::CLI_COMMANDS:
|
| 700 |
usage += HelpMessageGroup("CLI Commands:");
|
| 701 |
break;
|
| 702 |
+
case OptionsCategory::STATS:
|
| 703 |
+
usage += HelpMessageGroup("Statistic options:");
|
| 704 |
+
break;
|
| 705 |
default:
|
| 706 |
break;
|
| 707 |
}
|
|
|
|
| 815 |
m_config_path = path;
|
| 816 |
}
|
| 817 |
|
| 818 |
+
fs::path ArgsManager::GetRWConfigFilePath() const
|
| 819 |
+
{
|
| 820 |
+
LOCK(cs_args);
|
| 821 |
+
return *Assert(m_rwconf_path);
|
| 822 |
+
}
|
| 823 |
+
|
| 824 |
ChainType ArgsManager::GetChainType() const
|
| 825 |
{
|
| 826 |
std::variant<ChainType, std::string> arg = GetChainArg();
|
|
|
|
| 912 |
for (const auto& setting : m_settings.rw_settings) {
|
| 913 |
LogInfo("Setting file arg: %s = %s\n", setting.first, setting.second.write());
|
| 914 |
}
|
| 915 |
+
logArgsPrefix("R/W config file arg:", "", m_settings.rw_config);
|
| 916 |
logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
|
| 917 |
}
|
| 918 |
+
|
| 919 |
+
namespace {
|
| 920 |
+
|
| 921 |
+
// Like std::getline, but includes the EOL character in the result.
|
| 922 |
+
// A failed read must never turn an incomplete config into a replacement file.
|
| 923 |
+
bool getline_with_eol(std::istream& stream, std::string& result)
|
| 924 |
+
{
|
| 925 |
+
const auto read_char = [&stream] {
|
| 926 |
+
const auto current_char{stream.get()};
|
| 927 |
+
if (stream.bad() || (stream.fail() && !stream.eof())) {
|
| 928 |
+
throw std::ios_base::failure("Failed to read R/W config stream");
|
| 929 |
+
}
|
| 930 |
+
return current_char;
|
| 931 |
+
};
|
| 932 |
+
auto current_char{read_char()};
|
| 933 |
+
if (current_char == std::char_traits<char>::eof()) return false;
|
| 934 |
+
result.clear();
|
| 935 |
+
result.push_back(char(current_char));
|
| 936 |
+
while (current_char != '\n') {
|
| 937 |
+
current_char = read_char();
|
| 938 |
+
if (current_char == std::char_traits<char>::eof()) break;
|
| 939 |
+
result.push_back(char(current_char));
|
| 940 |
+
}
|
| 941 |
+
return true;
|
| 942 |
+
}
|
| 943 |
+
|
| 944 |
+
const char * const ModifyRWConfigFile_ws_chars = " \t\r\n";
|
| 945 |
+
|
| 946 |
+
void ModifyRWConfigFile_SanityCheck(const std::string& s)
|
| 947 |
+
{
|
| 948 |
+
if (s.empty()) {
|
| 949 |
+
// Dereferencing .begin or .rbegin below is invalid unless the string has at least one character.
|
| 950 |
+
return;
|
| 951 |
+
}
|
| 952 |
+
|
| 953 |
+
static const char * const newline_chars = "\r\n";
|
| 954 |
+
static std::string ws_chars(ModifyRWConfigFile_ws_chars);
|
| 955 |
+
if (s.find_first_of(newline_chars) != std::string::npos) {
|
| 956 |
+
throw std::invalid_argument("New-line in config name/value");
|
| 957 |
+
}
|
| 958 |
+
if (ws_chars.find(*s.begin()) != std::string::npos || ws_chars.find(*s.rbegin()) != std::string::npos) {
|
| 959 |
+
throw std::invalid_argument("Config name/value has leading/trailing whitespace");
|
| 960 |
+
}
|
| 961 |
+
}
|
| 962 |
+
|
| 963 |
+
void ModifyRWConfigFile_WriteRemaining(std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change, std::set<std::string>& setFound)
|
| 964 |
+
{
|
| 965 |
+
for (const auto& setting_pair : settings_to_change) {
|
| 966 |
+
const std::string& key = setting_pair.first;
|
| 967 |
+
const std::string& val = setting_pair.second;
|
| 968 |
+
if (setFound.find(key) != setFound.end()) {
|
| 969 |
+
continue;
|
| 970 |
+
}
|
| 971 |
+
setFound.insert(key);
|
| 972 |
+
ModifyRWConfigFile_SanityCheck(key);
|
| 973 |
+
ModifyRWConfigFile_SanityCheck(val);
|
| 974 |
+
stream_out << key << "=" << val << "\n";
|
| 975 |
+
}
|
| 976 |
+
}
|
| 977 |
+
} // namespace
|
| 978 |
+
|
| 979 |
+
void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change)
|
| 980 |
+
{
|
| 981 |
+
static const char * const ws_chars = ModifyRWConfigFile_ws_chars;
|
| 982 |
+
std::set<std::string> setFound;
|
| 983 |
+
std::string s, lineend, linebegin, key;
|
| 984 |
+
std::string::size_type n, n2;
|
| 985 |
+
bool inside_group = false, have_eof_nl = true;
|
| 986 |
+
std::map<std::string, std::string>::const_iterator iterCS;
|
| 987 |
+
size_t lineno = 0;
|
| 988 |
+
while (getline_with_eol(stream_in, s)) {
|
| 989 |
+
++lineno;
|
| 990 |
+
|
| 991 |
+
have_eof_nl = (!s.empty()) && (*s.rbegin() == '\n');
|
| 992 |
+
n = s.find('#');
|
| 993 |
+
const bool has_comment = (n != std::string::npos);
|
| 994 |
+
if (!has_comment) {
|
| 995 |
+
n = s.size();
|
| 996 |
+
}
|
| 997 |
+
if (n > 0) {
|
| 998 |
+
n2 = s.find_last_not_of(ws_chars, n - 1);
|
| 999 |
+
if (n2 != std::string::npos) {
|
| 1000 |
+
n = n2 + 1;
|
| 1001 |
+
}
|
| 1002 |
+
}
|
| 1003 |
+
n2 = s.find_first_not_of(ws_chars);
|
| 1004 |
+
if (n2 == std::string::npos || n2 >= n) {
|
| 1005 |
+
// Blank or comment-only line
|
| 1006 |
+
stream_out << s;
|
| 1007 |
+
continue;
|
| 1008 |
+
}
|
| 1009 |
+
lineend = s.substr(n);
|
| 1010 |
+
linebegin = s.substr(0, n2);
|
| 1011 |
+
s = s.substr(n2, n - n2);
|
| 1012 |
+
|
| 1013 |
+
// It is impossible for s to be empty here, due to the blank line check above
|
| 1014 |
+
if (*s.begin() == '[' && *s.rbegin() == ']') {
|
| 1015 |
+
// We don't use sections, so we could possibly just write out the rest of the file - but we need to check for unparsable lines, so we just set a flag to ignore settings from here on
|
| 1016 |
+
ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
|
| 1017 |
+
inside_group = true;
|
| 1018 |
+
key.clear();
|
| 1019 |
+
|
| 1020 |
+
stream_out << linebegin << s << lineend;
|
| 1021 |
+
continue;
|
| 1022 |
+
}
|
| 1023 |
+
|
| 1024 |
+
n = s.find('=');
|
| 1025 |
+
if (n == std::string::npos) {
|
| 1026 |
+
// Bad line; this causes boost to throw an exception when parsing, so we comment out the entire file
|
| 1027 |
+
// A valid final line without a newline leaves eof/fail set. The
|
| 1028 |
+
// reader already rejected I/O failures, so clear only that EOF state.
|
| 1029 |
+
if (stream_in.eof() && !stream_in.bad()) stream_in.clear();
|
| 1030 |
+
stream_in.seekg(0, std::ios_base::beg);
|
| 1031 |
+
stream_out.seekp(0, std::ios_base::beg);
|
| 1032 |
+
if (!(stream_in.good() && stream_out.good())) {
|
| 1033 |
+
throw std::ios_base::failure("Failed to rewind (to comment out existing file)");
|
| 1034 |
+
}
|
| 1035 |
+
// First, write out all the settings we intend to set
|
| 1036 |
+
setFound.clear();
|
| 1037 |
+
ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
|
| 1038 |
+
// We then define a category to ensure new settings get added before the invalid stuff
|
| 1039 |
+
stream_out << "[INVALID]\n";
|
| 1040 |
+
// Then, describe the problem in a comment
|
| 1041 |
+
stream_out << "# Error parsing line " << lineno << ": " << s << "\n";
|
| 1042 |
+
// Finally, dump the rest of the file commented out
|
| 1043 |
+
while (getline_with_eol(stream_in, s)) {
|
| 1044 |
+
stream_out << "#" << s;
|
| 1045 |
+
}
|
| 1046 |
+
return;
|
| 1047 |
+
}
|
| 1048 |
+
|
| 1049 |
+
if (!inside_group) {
|
| 1050 |
+
// We don't support/use groups, so once we're inside key is always null to avoid setting anything
|
| 1051 |
+
n2 = s.find_last_not_of(ws_chars, n - 1);
|
| 1052 |
+
if (n2 == std::string::npos) {
|
| 1053 |
+
n2 = n - 1;
|
| 1054 |
+
} else {
|
| 1055 |
+
++n2;
|
| 1056 |
+
}
|
| 1057 |
+
key = s.substr(0, n2);
|
| 1058 |
+
}
|
| 1059 |
+
if ((!key.empty()) && (iterCS = settings_to_change.find(key)) != settings_to_change.end() && setFound.find(key) == setFound.end()) {
|
| 1060 |
+
// This is the key we want to change
|
| 1061 |
+
const std::string& val = iterCS->second;
|
| 1062 |
+
setFound.insert(key);
|
| 1063 |
+
ModifyRWConfigFile_SanityCheck(val);
|
| 1064 |
+
if (has_comment) {
|
| 1065 |
+
// Rather than change a commented line, comment it out entirely (the existing comment may relate to the value) and replace it
|
| 1066 |
+
stream_out << key << "=" << val << "\n";
|
| 1067 |
+
linebegin.insert(linebegin.begin(), '#');
|
| 1068 |
+
} else {
|
| 1069 |
+
// Just modify the value in-line otherwise
|
| 1070 |
+
n2 = s.find_first_not_of(ws_chars, n + 1);
|
| 1071 |
+
if (n2 == std::string::npos) {
|
| 1072 |
+
n2 = n + 1;
|
| 1073 |
+
}
|
| 1074 |
+
s = s.substr(0, n2) + val;
|
| 1075 |
+
}
|
| 1076 |
+
}
|
| 1077 |
+
stream_out << linebegin << s << lineend;
|
| 1078 |
+
}
|
| 1079 |
+
if (setFound.size() < settings_to_change.size()) {
|
| 1080 |
+
if (!have_eof_nl) {
|
| 1081 |
+
stream_out << "\n";
|
| 1082 |
+
}
|
| 1083 |
+
ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
|
| 1084 |
+
}
|
| 1085 |
+
}
|
| 1086 |
+
|
| 1087 |
+
void ArgsManager::ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, const bool also_settings_json)
|
| 1088 |
+
{
|
| 1089 |
+
LOCK(cs_args);
|
| 1090 |
+
fs::path rwconf_path{GetRWConfigFilePath()};
|
| 1091 |
+
fs::path rwconf_new_path{rwconf_path};
|
| 1092 |
+
rwconf_new_path += ".new";
|
| 1093 |
+
try {
|
| 1094 |
+
fs::remove(rwconf_new_path);
|
| 1095 |
+
std::ofstream streamRWConfigOut;
|
| 1096 |
+
streamRWConfigOut.exceptions(std::ios::failbit | std::ios::badbit);
|
| 1097 |
+
streamRWConfigOut.open(rwconf_new_path.std_path(), std::ios_base::out | std::ios_base::trunc);
|
| 1098 |
+
if (fs::exists(rwconf_path)) {
|
| 1099 |
+
std::ifstream streamRWConfig(rwconf_path.std_path());
|
| 1100 |
+
if (!streamRWConfig.is_open()) throw std::ios_base::failure("Failed to read R/W config file");
|
| 1101 |
+
::ModifyRWConfigStream(streamRWConfig, streamRWConfigOut, settings_to_change);
|
| 1102 |
+
} else {
|
| 1103 |
+
std::istringstream streamIn;
|
| 1104 |
+
::ModifyRWConfigStream(streamIn, streamRWConfigOut, settings_to_change);
|
| 1105 |
+
}
|
| 1106 |
+
streamRWConfigOut.close();
|
| 1107 |
+
} catch (...) {
|
| 1108 |
+
fs::remove(rwconf_new_path);
|
| 1109 |
+
throw;
|
| 1110 |
+
}
|
| 1111 |
+
if (!RenameOver(rwconf_new_path, rwconf_path)) {
|
| 1112 |
+
fs::remove(rwconf_new_path);
|
| 1113 |
+
throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path)));
|
| 1114 |
+
}
|
| 1115 |
+
for (const auto& setting_change : settings_to_change) {
|
| 1116 |
+
m_settings.rw_config[setting_change.first] = {setting_change.second};
|
| 1117 |
+
}
|
| 1118 |
+
if (also_settings_json && !IsArgNegated("-settings")) {
|
| 1119 |
+
// Also save to settings.json for Core (0.21+) compatibility
|
| 1120 |
+
for (const auto& setting_change : settings_to_change) {
|
| 1121 |
+
m_settings.rw_settings[setting_change.first] = setting_change.second;
|
| 1122 |
+
}
|
| 1123 |
+
WriteSettingsFile();
|
| 1124 |
+
}
|
| 1125 |
+
if (settings_to_change.count("prune")) {
|
| 1126 |
+
m_rwconf_had_prune_option = true;
|
| 1127 |
+
}
|
| 1128 |
+
}
|
| 1129 |
+
|
| 1130 |
+
void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, const bool also_settings_json)
|
| 1131 |
+
{
|
| 1132 |
+
std::map<std::string, std::string> settings_to_change;
|
| 1133 |
+
settings_to_change[setting_to_change] = new_value;
|
| 1134 |
+
ModifyRWConfigFile(settings_to_change, also_settings_json);
|
| 1135 |
+
}
|
| 1136 |
+
|
| 1137 |
+
void ArgsManager::EraseRWConfigFile()
|
| 1138 |
+
{
|
| 1139 |
+
LOCK(cs_args);
|
| 1140 |
+
fs::path rwconf_path{GetRWConfigFilePath()};
|
| 1141 |
+
if (!fs::exists(rwconf_path)) {
|
| 1142 |
+
return;
|
| 1143 |
+
}
|
| 1144 |
+
fs::path rwconf_reset_path = rwconf_path;
|
| 1145 |
+
rwconf_reset_path += ".reset";
|
| 1146 |
+
if (!RenameOver(rwconf_path, rwconf_reset_path)) {
|
| 1147 |
+
if (!fs::remove(rwconf_path)) {
|
| 1148 |
+
throw std::ios_base::failure(strprintf("Failed to remove %s", fs::PathToString(rwconf_path)));
|
| 1149 |
+
}
|
| 1150 |
+
}
|
| 1151 |
+
}
|