Skip to content
Bitcoin Roots

v29.4-roots.4 · Commit 2: [PATCH 02/37] runtime: preserve shared operator controls

src/common/args.cpp

Before: src/common/args.cpp · After: src/common/args.cpp

File metadata
index 833a0b28bd2fef8e724e8c79efbc402ec97c7c2b..4d5e3cf31ba3bd474688c2e9d7071162fcb35540 100644

Download original patch · Release notes

Diff rows 1–395 of 395. 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
@@ -29,15 +29,18 @@
29
29
  #include <cstdint>
30
30
  #include <cstdlib>
31
31
  #include <cstring>
32
+ #include <fstream>
32
33
  #include <map>
33
34
  #include <optional>
34
35
  #include <stdexcept>
35
36
  #include <string>
37
+ #include <unordered_set>
36
38
  #include <utility>
37
39
  #include <variant>
38
40
 
39
41
  const char * const BITCOIN_CONF_FILENAME = "bitcoin.conf";
40
42
  const char * const BITCOIN_SETTINGS_FILENAME = "settings.json";
43
+ const char * const BITCOIN_RW_CONF_FILENAME = "bitcoin_rw.conf";
41
44
 
42
45
  ArgsManager gArgs;
43
46
 
@@ -115,7 +118,7 @@ std::optional<common::SettingsValue> InterpretValue(const KeyInfo& key, const st
115
118
  }
116
119
  // Double negatives like -nofoo=0 are supported (but discouraged)
117
120
  if (value && !InterpretBool(*value)) {
118
- LogPrintf("Warning: parsed potentially confusing double-negative -%s=%s\n", key.name, *value);
121
+ LogWarning("Parsed potentially confusing double-negative -%s=%s", key.name, *value);
119
122
  return true;
120
123
  }
121
124
  return false;
@@ -394,7 +397,7 @@ static void SaveErrors(const std::vector<std::string> errors, std::vector<std::s
394
397
  if (error_out) {
395
398
  error_out->emplace_back(error);
396
399
  } else {
397
- LogPrintf("%s\n", error);
400
+ LogWarning("%s", error);
398
401
  }
399
402
  }
400
403
  }
@@ -416,7 +419,7 @@ bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors)
416
419
  for (const auto& setting : m_settings.rw_settings) {
417
420
  KeyInfo key = InterpretKey(setting.first); // Split setting key into section and argname
418
421
  if (!GetArgFlags('-' + key.name)) {
419
- LogPrintf("Ignoring unknown rw_settings value %s\n", setting.first);
422
+ LogWarning("Ignoring unknown rw_settings value %s", setting.first);
420
423
  }
421
424
  }
422
425
  return true;
@@ -504,6 +507,25 @@ int64_t SettingToInt(const common::SettingsValue& value, int64_t nDefault)
504
507
  return SettingToInt(value).value_or(nDefault);
505
508
  }
506
509
 
510
+ std::optional<int64_t> ArgsManager::GetFixedPointArg(const std::string& arg, int decimals) const
511
+ {
512
+ const common::SettingsValue value = GetSetting(arg);
513
+ return SettingToFixedPoint(value, decimals);
514
+ }
515
+
516
+ std::optional<int64_t> SettingToFixedPoint(const common::SettingsValue& value, int decimals)
517
+ {
518
+ if (value.isNull()) return std::nullopt;
519
+ if (value.isFalse()) return 0;
520
+ if (value.isTrue()) return 1;
521
+ if (!value.isNum()) value.get_str(); // throws an exception if type is wrong
522
+ int64_t v;
523
+ if (!ParseFixedPoint(value.getValStr(), decimals, &v)) {
524
+ throw std::runtime_error(strprintf("Parse error ('%s')", value.getValStr()));
525
+ }
526
+ return v;
527
+ }
528
+
507
529
  bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const
508
530
  {
509
531
  return GetBoolArg(strArg).value_or(fDefault);
@@ -517,9 +539,21 @@ std::optional<bool> ArgsManager::GetBoolArg(const std::string& strArg) const
517
539
 
518
540
  std::optional<bool> SettingToBool(const common::SettingsValue& value)
519
541
  {
520
- if (value.isNull()) return std::nullopt;
521
- if (value.isBool()) return value.get_bool();
522
- return InterpretBool(value.get_str());
542
+ switch (value.getType()) {
543
+ case UniValue::VNULL:
544
+ return std::nullopt;
545
+ case UniValue::VBOOL:
546
+ return value.get_bool();
547
+ case UniValue::VOBJ:
548
+ case UniValue::VARR:
549
+ // Throws an exception
550
+ value.get_str();
551
+ assert(false);
552
+ case UniValue::VSTR:
553
+ case UniValue::VNUM:
554
+ return InterpretBool(value.getValStr());
555
+ }
556
+ assert(false);
523
557
  }
524
558
 
525
559
  bool SettingToBool(const common::SettingsValue& value, bool fDefault)
@@ -544,9 +578,19 @@ bool ArgsManager::SoftSetBoolArg(const std::string& strArg, bool fValue)
544
578
  }
