7070#include < malloc.h>
7171#endif
7272
73- #include < boost/algorithm/string/case_conv.hpp> // for to_lower()
74- #include < boost/algorithm/string/predicate.hpp> // for startswith() and endswith()
7573#include < boost/interprocess/sync/file_lock.hpp>
7674#include < boost/program_options/detail/config_file.hpp>
7775#include < boost/thread.hpp>
@@ -432,21 +430,54 @@ bool DirIsWritable(const fs::path& directory)
432430 return true ;
433431}
434432
435- /* * Interpret string as boolean, for argument parsing */
433+ /* *
434+ * Interpret a string argument as a boolean.
435+ *
436+ * The definition of atoi() requires that non-numeric string values like "foo",
437+ * return 0. This means that if a user unintentionally supplies a non-integer
438+ * argument here, the return value is always false. This means that -foo=false
439+ * does what the user probably expects, but -foo=true is well defined but does
440+ * not do what they probably expected.
441+ *
442+ * The return value of atoi() is undefined when given input not representable as
443+ * an int. On most systems this means string value between "-2147483648" and
444+ * "2147483647" are well defined (this method will return true). Setting
445+ * -txindex=2147483648 on most systems, however, is probably undefined.
446+ *
447+ * For a more extensive discussion of this topic (and a wide range of opinions
448+ * on the Right Way to change this code), see PR12713.
449+ */
436450static bool InterpretBool (const std::string& strValue)
437451{
438452 if (strValue.empty ())
439453 return true ;
440454 return (atoi (strValue) != 0 );
441455}
442456
443- /* * Turn -noX into -X=0 */
444- static void InterpretNegativeSetting (std::string& strKey, std::string& strValue)
457+ /* *
458+ * Interpret -nofoo as if the user supplied -foo=0.
459+ *
460+ * This method also tracks when the -no form was supplied, and treats "-foo" as
461+ * a negated option when this happens. This can be later checked using the
462+ * IsArgNegated() method. One use case for this is to have a way to disable
463+ * options that are not normally boolean (e.g. using -nodebuglogfile to request
464+ * that debug log output is not sent to any file at all).
465+ */
466+ void ArgsManager::InterpretNegatedOption (std::string& key, std::string& val)
445467{
446- if (strKey.length ()>3 && strKey[0 ]==' -' && strKey[1 ]==' n' && strKey[2 ]==' o' )
447- {
448- strKey = " -" + strKey.substr (3 );
449- strValue = InterpretBool (strValue) ? " 0" : " 1" ;
468+ if (key.substr (0 , 3 ) == " -no" ) {
469+ bool bool_val = InterpretBool (val);
470+ if (!bool_val ) {
471+ // Double negatives like -nofoo=0 are supported (but discouraged)
472+ LogPrintf (" Warning: parsed potentially confusing double-negative %s=%s\n " , key, val);
473+ }
474+ key.erase (1 , 2 );
475+ m_negated_args.insert (key);
476+ val = bool_val ? " 0" : " 1" ;
477+ } else {
478+ // In an invocation like "bitcoind -nofoo -foo" we want to unmark -foo
479+ // as negated when we see the second option.
480+ m_negated_args.erase (key);
450481 }
451482}
452483
@@ -455,34 +486,34 @@ void ArgsManager::ParseParameters(int argc, const char* const argv[])
455486 LOCK (cs_args);
456487 mapArgs.clear ();
457488 mapMultiArgs.clear ();
458-
459- for (int i = 1 ; i < argc; i++)
460- {
461- std::string str (argv[i]);
462- std::string strValue;
463- size_t is_index = str.find (' =' );
464- if (is_index != std::string::npos)
465- {
466- strValue = str.substr (is_index+1 );
467- str = str.substr (0 , is_index);
489+ m_negated_args.clear ();
490+
491+ for (int i = 1 ; i < argc; i++) {
492+ std::string key (argv[i]);
493+ std::string val;
494+ size_t is_index = key.find (' =' );
495+ if (is_index != std::string::npos) {
496+ val = key.substr (is_index + 1 );
497+ key.erase (is_index);
468498 }
469499#ifdef WIN32
470- boost::to_lower (str );
471- if (boost::algorithm::starts_with (str, " / " ) )
472- str = " - " + str. substr ( 1 ) ;
500+ std::transform (key. begin (), key. end (), key. begin (), ::tolower );
501+ if (key[ 0 ] == ' / ' )
502+ key[ 0 ] = ' - ' ;
473503#endif
474504
475- if (str [0 ] != ' -' )
505+ if (key [0 ] != ' -' )
476506 break ;
477507
478- // Interpret --foo as -foo.
479- // If both --foo and -foo are set, the last takes effect.
480- if (str.length () > 1 && str[1 ] == ' -' )
481- str = str.substr (1 );
482- InterpretNegativeSetting (str, strValue);
508+ // Transform --foo to -foo
509+ if (key.length () > 1 && key[1 ] == ' -' )
510+ key.erase (0 , 1 );
511+
512+ // Transform -nofoo to -foo=0
513+ InterpretNegatedOption (key, val);
483514
484- mapArgs[str ] = strValue ;
485- mapMultiArgs[str ].push_back (strValue );
515+ mapArgs[key ] = val ;
516+ mapMultiArgs[key ].push_back (val );
486517 }
487518}
488519
@@ -500,6 +531,12 @@ bool ArgsManager::IsArgSet(const std::string& strArg) const
500531 return mapArgs.count (strArg);
501532}
502533
534+ bool ArgsManager::IsArgNegated (const std::string& strArg) const
535+ {
536+ LOCK (cs_args);
537+ return m_negated_args.find (strArg) != m_negated_args.end ();
538+ }
539+
503540std::string ArgsManager::GetArg (const std::string& strArg, const std::string& strDefault) const
504541{
505542 LOCK (cs_args);
@@ -711,7 +748,7 @@ void ArgsManager::ReadConfigFile(const std::string& confPath)
711748 // Don't overwrite existing settings so command line settings override bitcoin.conf
712749 std::string strKey = std::string (" -" ) + it->string_key ;
713750 std::string strValue = it->value [0 ];
714- InterpretNegativeSetting (strKey, strValue);
751+ InterpretNegatedOption (strKey, strValue);
715752 if (mapArgs.count (strKey) == 0 )
716753 mapArgs[strKey] = strValue;
717754 mapMultiArgs[strKey].push_back (strValue);
0 commit comments