2023-03-23 12:23:29 +01:00
// Copyright (c) 2023 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
# include <common/args.h>
2023-05-24 15:55:53 +02:00
# include <common/settings.h>
2023-03-23 12:23:29 +01:00
# include <logging.h>
# include <sync.h>
# include <tinyformat.h>
# include <univalue.h>
2023-04-17 22:20:59 +02:00
# include <util/chaintype.h>
2023-03-23 12:23:29 +01:00
# include <util/fs.h>
# include <util/string.h>
# include <algorithm>
# include <cassert>
# include <cstdlib>
# include <fstream>
# include <iostream>
# include <list>
# include <map>
# include <memory>
# include <optional>
# include <string>
# include <string_view>
# include <utility>
# include <vector>
2023-12-06 15:13:39 -05:00
using util : : TrimString ;
using util : : TrimStringView ;
2023-03-23 12:23:29 +01:00
static bool GetConfigOptions ( std : : istream & stream , const std : : string & filepath , std : : string & error , std : : vector < std : : pair < std : : string , std : : string > > & options , std : : list < SectionInfo > & sections )
{
std : : string str , prefix ;
std : : string : : size_type pos ;
int linenr = 1 ;
while ( std : : getline ( stream , str ) ) {
bool used_hash = false ;
if ( ( pos = str . find ( ' # ' ) ) ! = std : : string : : npos ) {
str = str . substr ( 0 , pos ) ;
used_hash = true ;
}
const static std : : string pattern = " \t \r \n " ;
str = TrimString ( str , pattern ) ;
if ( ! str . empty ( ) ) {
if ( * str . begin ( ) = = ' [ ' & & * str . rbegin ( ) = = ' ] ' ) {
const std : : string section = str . substr ( 1 , str . size ( ) - 2 ) ;
sections . emplace_back ( SectionInfo { section , filepath , linenr } ) ;
prefix = section + ' . ' ;
} else if ( * str . begin ( ) = = ' - ' ) {
error = strprintf ( " parse error on line %i: %s, options in configuration file must be specified without leading - " , linenr , str ) ;
return false ;
} else if ( ( pos = str . find ( ' = ' ) ) ! = std : : string : : npos ) {
std : : string name = prefix + TrimString ( std : : string_view { str } . substr ( 0 , pos ) , pattern ) ;
std : : string_view value = TrimStringView ( std : : string_view { str } . substr ( pos + 1 ) , pattern ) ;
if ( used_hash & & name . find ( " rpcpassword " ) ! = std : : string : : npos ) {
error = strprintf ( " parse error on line %i, using # in rpcpassword can be ambiguous and should be avoided " , linenr ) ;
return false ;
}
options . emplace_back ( name , value ) ;
if ( ( pos = name . rfind ( ' . ' ) ) ! = std : : string : : npos & & prefix . length ( ) < = pos ) {
sections . emplace_back ( SectionInfo { name . substr ( 0 , pos ) , filepath , linenr } ) ;
}
} else {
error = strprintf ( " parse error on line %i: %s " , linenr , str ) ;
if ( str . size ( ) > = 2 & & str . substr ( 0 , 2 ) = = " no " ) {
error + = strprintf ( " , if you intended to specify a negated option, use %s=1 instead " , str ) ;
}
return false ;
}
}
+ + linenr ;
}
return true ;
}
bool IsConfSupported ( KeyInfo & key , std : : string & error ) {
if ( key . name = = " conf " ) {
error = " conf cannot be set in the configuration file; use includeconf= if you want to include additional config files " ;
return false ;
}
if ( key . name = = " reindex " ) {
// reindex can be set in a config file but it is strongly discouraged as this will cause the node to reindex on
// every restart. Allow the config but throw a warning
LogPrintf ( " Warning: reindex=1 is set in the configuration file, which will significantly slow down startup. Consider removing or commenting out this option for better performance, unless there is currently a condition which makes rebuilding the indexes necessary \n " ) ;
return true ;
}
return true ;
}
bool ArgsManager : : ReadConfigStream ( std : : istream & stream , const std : : string & filepath , std : : string & error , bool ignore_invalid_keys )
{
LOCK ( cs_args ) ;
std : : vector < std : : pair < std : : string , std : : string > > options ;
if ( ! GetConfigOptions ( stream , filepath , error , options , m_config_sections ) ) {
return false ;
}
for ( const std : : pair < std : : string , std : : string > & option : options ) {
KeyInfo key = InterpretKey ( option . first ) ;
std : : optional < unsigned int > flags = GetArgFlags ( ' - ' + key . name ) ;
if ( ! IsConfSupported ( key , error ) ) return false ;
if ( flags ) {
2023-05-24 16:18:59 +02:00
std : : optional < common : : SettingsValue > value = InterpretValue ( key , & option . second , * flags , error ) ;
2023-03-23 12:23:29 +01:00
if ( ! value ) {
return false ;
}
m_settings . ro_config [ key . section ] [ key . name ] . push_back ( * value ) ;
} else {
if ( ignore_invalid_keys ) {
LogPrintf ( " Ignoring unknown configuration value %s \n " , option . first ) ;
} else {
error = strprintf ( " Invalid configuration value %s " , option . first ) ;
return false ;
}
}
}
return true ;
}
bool ArgsManager : : ReadConfigFiles ( std : : string & error , bool ignore_invalid_keys )
{
{
LOCK ( cs_args ) ;
m_settings . ro_config . clear ( ) ;
m_config_sections . clear ( ) ;
2023-03-27 10:17:57 -04:00
m_config_path = AbsPathForConfigVal ( * this , GetPathArg ( " -conf " , BITCOIN_CONF_FILENAME ) , /*net_specific=*/ false ) ;
2023-03-23 12:23:29 +01:00
}
const auto conf_path { GetConfigFilePath ( ) } ;
std : : ifstream stream { conf_path } ;
// not ok to have a config file specified that cannot be opened
if ( IsArgSet ( " -conf " ) & & ! stream . good ( ) ) {
error = strprintf ( " specified config file \" %s \" could not be opened. " , fs : : PathToString ( conf_path ) ) ;
return false ;
}
// ok to not have a config file
if ( stream . good ( ) ) {
if ( ! ReadConfigStream ( stream , fs : : PathToString ( conf_path ) , error , ignore_invalid_keys ) ) {
return false ;
}
// `-includeconf` cannot be included in the command line arguments except
// as `-noincludeconf` (which indicates that no included conf file should be used).
bool use_conf_file { true } ;
{
LOCK ( cs_args ) ;
2023-05-24 16:18:59 +02:00
if ( auto * includes = common : : FindKey ( m_settings . command_line_options , " includeconf " ) ) {
2023-03-23 12:23:29 +01:00
// ParseParameters() fails if a non-negated -includeconf is passed on the command-line
2023-05-24 16:18:59 +02:00
assert ( common : : SettingsSpan ( * includes ) . last_negated ( ) ) ;
2023-03-23 12:23:29 +01:00
use_conf_file = false ;
}
}
if ( use_conf_file ) {
2023-04-17 22:20:59 +02:00
std : : string chain_id = GetChainTypeString ( ) ;
2023-03-23 12:23:29 +01:00
std : : vector < std : : string > conf_file_names ;
auto add_includes = [ & ] ( const std : : string & network , size_t skip = 0 ) {
size_t num_values = 0 ;
LOCK ( cs_args ) ;
2023-05-24 16:18:59 +02:00
if ( auto * section = common : : FindKey ( m_settings . ro_config , network ) ) {
if ( auto * values = common : : FindKey ( * section , " includeconf " ) ) {
for ( size_t i = std : : max ( skip , common : : SettingsSpan ( * values ) . negated ( ) ) ; i < values - > size ( ) ; + + i ) {
2023-03-23 12:23:29 +01:00
conf_file_names . push_back ( ( * values ) [ i ] . get_str ( ) ) ;
}
num_values = values - > size ( ) ;
}
}
return num_values ;
} ;
// We haven't set m_network yet (that happens in SelectParams()), so manually check
// for network.includeconf args.
const size_t chain_includes = add_includes ( chain_id ) ;
const size_t default_includes = add_includes ( { } ) ;
for ( const std : : string & conf_file_name : conf_file_names ) {
2023-03-27 10:17:57 -04:00
std : : ifstream conf_file_stream { AbsPathForConfigVal ( * this , fs : : PathFromString ( conf_file_name ) , /*net_specific=*/ false ) } ;
2023-03-23 12:23:29 +01:00
if ( conf_file_stream . good ( ) ) {
if ( ! ReadConfigStream ( conf_file_stream , conf_file_name , error , ignore_invalid_keys ) ) {
return false ;
}
LogPrintf ( " Included configuration file %s \n " , conf_file_name ) ;
} else {
error = " Failed to include configuration file " + conf_file_name ;
return false ;
}
}
// Warn about recursive -includeconf
conf_file_names . clear ( ) ;
add_includes ( chain_id , /* skip= */ chain_includes ) ;
add_includes ( { } , /* skip= */ default_includes ) ;
2023-04-17 22:20:59 +02:00
std : : string chain_id_final = GetChainTypeString ( ) ;
2023-03-23 12:23:29 +01:00
if ( chain_id_final ! = chain_id ) {
// Also warn about recursive includeconf for the chain that was specified in one of the includeconfs
add_includes ( chain_id_final ) ;
}
for ( const std : : string & conf_file_name : conf_file_names ) {
tfm : : format ( std : : cerr , " warning: -includeconf cannot be used from included files; ignoring -includeconf=%s \n " , conf_file_name ) ;
}
}
}
// If datadir is changed in .conf file:
ClearPathCache ( ) ;
if ( ! CheckDataDirOption ( * this ) ) {
error = strprintf ( " specified data directory \" %s \" does not exist. " , GetArg ( " -datadir " , " " ) ) ;
return false ;
}
return true ;
}
fs : : path AbsPathForConfigVal ( const ArgsManager & args , const fs : : path & path , bool net_specific )
{
if ( path . is_absolute ( ) ) {
return path ;
}
return fsbridge : : AbsPathJoin ( net_specific ? args . GetDataDirNet ( ) : args . GetDataDirBase ( ) , path ) ;
}