545
579
 
546
580
  void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strValue)
581
+ {
582
+ ForceSetArgV(strArg, common::SettingsValue{strValue});
583
+ }
584
+
585
+ void ArgsManager::ForceSetArg(const std::string& arg, const int64_t value)
586
+ {
587
+ ForceSetArg(arg, util::ToString(value));
588
+ }
589
+
590
+ void ArgsManager::ForceSetArgV(const std::string& arg, const common::SettingsValue& value)
547
591
  {
548
592
  LOCK(cs_args);
549
- m_settings.forced_settings[SettingName(strArg)] = strValue;
593
+ m_settings.forced_settings[SettingName(arg)] = value;
550
594
  }
551
595
 
552
596
  void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
@@ -582,10 +626,10 @@ void ArgsManager::AddArg(const std::string& name, const std::string& help, unsig
582
626
  }
583
627
  }
584
628
 
585
- void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names)
629
+ void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names, unsigned int flags)
586
630
  {
587
631
  for (const std::string& name : names) {
588
- AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
632
+ AddArg(name, "", flags, OptionsCategory::HIDDEN);
589
633
  }
590
634
  }
591
635
 
@@ -659,6 +703,9 @@ std::string ArgsManager::GetHelpMessage() const
659
703
  case OptionsCategory::CLI_COMMANDS:
660
704
  usage += HelpMessageGroup("CLI Commands:");
661
705
  break;
706
+ case OptionsCategory::STATS:
707
+ usage += HelpMessageGroup("Statistic options:");
708
+ break;
662
709
  default:
663
710
  break;
664
711
  }
@@ -688,8 +735,8 @@ bool HelpRequested(const ArgsManager& args)
688
735
 
689
736
  void SetupHelpOptions(ArgsManager& args)
