Skip to content
Bitcoin Roots

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
+ }