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. The two line-number columns refer to before and after.
src/common/args.cpp
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
#include <cstdint>
|
|
28
28
|
#include <cstdlib>
|
|
29
29
|
#include <cstring>
|
|
30
|
+
#include <fstream>
|
|
30
31
|
#include <map>
|
|
31
32
|
#include <optional>
|
|
32
33
|
#include <stdexcept>
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
|
|
37
38
|
const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
|
|
38
39
|
const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
|
|
40
|
+
const char * const BITCOIN_RW_CONF_FILENAME = "bitcoin_rw.conf";
|
|
39
41
|
|
|
40
42
|
ArgsManager gArgs;
|
|
41
43
|
|
|
@@ -531,6 +533,7 @@ std::optional<bool> SettingToBool(const common::SettingsValue& value)
|
|
|
531
533
|
{
|
|
532
534
|
if (value.isNull()) return std::nullopt;
|
|
533
535
|
if (value.isBool()) return value.get_bool();
|
|
536
|
+
if (value.isNum()) return InterpretBool(value.getValStr());
|
|
534
537
|
return InterpretBool(value.get_str());
|
|
535
538
|
}
|
|
536
539
|
|
|
@@ -696,6 +699,9 @@ std::string ArgsManager::GetHelpMessage() const
|
|
|
696
699
|
case OptionsCategory::CLI_COMMANDS:
|
|
697
700
|
usage += HelpMessageGroup("CLI Commands:");
|
|
698
701
|
break;
|
|
702
|
+
case OptionsCategory::STATS:
|
|
703
|
+
usage += HelpMessageGroup("Statistic options:");
|
|
704
|
+
break;
|
|
699
705
|
default:
|
|
700
706
|
break;
|
|
701
707
|
}
|
|
@@ -809,6 +815,12 @@ void ArgsManager::SetConfigFilePath(fs::path path)
|
|
|
809
815
|
m_config_path = path;
|
|
810
816
|
}
|
|
811
817
|
|
|
818
|
+
fs::path ArgsManager::GetRWConfigFilePath() const
|
|
819
|
+
{
|
|
820
|
+
LOCK(cs_args);
|
|
821
|
+
return *Assert(m_rwconf_path);
|
|
822
|
+
}
|
|
823
|
+
|
|
812
824
|
ChainType ArgsManager::GetChainType() const
|
|
813
825
|
{
|
|
814
826
|
std::variant<ChainType, std::string> arg = GetChainArg();
|
|
@@ -900,5 +912,240 @@ void ArgsManager::LogArgs() const
|
|
|
900
912
|
for (const auto& setting : m_settings.rw_settings) {
|
|
901
913
|
LogInfo("Setting file arg: %s = %s\n", setting.first, setting.second.write());
|
|
902
914
|
}
|
|
915
|
+
logArgsPrefix("R/W config file arg:", "", m_settings.rw_config);
|
|
903
916
|
logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
|
|
904
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
|
+
}
|