690
737
  {
691
- args.AddArg("-help", "Print this help message and exit (also -h or -?)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
692
- args.AddHiddenArgs({"-h", "-?"});
738
+ args.AddArg("-help", "Print this help message and exit (also -h or -?)", ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
739
+ args.AddHiddenArgs({"-h", "-?"}, ArgsManager::DISALLOW_NEGATION);
693
740
  }
694
741
 
695
742
  static const int screenWidth = 79;
@@ -709,6 +756,7 @@ std::string HelpMessageOpt(const std::string &option, const std::string &message
709
756
 
710
757
  const std::vector<std::string> TEST_OPTIONS_DOC{
711
758
  "addrman (use deterministic addrman)",
759
+ "reindex_after_failure_noninteractive_yes (When asked for a reindex after failure interactively, simulate as-if answered with 'yes')",
712
760
  "bip94 (enforce BIP94 consensus rules)",
713
761
  };
714
762
 
@@ -771,6 +819,12 @@ void ArgsManager::SetConfigFilePath(fs::path path)
771
819
  m_config_path = path;
772
820
  }
773
821
 
822
+ fs::path ArgsManager::GetRWConfigFilePath() const
823
+ {
824
+ LOCK(cs_args);
825
+ return *Assert(m_rwconf_path);
826
+ }
827
+
774
828
  ChainType ArgsManager::GetChainType() const
775
829
  {
776
830
  std::variant<ChainType, std::string> arg = GetChainArg();
@@ -862,9 +916,234 @@ void ArgsManager::LogArgs() const
862
916
  for (const auto& setting : m_settings.rw_settings) {
863
917
  LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write());
864
918
  }
919
+ logArgsPrefix("R/W config file arg:", "", m_settings.rw_config);
865
920
  logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
866
921
  }
867
922
 
923
+ namespace {
924
+
925
+ // Like std::getline, but includes the EOL character in the result
926
+ bool getline_with_eol(std::istream& stream, std::string& result)
927
+ {
928
+ int current_char;
929
+ current_char = stream.get();
930
+ if (current_char == std::char_traits<char>::eof()) {
931
+ return false;
932
+ }
933
+ result.clear();
934
+ result.push_back(char(current_char));
935
+ while (current_char != '\n') {
936
+ current_char = stream.get();
937
+ if (current_char == std::char_traits<char>::eof()) {
938
+ break;
939
+ }
940
+ result.push_back(char(current_char));
941
+ }
942
+ return true;
943
+ }
944
+
945
+ const char * const ModifyRWConfigFile_ws_chars = " \t\r\n";
946
+
947
+ void ModifyRWConfigFile_SanityCheck(const std::string& s)
948
+ {
949
+ if (s.empty()) {
950
+ // Dereferencing .begin or .rbegin below is invalid unless the string has at least one character.
951
+ return;
952
+ }
953
+
954
+ static const char * const newline_chars = "\r\n";
955
+ static std::string ws_chars(ModifyRWConfigFile_ws_chars);
956
+ if (s.find_first_of(newline_chars) != std::string::npos) {
957
+ throw std::invalid_argument("New-line in config name/value");
958
+ }
959
+ if (ws_chars.find(*s.begin()) != std::string::npos || ws_chars.find(*s.rbegin()) != std::string::npos) {
960
+ throw std::invalid_argument("Config name/value has leading/trailing whitespace");
961
+ }
962
+ }
963
+
964
+ void ModifyRWConfigFile_WriteRemaining(std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change, std::set<std::string>& setFound)
965
+ {
966
+ for (const auto& setting_pair : settings_to_change) {
967
+ const std::string& key = setting_pair.first;
968
+ const std::string& val = setting_pair.second;
969
+ if (setFound.find(key) != setFound.end()) {
970
+ continue;
971
+ }
972
+ setFound.insert(key);
973
+ ModifyRWConfigFile_SanityCheck(key);
974
+ ModifyRWConfigFile_SanityCheck(val);
975
+ stream_out << key << "=" << val << "\n";
976
+ }
977
+ }
978
+ } // namespace
979
+
980
+ void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change)
981
+ {
982
+ static const char * const ws_chars = ModifyRWConfigFile_ws_chars;
983
+ std::set<std::string> setFound;
984
+ std::string s, lineend, linebegin, key;
985
+ std::string::size_type n, n2;
986
+ bool inside_group = false, have_eof_nl = true;
987
+ std::map<std::string, std::string>::const_iterator iterCS;
988
+ size_t lineno = 0;
989
+ while (getline_with_eol(stream_in, s)) {
990
+ ++lineno;
991
+
992
+ have_eof_nl = (!s.empty()) && (*s.rbegin() == '\n');
993
+ n = s.find('#');
994
+ const bool has_comment = (n != std::string::npos);
995
+ if (!has_comment) {
996
+ n = s.size();
997
+ }
998
+ if (n > 0) {
999
+ n2 = s.find_last_not_of(ws_chars, n - 1);
1000
+ if (n2 != std::string::npos) {
1001
+ n = n2 + 1;
1002
+ }
1003
+ }
1004
+ n2 = s.find_first_not_of(ws_chars);
1005
+ if (n2 == std::string::npos || n2 >= n) {
1006
+ // Blank or comment-only line
1007
+ stream_out << s;
1008
+ continue;
1009
+ }
1010
+ lineend = s.substr(n);
1011
+ linebegin = s.substr(0, n2);
1012
+ s = s.substr(n2, n - n2);
1013
+
1014
+ // It is impossible for s to be empty here, due to the blank line check above
1015
+ if (*s.begin() == '[' && *s.rbegin() == ']') {
1016
+ // 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
1017
+ ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
1018
+ inside_group = true;
1019
+ key.clear();
1020
+
1021
+ stream_out << linebegin << s << lineend;
1022
+ continue;
1023
+ }
1024
+
1025
+ n = s.find('=');
1026
+ if (n == std::string::npos) {
1027
+ // Bad line; this causes boost to throw an exception when parsing, so we comment out the entire file
1028
+ stream_in.seekg(0, std::ios_base::beg);
1029
+ stream_out.seekp(0, std::ios_base::beg);
1030
+ if (!(stream_in.good() && stream_out.good())) {
1031
+ throw std::ios_base::failure("Failed to rewind (to comment out existing file)");
1032
+ }
1033
+ // First, write out all the settings we intend to set
1034
+ setFound.clear();
1035
+ ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
1036
+ // We then define a category to ensure new settings get added before the invalid stuff
1037
+ stream_out << "[INVALID]\n";
1038
+ // Then, describe the problem in a comment
1039
+ stream_out << "# Error parsing line " << lineno << ": " << s << "\n";
1040
+ // Finally, dump the rest of the file commented out
1041
+ while (getline_with_eol(stream_in, s)) {
1042
+ stream_out << "#" << s;
1043
+ }
1044
+ return;
1045
+ }
1046
+
1047
+ if (!inside_group) {
1048
+ // We don't support/use groups, so once we're inside key is always null to avoid setting anything
1049
+ n2 = s.find_last_not_of(ws_chars, n - 1);
1050
+ if (n2 == std::string::npos) {
1051
+ n2 = n - 1;
1052
+ } else {
1053
+ ++n2;
1054
+ }
1055
+ key = s.substr(0, n2);
1056
+ }
1057
+ if ((!key.empty()) && (iterCS = settings_to_change.find(key)) != settings_to_change.end() && setFound.find(key) == setFound.end()) {
1058
+ // This is the key we want to change
1059
+ const std::string& val = iterCS->second;
1060
+ setFound.insert(key);
1061
+ ModifyRWConfigFile_SanityCheck(val);
1062
+ if (has_comment) {
1063
+ // Rather than change a commented line, comment it out entirely (the existing comment may relate to the value) and replace it
1064
+ stream_out << key << "=" << val << "\n";
1065
+ linebegin.insert(linebegin.begin(), '#');
1066
+ } else {
1067
+ // Just modify the value in-line otherwise
1068
+ n2 = s.find_first_not_of(ws_chars, n + 1);
1069
+ if (n2 == std::string::npos) {
1070
+ n2 = n + 1;
1071
+ }
1072
+ s = s.substr(0, n2) + val;
1073
+ }
1074
+ }
1075
+ stream_out << linebegin << s << lineend;
1076
+ }
1077
+ if (setFound.size() < settings_to_change.size()) {
1078
+ if (!have_eof_nl) {
1079
+ stream_out << "\n";
1080
+ }
1081
+ ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
1082
+ }
1083
+ }
1084
+
1085
+ void ArgsManager::ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, const bool also_settings_json)
1086
+ {
1087
+ LOCK(cs_args);
1088
+ fs::path rwconf_path{GetRWConfigFilePath()};
1089
+ fs::path rwconf_new_path{rwconf_path};
1090
+ rwconf_new_path += ".new";
1091
+ try {
1092
+ fs::remove(rwconf_new_path);
1093
+ std::ofstream streamRWConfigOut(rwconf_new_path, std::ios_base::out | std::ios_base::trunc);
1094
+ if (fs::exists(rwconf_path)) {
1095
+ std::ifstream streamRWConfig(rwconf_path);
1096
+ ::ModifyRWConfigStream(streamRWConfig, streamRWConfigOut, settings_to_change);
1097
+ } else {
1098
+ std::istringstream streamIn;
1099
+ ::ModifyRWConfigStream(streamIn, streamRWConfigOut, settings_to_change);
1100
+ }
1101
+ } catch (...) {
1102
+ fs::remove(rwconf_new_path);
1103
+ throw;
1104
+ }
1105
+ if (!RenameOver(rwconf_new_path, rwconf_path)) {
1106
+ fs::remove(rwconf_new_path);
1107
+ throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path)));
1108
+ }
1109
+ for (const auto& setting_change : settings_to_change) {
1110
+ m_settings.rw_config[setting_change.first] = {setting_change.second};
1111
+ }
1112
+ if (also_settings_json && !IsArgNegated("-settings")) {
1113
+ // Also save to settings.json for Core (0.21+) compatibility
1114
+ for (const auto& setting_change : settings_to_change) {
1115
+ m_settings.rw_settings[setting_change.first] = setting_change.second;
1116
+ }
1117
+ WriteSettingsFile();
1118
+ }
1119
+ if (settings_to_change.count("prune")) {
1120
+ m_rwconf_had_prune_option = true;
1121
+ }
1122
+ }
1123
+
1124
+ void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, const bool also_settings_json)
1125
+ {
1126
+ std::map<std::string, std::string> settings_to_change;
1127
+ settings_to_change[setting_to_change] = new_value;
1128
+ ModifyRWConfigFile(settings_to_change, also_settings_json);
1129
+ }
1130
+
1131
+ void ArgsManager::EraseRWConfigFile()
1132
+ {
1133
+ LOCK(cs_args);
1134
+ fs::path rwconf_path{GetRWConfigFilePath()};
1135
+ if (!fs::exists(rwconf_path)) {
1136
+ return;
1137
+ }
1138
+ fs::path rwconf_reset_path = rwconf_path;
1139
+ rwconf_reset_path += ".reset";
1140
+ if (!RenameOver(rwconf_path, rwconf_reset_path)) {
1141
+ if (fs::remove(rwconf_path)) {
1142
+ throw std::ios_base::failure(strprintf("Failed to remove %s", fs::PathToString(rwconf_path)));
1143
+ }
1144
+ }
1145
+ }
1146
+
868
1147
  namespace common {
869
1148
  #ifdef WIN32
870
1149
  WinCmdLineArgs::WinCmdLineArgs()