polserver/pol-core/clib/cfgfile.cpp
turleypol 7bc35dda6b Removed the uncountable ways to get the toplevel owner... :(
loading objects handles now nonset realm for containers correctly
2020-04-23 21:23:13 +02:00

1088 lines
23 KiB
C++

/** @file
*
* @par History
* - 2005/07/01 Shinigami: added ConfigFile::_modified (stat.st_mtime) to detect cfg file
* modification
* - 2009/08/25 Shinigami: STLport-5.2.1 fix: elem->type() check will use strlen() now
*/
#include "cfgfile.h"
#include <ctype.h>
#include <exception>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "cfgelem.h"
#include "clib.h"
#include "logfacility.h"
#include "stlutil.h"
#include "strutil.h"
#include <format/format.h>
namespace Pol
{
namespace Clib
{
namespace
{
bool commentline( const std::string& str )
{
#ifdef __GNUC__
return ( ( str[0] == '#' ) || ( str.substr( 0, 2 ) == "//" ) );
#else
return ( ( str[0] == '#' ) || ( str.compare( 0, 2, "//" ) == 0 ) );
#endif
}
} // namespace
ConfigProperty::ConfigProperty( std::string name, std::string value )
: name_( std::move( name ) ), value_( std::move( value ) )
{
}
ConfigElemBase::ConfigElemBase() : type_( "" ), rest_( "" ), _source( nullptr ) {}
size_t ConfigElemBase::estimateSize() const
{
return type_.capacity() + rest_.capacity() + sizeof( _source );
}
void ConfigElemBase::throw_error( const std::string& errmsg ) const
{
if ( _source != nullptr )
_source->display_error( errmsg, false, this );
throw std::runtime_error( "Configuration file error" );
}
unsigned short ConfigElemBase::string_to_u16( const char* prop, const std::string& str )
{
// FIXME isdigit isxdigit - +
// or, use endptr
// FIXME check range within unsigned short
char* endptr = nullptr;
unsigned short val = (unsigned short)strtoul( str.c_str(), &endptr, 0 );
if ( ( endptr != nullptr ) && ( *endptr != '\0' ) && !isspace( *endptr ) )
{
std::string errmsg;
errmsg = "Poorly formed number in property '";
errmsg += prop;
errmsg += "': " + str;
throw_error( errmsg );
}
return val;
}
ConfigElem::ConfigElem() : ConfigElemBase() {}
ConfigElem::~ConfigElem() {}
size_t ConfigElem::estimateSize() const
{
size_t size = ConfigElemBase::estimateSize();
for ( const auto& ele : properties )
size += ele.first.capacity() + ele.second.capacity() + ( sizeof( void* ) * 3 + 1 ) / 2;
return size;
}
const char* ConfigElemBase::type() const
{
return type_.c_str();
}
const char* ConfigElemBase::rest() const
{
return rest_.c_str();
}
void ConfigElem::set_rest( const char* rest )
{
rest_ = rest;
}
void ConfigElem::set_type( const char* type )
{
type_ = type;
}
void ConfigElem::set_source( const ConfigElem& elem )
{
_source = elem._source;
}
void ConfigElem::set_source( const ConfigSource* source )
{
_source = source;
}
bool ConfigElemBase::type_is( const char* type ) const
{
return ( stricmp( type_.c_str(), type ) == 0 );
}
bool ConfigElem::remove_first_prop( std::string* propname, std::string* value )
{
if ( properties.empty() )
return false;
auto itr = properties.begin();
*propname = ( *itr ).first;
*value = ( *itr ).second;
properties.erase( itr );
return true;
}
bool ConfigElem::has_prop( const char* propname ) const
{
return properties.count( propname ) != 0;
}
bool VectorConfigElem::has_prop( const char* propname ) const
{
for ( const auto& prop : properties )
{
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
{
return true;
}
}
return false;
}
bool ConfigElem::remove_prop( const char* propname, std::string* value )
{
auto itr = properties.find( propname );
if ( itr != properties.end() )
{
*value = ( *itr ).second;
properties.erase( itr );
return true;
}
else
{
return false;
}
}
bool VectorConfigElem::remove_prop( const char* propname, std::string* value )
{
auto itr = properties.begin(), end = properties.end();
for ( ; itr != end; ++itr )
{
ConfigProperty* prop = *itr;
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
{
*value = prop->value_;
delete prop;
properties.erase( itr );
return true;
}
}
return false;
}
bool ConfigElem::read_prop( const char* propname, std::string* value ) const
{
auto itr = properties.find( propname );
if ( itr != properties.end() )
{
*value = ( *itr ).second;
return true;
}
else
{
return false;
}
}
bool VectorConfigElem::read_prop( const char* propname, std::string* value ) const
{
for ( const auto& prop : properties )
{
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
{
*value = prop->value_;
return true;
}
}
return false;
}
void ConfigElem::get_prop( const char* propname, unsigned int* plong ) const
{
auto itr = properties.find( propname );
if ( itr != properties.end() )
{
*plong = strtoul( ( *itr ).second.c_str(), nullptr, 0 );
}
else
{
throw_error( "SERIAL property not found" );
}
}
bool ConfigElem::remove_prop( const char* propname, unsigned int* plong )
{
auto itr = properties.find( propname );
if ( itr != properties.end() )
{
*plong = strtoul( ( *itr ).second.c_str(), nullptr, 0 );
properties.erase( itr );
return true;
}
else
{
return false;
}
}
bool VectorConfigElem::remove_prop( const char* propname, unsigned int* plong )
{
auto itr = properties.begin(), end = properties.end();
for ( ; itr != end; ++itr )
{
ConfigProperty* prop = *itr;
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
{
*plong = strtoul( prop->value_.c_str(), nullptr, 0 );
delete prop;
properties.erase( itr );
return true;
}
}
return false;
}
bool ConfigElem::remove_prop( const char* propname, unsigned short* psval )
{
std::string temp;
if ( remove_prop( propname, &temp ) )
{
*psval = string_to_u16( propname, temp );
return true;
}
return false;
}
VectorConfigElem::~VectorConfigElem() {}
bool VectorConfigElem::remove_prop( const char* propname, unsigned short* psval )
{
auto itr = properties.begin(), end = properties.end();
for ( ; itr != end; ++itr )
{
ConfigProperty* prop = *itr;
if ( stricmp( prop->name_.c_str(), propname ) == 0 )
{
*psval = string_to_u16( propname, prop->value_ );
delete prop;
properties.erase( itr );
return true;
}
}
return false;
}
void ConfigElem::warn( const std::string& errmsg ) const
{
if ( _source != nullptr )
_source->display_error( errmsg, false, this, false );
}
void ConfigElem::warn_with_line( const std::string& errmsg ) const
{
if ( _source != nullptr )
_source->display_error( errmsg, true, this, false );
}
void ConfigElem::prop_not_found( const char* propname ) const
{
std::string errmsg( "Property '" );
errmsg += propname;
errmsg += "' was not found.";
throw_error( errmsg );
}
unsigned short ConfigElem::remove_ushort( const char* propname )
{
unsigned short temp;
if ( remove_prop( propname, &temp ) )
{
return temp;
}
else
{
prop_not_found( propname ); // prop_not_found throws
}
}
unsigned short ConfigElem::remove_ushort( const char* propname, unsigned short dflt )
{
unsigned short temp;
if ( remove_prop( propname, &temp ) )
return temp;
else
return dflt;
}
unsigned short ConfigElem::read_ushort( const char* propname, unsigned short dflt )
{
std::string temp;
if ( read_prop( propname, &temp ) )
return string_to_u16( propname, temp );
return dflt;
}
unsigned short ConfigElem::read_ushort( const char* propname )
{
std::string temp;
if ( read_prop( propname, &temp ) )
return string_to_u16( propname, temp );
prop_not_found( propname ); // prop_not_found throws
}
int ConfigElem::read_int( const char* propname, int dflt )
{
std::string temp;
if ( read_prop( propname, &temp ) )
return atoi( temp.c_str() );
return dflt;
}
int ConfigElem::read_int( const char* propname )
{
std::string temp;
if ( read_prop( propname, &temp ) )
return atoi( temp.c_str() );
prop_not_found( propname ); // prop_not_found throws
}
int ConfigElem::remove_int( const char* propname )
{
std::string temp = remove_string( propname );
return atoi( temp.c_str() );
}
int ConfigElem::remove_int( const char* propname, int dflt )
{
std::string temp;
if ( remove_prop( propname, &temp ) )
{
return atoi( temp.c_str() );
}
else
{
return dflt;
}
}
unsigned ConfigElem::remove_unsigned( const char* propname )
{
std::string temp = remove_string( propname );
return strtoul( temp.c_str(), nullptr, 0 ); // TODO check unsigned range
}
unsigned ConfigElem::remove_unsigned( const char* propname, int dflt )
{
std::string temp;
if ( remove_prop( propname, &temp ) )
{
return strtoul( temp.c_str(), nullptr, 0 ); // TODO check unsigned range
}
else
{
return dflt;
}
}
std::string ConfigElem::remove_string( const char* propname )
{
std::string temp;
if ( remove_prop( propname, &temp ) )
{
return temp;
}
else
{
prop_not_found( propname );
return ""; // this is not reached, prop_not_found throws
}
}
std::string ConfigElem::read_string( const char* propname ) const
{
std::string temp;
if ( read_prop( propname, &temp ) )
{
return temp;
}
else
{
prop_not_found( propname );
return ""; // this is not reached, prop_not_found throws
}
}
std::string ConfigElem::read_string( const char* propname, const char* dflt ) const
{
std::string temp;
if ( read_prop( propname, &temp ) )
return temp;
else
return dflt;
}
std::string ConfigElem::remove_string( const char* propname, const char* dflt )
{
std::string temp;
if ( remove_prop( propname, &temp ) )
return temp;
else
return dflt;
}
bool ConfigElem::remove_bool( const char* propname )
{
return remove_ushort( propname ) ? true : false;
}
bool ConfigElem::remove_bool( const char* propname, bool dflt )
{
return remove_ushort( propname, dflt ) ? true : false;
}
float ConfigElem::remove_float( const char* propname, float dflt )
{
std::string tmp;
if ( remove_prop( propname, &tmp ) )
{
return static_cast<float>( strtod( tmp.c_str(), nullptr ) );
}
else
{
return dflt;
}
}
double ConfigElem::remove_double( const char* propname, double dflt )
{
std::string tmp;
if ( remove_prop( propname, &tmp ) )
{
return strtod( tmp.c_str(), nullptr );
}
else
{
return dflt;
}
}
unsigned int ConfigElem::remove_ulong( const char* propname )
{
unsigned int temp;
if ( remove_prop( propname, &temp ) )
{
return temp;
}
else
{
prop_not_found( propname ); // prop_not_found throws
}
}
unsigned int ConfigElem::remove_ulong( const char* propname, unsigned int dflt )
{
unsigned int temp;
if ( remove_prop( propname, &temp ) )
return temp;
else
return dflt;
}
void ConfigElem::clear_prop( const char* propname )
{
unsigned int dummy;
while ( remove_prop( propname, &dummy ) )
continue;
}
void ConfigElem::add_prop( std::string propname, std::string propval )
{
properties.emplace( std::move( propname ), std::move( propval ) );
}
void VectorConfigElem::add_prop( std::string propname, std::string propval )
{
ConfigProperty* prop = new ConfigProperty( std::move( propname ), std::move( propval ) );
properties.push_back( prop );
}
void ConfigElem::add_prop( std::string propname, unsigned short sval )
{
properties.emplace( std::move( propname ), std::to_string( sval ) );
}
void ConfigElem::add_prop( std::string propname, short sval )
{
properties.emplace( std::move( propname ), std::to_string( sval ) );
}
void VectorConfigElem::add_prop( std::string propname, unsigned short sval )
{
auto prop = new ConfigProperty( std::move( propname ), std::to_string( sval ) );
properties.push_back( prop );
}
void ConfigElem::add_prop( std::string propname, unsigned int lval )
{
properties.emplace( std::move( propname ), std::to_string( lval ) );
}
void VectorConfigElem::add_prop( std::string propname, unsigned int lval )
{
auto prop = new ConfigProperty( std::move( propname ), std::to_string( lval ) );
properties.push_back( prop );
}
bool VectorConfigElem::remove_first_prop( std::string* propname, std::string* value )
{
if ( properties.empty() )
return false;
ConfigProperty* prop = properties.front();
*propname = prop->name_;
*value = prop->value_;
delete prop;
properties.erase( properties.begin() );
return true;
}
ConfigFile::ConfigFile( const char* i_filename, const char* allowed_types_str )
: _filename( "<n/a>" ),
_modified( 0 ),
#if CFGFILE_USES_IOSTREAMS
ifs(),
#else
fp( nullptr ),
#endif
_element_line_start( 0 ),
_cur_line( 0 )
{
init( i_filename, allowed_types_str );
}
ConfigFile::ConfigFile( const std::string& i_filename, const char* allowed_types_str )
: _filename( "<n/a>" ),
_modified( 0 ),
#if CFGFILE_USES_IOSTREAMS
ifs(),
#else
fp( nullptr ),
#endif
_element_line_start( 0 ),
_cur_line( 0 )
{
init( i_filename.c_str(), allowed_types_str );
}
void ConfigFile::init( const char* i_filename, const char* allowed_types_str )
{
if ( i_filename )
{
open( i_filename );
}
if ( allowed_types_str != nullptr )
{
ISTRINGSTREAM is( allowed_types_str );
std::string tag;
while ( is >> tag )
{
allowed_types_.insert( tag.c_str() );
}
}
}
const std::string& ConfigFile::filename() const
{
return _filename;
}
time_t ConfigFile::modified() const
{
return _modified;
}
unsigned ConfigFile::element_line_start() const
{
return _element_line_start;
}
void ConfigFile::open( const char* i_filename )
{
_filename = i_filename;
#if CFGFILE_USES_IOSTREAMS
ifs.open( i_filename, ios::in );
if ( !ifs.is_open() )
{
ERROR_PRINT << "Unable to open configuration file " << _filename << "\n";
throw std::runtime_error( std::string( "Unable to open configuration file " ) + _filename );
}
#else
fp = fopen( i_filename, "rt" );
if ( !fp )
{
POLLOG_ERROR << "Unable to open configuration file " << _filename << " " << errno << ": "
<< std::strerror( errno ) << "\n";
throw std::runtime_error( std::string( "Unable to open configuration file " ) + _filename );
}
#endif
struct stat cfgstat;
stat( i_filename, &cfgstat );
_modified = cfgstat.st_mtime;
}
ConfigFile::~ConfigFile()
{
#if !CFGFILE_USES_IOSTREAMS
if ( fp )
fclose( fp );
fp = nullptr;
#endif
}
#if !CFGFILE_USES_IOSTREAMS
char ConfigFile::buffer[1024];
#endif
#if CFGFILE_USES_IOSTREAMS
// returns true if ended on a }, false if ended on EOF.
bool ConfigFile::read_properties( ConfigElem& elem )
{
std::string strbuf;
std::string propname, propvalue;
while ( getline( ifs, strbuf ) ) // get
{
if ( !cur_line )
remove_bom( &strbuf );
++cur_line;
sanitizeUnicodeWithIso( &strbuf );
ISTRINGSTREAM is( strbuf );
splitnamevalue( strbuf, propname, propvalue );
if ( propname.empty() || // empty line
commentline( propname ) )
{
continue;
}
if ( propname == "}" )
return true;
if ( propvalue[0] == '\"' )
convertquotedstring( propvalue );
ConfigProperty* prop = new ConfigProperty( propname, propvalue );
elem.properties.push_back( prop );
}
return false;
}
bool ConfigFile::_read( ConfigElem& elem )
{
while ( !elem.properties.empty() )
{
delete elem.properties.back();
elem.properties.pop_back();
}
elem.type_ = "";
elem.rest_ = "";
element_line_start = 0;
std::string strbuf;
while ( getline( ifs, strbuf ) )
{
if ( !cur_line )
remove_bom( &strbuf );
++cur_line;
sanitizeUnicodeWithIso( &strbuf );
string type, rest;
splitnamevalue( strbuf, type, rest );
if ( type.empty() || // empty line
commentline( type ) )
{
continue;
}
element_line_start = cur_line;
elem.type_ = type;
if ( !allowed_types_.empty() )
{
if ( allowed_types_.find( type.c_str() ) == allowed_types_.end() )
{
e_ostringstream os;
os << "Unexpected type '" << type << "'" << endl;
os << "\tValid types are:";
for ( AllowedTypesCont::const_iterator itr = allowed_types_.begin();
itr != allowed_types_.end(); ++itr )
{
os << " " << ( *itr ).c_str();
}
throw std::runtime_error( os.e_str() );
}
}
elem.rest_ = rest;
if ( !getline( ifs, strbuf ) )
throw std::runtime_error( "File ends after element type -- expected a '{'" );
++cur_line;
sanitizeUnicodeWithIso( &strbuf );
if ( strbuf.empty() || strbuf[0] != '{' )
{
throw std::runtime_error( "Expected '{' on a blank line after element type" );
}
if ( read_properties( elem ) )
return true;
else
throw std::runtime_error( "Expected '}' on a blank line after element properties" );
}
return false;
}
#else
bool ConfigFile::readline( std::string& strbuf )
{
if ( !fgets( buffer, sizeof buffer, fp ) )
return false;
strbuf = "";
do
{
char* nl = strchr( buffer, '\n' );
if ( nl )
{
if ( nl != buffer && *( nl - 1 ) == '\r' )
--nl;
*nl = '\0';
strbuf += buffer;
return true;
}
else
{
strbuf += buffer;
}
} while ( fgets( buffer, sizeof buffer, fp ) );
return true;
}
// returns true if ended on a }, false if ended on EOF.
bool ConfigFile::read_properties( ConfigElem& elem )
{
static std::string strbuf;
static std::string propname, propvalue;
while ( readline( strbuf ) )
{
if ( !_cur_line )
remove_bom( &strbuf );
++_cur_line;
sanitizeUnicodeWithIso( &strbuf );
ISTRINGSTREAM is( strbuf );
splitnamevalue( strbuf, propname, propvalue );
if ( propname.empty() || // empty line
commentline( propname ) )
{
continue;
}
if ( propname == "}" )
return true;
if ( propvalue[0] == '\"' )
{
decodequotedstring( propvalue );
}
elem.properties.emplace( propname, propvalue );
}
return false;
}
bool ConfigFile::read_properties( VectorConfigElem& elem )
{
static std::string strbuf;
static std::string propname, propvalue;
while ( readline( strbuf ) )
{
if ( !_cur_line )
remove_bom( &strbuf );
++_cur_line;
sanitizeUnicodeWithIso( &strbuf );
ISTRINGSTREAM is( strbuf );
splitnamevalue( strbuf, propname, propvalue );
if ( propname.empty() || // empty line
commentline( propname ) )
{
continue;
}
if ( propname == "}" )
return true;
if ( propvalue[0] == '\"' )
{
decodequotedstring( propvalue );
}
auto prop = new ConfigProperty( propname, propvalue );
elem.properties.push_back( prop );
}
return false;
}
bool ConfigFile::_read( ConfigElem& elem )
{
elem.properties.clear();
elem.type_ = "";
elem.rest_ = "";
_element_line_start = 0;
std::string strbuf;
while ( readline( strbuf ) )
{
if ( !_cur_line )
remove_bom( &strbuf );
++_cur_line;
sanitizeUnicodeWithIso( &strbuf );
std::string type, rest;
splitnamevalue( strbuf, type, rest );
if ( type.empty() || // empty line
commentline( type ) )
{
continue;
}
_element_line_start = _cur_line;
elem.type_ = type;
if ( !allowed_types_.empty() )
{
if ( allowed_types_.find( type.c_str() ) == allowed_types_.end() )
{
OSTRINGSTREAM os;
os << "Unexpected type '" << type << "'" << std::endl;
os << "\tValid types are:";
for ( const auto& allowed : allowed_types_ )
{
os << " " << allowed.c_str();
}
throw std::runtime_error( OSTRINGSTREAM_STR( os ) );
}
}
elem.rest_ = rest;
if ( !fgets( buffer, sizeof buffer, fp ) )
throw std::runtime_error( "File ends after element type -- expected a '{'" );
strbuf = buffer;
++_cur_line;
sanitizeUnicodeWithIso( &strbuf );
if ( strbuf.empty() || strbuf[0] != '{' )
{
throw std::runtime_error( "Expected '{' on a blank line after element type" );
}
if ( read_properties( elem ) )
return true;
else
throw std::runtime_error( "Expected '}' on a blank line after element properties" );
}
return false;
}
bool ConfigFile::_read( VectorConfigElem& elem )
{
while ( !elem.properties.empty() )
{
delete elem.properties.back();
elem.properties.pop_back();
}
elem.type_ = "";
elem.rest_ = "";
_element_line_start = 0;
std::string strbuf;
while ( readline( strbuf ) )
{
if ( !_cur_line )
remove_bom( &strbuf );
++_cur_line;
sanitizeUnicodeWithIso( &strbuf );
std::string type, rest;
splitnamevalue( strbuf, type, rest );
if ( type.empty() || // empty line
commentline( type ) )
{
continue;
}
_element_line_start = _cur_line;
elem.type_ = type;
if ( !allowed_types_.empty() )
{
if ( allowed_types_.find( type.c_str() ) == allowed_types_.end() )
{
OSTRINGSTREAM os;
os << "Unexpected type '" << type << "'" << std::endl;
os << "\tValid types are:";
for ( const auto& allowed : allowed_types_ )
{
os << " " << allowed.c_str();
}
throw std::runtime_error( OSTRINGSTREAM_STR( os ) );
}
}
elem.rest_ = rest;
if ( !fgets( buffer, sizeof buffer, fp ) )
throw std::runtime_error( "File ends after element type -- expected a '{'" );
strbuf = buffer;
++_cur_line;
sanitizeUnicodeWithIso( &strbuf );
if ( strbuf.empty() || strbuf[0] != '{' )
{
throw std::runtime_error( "Expected '{' on a blank line after element type" );
}
if ( read_properties( elem ) )
return true;
else
throw std::runtime_error( "Expected '}' on a blank line after element properties" );
}
return false;
}
#endif
void ConfigFile::display_error( const std::string& msg, bool show_curline,
const ConfigElemBase* elem, bool error ) const
{
bool showed_elem_line = false;
fmt::Writer tmp;
tmp << ( error ? "Error" : "Warning" ) << " reading configuration file " << _filename << ":\n"
<< "\t" << msg << "\n";
if ( elem != nullptr )
{
if ( strlen( elem->type() ) > 0 )
{
tmp << "\tElement: " << elem->type() << " " << elem->rest();
if ( _element_line_start )
tmp << ", found on line " << _element_line_start;
tmp << "\n";
showed_elem_line = true;
}
}
if ( show_curline )
tmp << "\tNear line: " << _cur_line << "\n";
if ( _element_line_start && !showed_elem_line )
tmp << "\tElement started on line: " << _element_line_start << "\n";
ERROR_PRINT << tmp.str();
}
void ConfigFile::display_and_rethrow_exception()
{
try
{
throw;
}
catch ( const char* msg )
{
display_error( msg );
}
catch ( std::string& str )
{
display_error( str );
}
catch ( std::exception& ex )
{
display_error( ex.what() );
}
catch ( ... )
{
display_error( "(Generic exception)" );
}
throw std::runtime_error( "Configuration file error." );
}
bool ConfigFile::read( ConfigElem& elem )
{
try
{
elem._source = this;
return _read( elem );
}
catch ( ... )
{
display_and_rethrow_exception(); // throws and doesn't return
}
}
void ConfigFile::readraw( ConfigElem& elem )
{
try
{
elem._source = this;
if ( read_properties( elem ) )
throw std::runtime_error( "unexpected '}' in file" );
}
catch ( ... )
{
display_and_rethrow_exception();
}
}
void StubConfigSource::display_error( const std::string& msg, bool /*show_curline*/,
const ConfigElemBase* /*elem*/, bool error ) const
{
ERROR_PRINT << ( error ? "Error" : "Warning" ) << " reading configuration element:"
<< "\t" << msg << "\n";
}
} // namespace Clib
} // namespace Pol