v30.3-roots.1 · Commit 2: [PATCH 02/68] feat(runtime): preserve Roots operator controls on Core 30.3
src/common/args.cpp
Before: src/common/args.cpp · After: src/common/args.cpp
File metadata
index d44cd4319b6aa17e5e0281545c887c5b4437379d..eaf15d8c3e0b98dca09b088f380bf575d6f43d6b 100644
Download original patch · Release notes
Diff rows 1–388 of 388. 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;
|
|
@@ -400,7 +403,7 @@ static void SaveErrors(const std::vector<std::string> errors, std::vector<std::s
|
|
|
400
403
|
if (error_out) {
|
|
401
404
|
error_out->emplace_back(error);
|
|
402
405
|
} else {
|
|
403
|
-
LogPrintf("%s\n", error);
|
|
406
|
+
LogWarning("%s", error);
|
|
404
407
|
}
|
|
405
408
|
}
|
|
406
409
|
}
|
|
@@ -422,7 +425,7 @@ bool ArgsManager::ReadSettingsFile(std::vector<std::string>* errors)
|
|
|
422
425
|
for (const auto& setting : m_settings.rw_settings) {
|
|
423
426
|
KeyInfo key = InterpretKey(setting.first); // Split setting key into section and argname
|
|
424
427
|
if (!GetArgFlags('-' + key.name)) {
|
|
425
|
-
LogPrintf("Ignoring unknown rw_settings value %s\n", setting.first);
|
|
428
|
+
LogWarning("Ignoring unknown rw_settings value %s", setting.first);
|
|
426
429
|
}
|
|
427
430
|
}
|
|
428
431
|
return true;
|
|
@@ -510,6 +513,25 @@ int64_t SettingToInt(const common::SettingsValue& value, int64_t nDefault)
|
|
|
510
513
|
return SettingToInt(value).value_or(nDefault);
|
|
511
514
|
}
|
|
512
515
|
|
|
516
|
+
std::optional<int64_t> ArgsManager::GetFixedPointArg(const std::string& arg, int decimals) const
|
|
517
|
+
{
|
|
518
|
+
const common::SettingsValue value = GetSetting(arg);
|
|
519
|
+
return SettingToFixedPoint(value, decimals);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
std::optional<int64_t> SettingToFixedPoint(const common::SettingsValue& value, int decimals)
|
|
523
|
+
{
|
|
524
|
+
if (value.isNull()) return std::nullopt;
|
|
525
|
+
if (value.isFalse()) return 0;
|
|
526
|
+
if (value.isTrue()) return 1;
|
|
527
|
+
if (!value.isNum()) value.get_str(); // throws an exception if type is wrong
|
|
528
|
+
int64_t v;
|
|
529
|
+
if (!ParseFixedPoint(value.getValStr(), decimals, &v)) {
|
|
530
|
+
throw std::runtime_error(strprintf("Parse error ('%s')", value.getValStr()));
|
|
531
|
+
}
|
|
532
|
+
return v;
|
|
533
|
+
}
|
|
534
|
+
|
|
513
535
|
bool ArgsManager::GetBoolArg(const std::string& strArg, bool fDefault) const
|
|
514
536
|
{
|
|
515
537
|
return GetBoolArg(strArg).value_or(fDefault);
|
|
@@ -523,9 +545,21 @@ std::optional<bool> ArgsManager::GetBoolArg(const std::string& strArg) const
|
|
|
523
545
|
|
|
524
546
|
std::optional<bool> SettingToBool(const common::SettingsValue& value)
|
|
525
547
|
{
|
|
526
|
-
if (value.isNull()) return std::nullopt;
|
|
527
|
-
if (value.isBool()) return value.get_bool();
|
|
528
|
-
return InterpretBool(value.get_str());
|
|
548
|
+
switch (value.getType()) {
|
|
549
|
+
case UniValue::VNULL:
|
|
550
|
+
return std::nullopt;
|
|
551
|
+
case UniValue::VBOOL:
|
|
552
|
+
return value.get_bool();
|
|
553
|
+
case UniValue::VOBJ:
|
|
554
|
+
case UniValue::VARR:
|
|
555
|
+
// Throws an exception
|
|
556
|
+
value.get_str();
|
|
557
|
+
assert(false);
|
|
558
|
+
case UniValue::VSTR:
|
|
559
|
+
case UniValue::VNUM:
|
|
560
|
+
return InterpretBool(value.getValStr());
|
|
561
|
+
}
|
|
562
|
+
assert(false);
|
|
529
563
|
}
|
|
530
564
|
|
|
531
565
|
bool SettingToBool(const common::SettingsValue& value, bool fDefault)
|
|
@@ -550,9 +584,19 @@ bool ArgsManager::SoftSetBoolArg(const std::string& strArg, bool fValue)
|
|
|
550
584
|
}
|
|
551
585
|
|
|
552
586
|
void ArgsManager::ForceSetArg(const std::string& strArg, const std::string& strValue)
|
|
587
|
+
{
|
|
588
|
+
ForceSetArgV(strArg, common::SettingsValue{strValue});
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
void ArgsManager::ForceSetArg(const std::string& arg, const int64_t value)
|
|
592
|
+
{
|
|
593
|
+
ForceSetArg(arg, util::ToString(value));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
void ArgsManager::ForceSetArgV(const std::string& arg, const common::SettingsValue& value)
|
|
553
597
|
{
|
|
554
598
|
LOCK(cs_args);
|
|
555
|
-
m_settings.forced_settings[SettingName(strArg)] = strValue;
|
|
599
|
+
m_settings.forced_settings[SettingName(arg)] = value;
|
|
556
600
|
}
|
|
557
601
|
|
|
558
602
|
void ArgsManager::AddCommand(const std::string& cmd, const std::string& help)
|
|
@@ -588,10 +632,10 @@ void ArgsManager::AddArg(const std::string& name, const std::string& help, unsig
|
|
|
588
632
|
}
|
|
589
633
|
}
|
|
590
634
|
|
|
591
|
-
void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names)
|
|
635
|
+
void ArgsManager::AddHiddenArgs(const std::vector<std::string>& names, unsigned int flags)
|
|
592
636
|
{
|
|
593
637
|
for (const std::string& name : names) {
|
|
594
|
-
AddArg(name, "", ArgsManager::ALLOW_ANY, OptionsCategory::HIDDEN);
|
|
638
|
+
AddArg(name, "", flags, OptionsCategory::HIDDEN);
|
|
595
639
|
}
|
|
596
640
|
}
|
|
597
641
|
|
|
@@ -673,6 +717,9 @@ std::string ArgsManager::GetHelpMessage() const
|
|
|
673
717
|
case OptionsCategory::CLI_COMMANDS:
|
|
674
718
|
usage += HelpMessageGroup("CLI Commands:");
|
|
675
719
|
break;
|
|
720
|
+
case OptionsCategory::STATS:
|
|
721
|
+
usage += HelpMessageGroup("Statistic options:");
|
|
722
|
+
break;
|
|
676
723
|
default:
|
|
677
724
|
break;
|
|
678
725
|
}
|
|
@@ -702,8 +749,8 @@ bool HelpRequested(const ArgsManager& args)
|
|
|
702
749
|
|
|
703
750
|
void SetupHelpOptions(ArgsManager& args)
|
|
704
751
|
{
|
|
705
|
-
args.AddArg("-help", "Print this help message and exit (also -h or -?)", ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
|
|
706
|
-
args.AddHiddenArgs({"-h", "-?"});
|
|
752
|
+
args.AddArg("-help", "Print this help message and exit (also -h or -?)", ArgsManager::DISALLOW_NEGATION, OptionsCategory::OPTIONS);
|
|
753
|
+
args.AddHiddenArgs({"-h", "-?"}, ArgsManager::DISALLOW_NEGATION);
|
|
707
754
|
}
|
|
708
755
|
|
|
709
756
|
static const int screenWidth = 79;
|
|
@@ -786,6 +833,12 @@ void ArgsManager::SetConfigFilePath(fs::path path)
|
|
|
786
833
|
m_config_path = path;
|
|
787
834
|
}
|
|
788
835
|
|
|
836
|
+
fs::path ArgsManager::GetRWConfigFilePath() const
|
|
837
|
+
{
|
|
838
|
+
LOCK(cs_args);
|
|
839
|
+
return *Assert(m_rwconf_path);
|
|
840
|
+
}
|
|
841
|
+
|
|
789
842
|
ChainType ArgsManager::GetChainType() const
|
|
790
843
|
{
|
|
791
844
|
std::variant<ChainType, std::string> arg = GetChainArg();
|
|
@@ -877,9 +930,234 @@ void ArgsManager::LogArgs() const
|
|
|
877
930
|
for (const auto& setting : m_settings.rw_settings) {
|
|
878
931
|
LogPrintf("Setting file arg: %s = %s\n", setting.first, setting.second.write());
|
|
879
932
|
}
|
|
933
|
+
logArgsPrefix("R/W config file arg:", "", m_settings.rw_config);
|
|
880
934
|
logArgsPrefix("Command-line arg:", "", m_settings.command_line_options);
|
|
881
935
|
}
|
|
882
936
|
|
|
937
|
+
namespace {
|
|
938
|
+
|
|
939
|
+
// Like std::getline, but includes the EOL character in the result
|
|
940
|
+
bool getline_with_eol(std::istream& stream, std::string& result)
|
|
941
|
+
{
|
|
942
|
+
int current_char;
|
|
943
|
+
current_char = stream.get();
|
|
944
|
+
if (current_char == std::char_traits<char>::eof()) {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
result.clear();
|
|
948
|
+
result.push_back(char(current_char));
|
|
949
|
+
while (current_char != '\n') {
|
|
950
|
+
current_char = stream.get();
|
|
951
|
+
if (current_char == std::char_traits<char>::eof()) {
|
|
952
|
+
break;
|
|
953
|
+
}
|
|
954
|
+
result.push_back(char(current_char));
|
|
955
|
+
}
|
|
956
|
+
return true;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const char * const ModifyRWConfigFile_ws_chars = " \t\r\n";
|
|
960
|
+
|
|
961
|
+
void ModifyRWConfigFile_SanityCheck(const std::string& s)
|
|
962
|
+
{
|
|
963
|
+
if (s.empty()) {
|
|
964
|
+
// Dereferencing .begin or .rbegin below is invalid unless the string has at least one character.
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
static const char * const newline_chars = "\r\n";
|
|
969
|
+
static std::string ws_chars(ModifyRWConfigFile_ws_chars);
|
|
970
|
+
if (s.find_first_of(newline_chars) != std::string::npos) {
|
|
971
|
+
throw std::invalid_argument("New-line in config name/value");
|
|
972
|
+
}
|
|
973
|
+
if (ws_chars.find(*s.begin()) != std::string::npos || ws_chars.find(*s.rbegin()) != std::string::npos) {
|
|
974
|
+
throw std::invalid_argument("Config name/value has leading/trailing whitespace");
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
void ModifyRWConfigFile_WriteRemaining(std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change, std::set<std::string>& setFound)
|
|
979
|
+
{
|
|
980
|
+
for (const auto& setting_pair : settings_to_change) {
|
|
981
|
+
const std::string& key = setting_pair.first;
|
|
982
|
+
const std::string& val = setting_pair.second;
|
|
983
|
+
if (setFound.find(key) != setFound.end()) {
|
|
984
|
+
continue;
|
|
985
|
+
}
|
|
986
|
+
setFound.insert(key);
|
|
987
|
+
ModifyRWConfigFile_SanityCheck(key);
|
|
988
|
+
ModifyRWConfigFile_SanityCheck(val);
|
|
989
|
+
stream_out << key << "=" << val << "\n";
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
} // namespace
|
|
993
|
+
|
|
994
|
+
void ModifyRWConfigStream(std::istream& stream_in, std::ostream& stream_out, const std::map<std::string, std::string>& settings_to_change)
|
|
995
|
+
{
|
|
996
|
+
static const char * const ws_chars = ModifyRWConfigFile_ws_chars;
|
|
997
|
+
std::set<std::string> setFound;
|
|
998
|
+
std::string s, lineend, linebegin, key;
|
|
999
|
+
std::string::size_type n, n2;
|
|
1000
|
+
bool inside_group = false, have_eof_nl = true;
|
|
1001
|
+
std::map<std::string, std::string>::const_iterator iterCS;
|
|
1002
|
+
size_t lineno = 0;
|
|
1003
|
+
while (getline_with_eol(stream_in, s)) {
|
|
1004
|
+
++lineno;
|
|
1005
|
+
|
|
1006
|
+
have_eof_nl = (!s.empty()) && (*s.rbegin() == '\n');
|
|
1007
|
+
n = s.find('#');
|
|
1008
|
+
const bool has_comment = (n != std::string::npos);
|
|
1009
|
+
if (!has_comment) {
|
|
1010
|
+
n = s.size();
|
|
1011
|
+
}
|
|
1012
|
+
if (n > 0) {
|
|
1013
|
+
n2 = s.find_last_not_of(ws_chars, n - 1);
|
|
1014
|
+
if (n2 != std::string::npos) {
|
|
1015
|
+
n = n2 + 1;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
n2 = s.find_first_not_of(ws_chars);
|
|
1019
|
+
if (n2 == std::string::npos || n2 >= n) {
|
|
1020
|
+
// Blank or comment-only line
|
|
1021
|
+
stream_out << s;
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
lineend = s.substr(n);
|
|
1025
|
+
linebegin = s.substr(0, n2);
|
|
1026
|
+
s = s.substr(n2, n - n2);
|
|
1027
|
+
|
|
1028
|
+
// It is impossible for s to be empty here, due to the blank line check above
|
|
1029
|
+
if (*s.begin() == '[' && *s.rbegin() == ']') {
|
|
1030
|
+
// 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
|
|
1031
|
+
ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
|
|
1032
|
+
inside_group = true;
|
|
1033
|
+
key.clear();
|
|
1034
|
+
|
|
1035
|
+
stream_out << linebegin << s << lineend;
|
|
1036
|
+
continue;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
n = s.find('=');
|
|
1040
|
+
if (n == std::string::npos) {
|
|
1041
|
+
// Bad line; this causes boost to throw an exception when parsing, so we comment out the entire file
|
|
1042
|
+
stream_in.seekg(0, std::ios_base::beg);
|
|
1043
|
+
stream_out.seekp(0, std::ios_base::beg);
|
|
1044
|
+
if (!(stream_in.good() && stream_out.good())) {
|
|
1045
|
+
throw std::ios_base::failure("Failed to rewind (to comment out existing file)");
|
|
1046
|
+
}
|
|
1047
|
+
// First, write out all the settings we intend to set
|
|
1048
|
+
setFound.clear();
|
|
1049
|
+
ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
|
|
1050
|
+
// We then define a category to ensure new settings get added before the invalid stuff
|
|
1051
|
+
stream_out << "[INVALID]\n";
|
|
1052
|
+
// Then, describe the problem in a comment
|
|
1053
|
+
stream_out << "# Error parsing line " << lineno << ": " << s << "\n";
|
|
1054
|
+
// Finally, dump the rest of the file commented out
|
|
1055
|
+
while (getline_with_eol(stream_in, s)) {
|
|
1056
|
+
stream_out << "#" << s;
|
|
1057
|
+
}
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
if (!inside_group) {
|
|
1062
|
+
// We don't support/use groups, so once we're inside key is always null to avoid setting anything
|
|
1063
|
+
n2 = s.find_last_not_of(ws_chars, n - 1);
|
|
1064
|
+
if (n2 == std::string::npos) {
|
|
1065
|
+
n2 = n - 1;
|
|
1066
|
+
} else {
|
|
1067
|
+
++n2;
|
|
1068
|
+
}
|
|
1069
|
+
key = s.substr(0, n2);
|
|
1070
|
+
}
|
|
1071
|
+
if ((!key.empty()) && (iterCS = settings_to_change.find(key)) != settings_to_change.end() && setFound.find(key) == setFound.end()) {
|
|
1072
|
+
// This is the key we want to change
|
|
1073
|
+
const std::string& val = iterCS->second;
|
|
1074
|
+
setFound.insert(key);
|
|
1075
|
+
ModifyRWConfigFile_SanityCheck(val);
|
|
1076
|
+
if (has_comment) {
|
|
1077
|
+
// Rather than change a commented line, comment it out entirely (the existing comment may relate to the value) and replace it
|
|
1078
|
+
stream_out << key << "=" << val << "\n";
|
|
1079
|
+
linebegin.insert(linebegin.begin(), '#');
|
|
1080
|
+
} else {
|
|
1081
|
+
// Just modify the value in-line otherwise
|
|
1082
|
+
n2 = s.find_first_not_of(ws_chars, n + 1);
|
|
1083
|
+
if (n2 == std::string::npos) {
|
|
1084
|
+
n2 = n + 1;
|
|
1085
|
+
}
|
|
1086
|
+
s = s.substr(0, n2) + val;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
stream_out << linebegin << s << lineend;
|
|
1090
|
+
}
|
|
1091
|
+
if (setFound.size() < settings_to_change.size()) {
|
|
1092
|
+
if (!have_eof_nl) {
|
|
1093
|
+
stream_out << "\n";
|
|
1094
|
+
}
|
|
1095
|
+
ModifyRWConfigFile_WriteRemaining(stream_out, settings_to_change, setFound);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
void ArgsManager::ModifyRWConfigFile(const std::map<std::string, std::string>& settings_to_change, const bool also_settings_json)
|
|
1100
|
+
{
|
|
1101
|
+
LOCK(cs_args);
|
|
1102
|
+
fs::path rwconf_path{GetRWConfigFilePath()};
|
|
1103
|
+
fs::path rwconf_new_path{rwconf_path};
|
|
1104
|
+
rwconf_new_path += ".new";
|
|
1105
|
+
try {
|
|
1106
|
+
fs::remove(rwconf_new_path);
|
|
1107
|
+
std::ofstream streamRWConfigOut(rwconf_new_path, std::ios_base::out | std::ios_base::trunc);
|
|
1108
|
+
if (fs::exists(rwconf_path)) {
|
|
1109
|
+
std::ifstream streamRWConfig(rwconf_path);
|
|
1110
|
+
::ModifyRWConfigStream(streamRWConfig, streamRWConfigOut, settings_to_change);
|
|
1111
|
+
} else {
|
|
1112
|
+
std::istringstream streamIn;
|
|
1113
|
+
::ModifyRWConfigStream(streamIn, streamRWConfigOut, settings_to_change);
|
|
1114
|
+
}
|
|
1115
|
+
} catch (...) {
|
|
1116
|
+
fs::remove(rwconf_new_path);
|
|
1117
|
+
throw;
|
|
1118
|
+
}
|
|
1119
|
+
if (!RenameOver(rwconf_new_path, rwconf_path)) {
|
|
1120
|
+
fs::remove(rwconf_new_path);
|
|
1121
|
+
throw std::ios_base::failure(strprintf("Failed to replace %s", fs::PathToString(rwconf_new_path)));
|
|
1122
|
+
}
|
|
1123
|
+
for (const auto& setting_change : settings_to_change) {
|
|
1124
|
+
m_settings.rw_config[setting_change.first] = {setting_change.second};
|
|
1125
|
+
}
|
|
1126
|
+
if (also_settings_json && !IsArgNegated("-settings")) {
|
|
1127
|
+
// Also save to settings.json for Core (0.21+) compatibility
|
|
1128
|
+
for (const auto& setting_change : settings_to_change) {
|
|
1129
|
+
m_settings.rw_settings[setting_change.first] = setting_change.second;
|
|
1130
|
+
}
|
|
1131
|
+
WriteSettingsFile();
|
|
1132
|
+
}
|
|
1133
|
+
if (settings_to_change.count("prune")) {
|
|
1134
|
+
m_rwconf_had_prune_option = true;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
void ArgsManager::ModifyRWConfigFile(const std::string& setting_to_change, const std::string& new_value, const bool also_settings_json)
|
|
1139
|
+
{
|
|
1140
|
+
std::map<std::string, std::string> settings_to_change;
|
|
1141
|
+
settings_to_change[setting_to_change] = new_value;
|
|
1142
|
+
ModifyRWConfigFile(settings_to_change, also_settings_json);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
void ArgsManager::EraseRWConfigFile()
|
|
1146
|
+
{
|
|
1147
|
+
LOCK(cs_args);
|
|
1148
|
+
fs::path rwconf_path{GetRWConfigFilePath()};
|
|
1149
|
+
if (!fs::exists(rwconf_path)) {
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
fs::path rwconf_reset_path = rwconf_path;
|
|
1153
|
+
rwconf_reset_path += ".reset";
|
|
1154
|
+
if (!RenameOver(rwconf_path, rwconf_reset_path)) {
|
|
1155
|
+
if (fs::remove(rwconf_path)) {
|
|
1156
|
+
throw std::ios_base::failure(strprintf("Failed to remove %s", fs::PathToString(rwconf_path)));
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
883
1161
|
namespace common {
|
|
884
1162
|
#ifdef WIN32
|
|
885
1163
|
WinCmdLineArgs::WinCmdLineArgs()
|