// -------------------------------------------------------------------------- // // File // Name: Configuration.cpp // Purpose: Reading configuration files // Created: 2003/07/23 // // -------------------------------------------------------------------------- #include "Box.h" #include #include #include "Configuration.h" #include "CommonException.h" #include "Guards.h" #include "FdGetLine.h" #include "MemLeakFindOn.h" // utility whitespace function inline bool iw(int c) { return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded } // boolean values static const char *sValueBooleanStrings[] = {"yes", "true", "no", "false", 0}; static const bool sValueBooleanValue[] = {true, true, false, false}; // -------------------------------------------------------------------------- // // Function // Name: Configuration::Configuration(const std::string &) // Purpose: Constructor // Created: 2003/07/23 // // -------------------------------------------------------------------------- Configuration::Configuration(const std::string &rName) : mName(rName) { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::Configuration(const Configuration &) // Purpose: Copy constructor // Created: 2003/07/23 // // -------------------------------------------------------------------------- Configuration::Configuration(const Configuration &rToCopy) : mName(rToCopy.mName), mSubConfigurations(rToCopy.mSubConfigurations), mKeys(rToCopy.mKeys) { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::~Configuration() // Purpose: Destructor // Created: 2003/07/23 // // -------------------------------------------------------------------------- Configuration::~Configuration() { } // -------------------------------------------------------------------------- // // Function // Name: Configuration::LoadAndVerify(const std::string &, const ConfigurationVerify *, std::string &) // Purpose: Loads a configuration file from disc, checks it. Returns NULL if it was faulting, in which // case they'll be an error message. // Created: 2003/07/23 // // -------------------------------------------------------------------------- std::auto_ptr Configuration::LoadAndVerify(const char *Filename, const ConfigurationVerify *pVerify, std::string &rErrorMsg) { // Check arguments if(Filename == 0) { THROW_EXCEPTION(CommonException, BadArguments) } // Just to make sure rErrorMsg.erase(); // Open the file FileHandleGuard file(Filename); // GetLine object FdGetLine getline(file); // Object to create Configuration *pconfig = new Configuration(std::string("")); try { // Load LoadInto(*pconfig, getline, rErrorMsg, true); if(!rErrorMsg.empty()) { // An error occured, return now //TRACE1("Error message from LoadInto: %s", rErrorMsg.c_str()); TRACE0("Error at Configuration::LoadInfo\n"); delete pconfig; pconfig = 0; return std::auto_ptr(0); } // Verify? if(pVerify) { if(!Verify(*pconfig, *pVerify, std::string(), rErrorMsg)) { //TRACE1("Error message from Verify: %s", rErrorMsg.c_str()); TRACE0("Error at Configuration::Verify\n"); delete pconfig; pconfig = 0; return std::auto_ptr(0); } } } catch(...) { // Clean up delete pconfig; pconfig = 0; throw; } // Success. Return result. return std::auto_ptr(pconfig); } // -------------------------------------------------------------------------- // // Function // Name: LoadInto(Configuration &, FdGetLine &, std::string &, bool) // Purpose: Private. Load configuration information from the file into the config object. // Returns 'abort' flag, if error, will be appended to rErrorMsg. // Created: 2003/07/24 // // -------------------------------------------------------------------------- bool Configuration::LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel) { bool startBlockExpected = false; std::string blockName; //TRACE1("BLOCK: |%s|\n", rConfig.mName.c_str()); while(!rGetLine.IsEOF()) { std::string line(rGetLine.GetLine(true)); /* preprocess out whitespace and comments */ if(line.empty()) { // Ignore blank lines continue; } // Line an open block string? if(line == "{") { if(startBlockExpected) { // New config object Configuration config(blockName); // Continue processing into this block if(!LoadInto(config, rGetLine, rErrorMsg, false)) { // Abort error return false; } startBlockExpected = false; // Store... rConfig.mSubConfigurations.push_back(std::pair(blockName, config)); } else { rErrorMsg += "Unexpected start block in " + rConfig.mName + "\n"; } } else { // Close block? if(line == "}") { if(RootLevel) { // error -- root level doesn't have a close rErrorMsg += "Root level has close block -- forget to terminate subblock?\n"; // but otherwise ignore } else { //TRACE0("ENDBLOCK\n"); return true; // All very good and nice } } // Either a key, or a sub block beginning else { // Can't be a start block if(startBlockExpected) { rErrorMsg += "Block " + blockName + " wasn't started correctly (no '{' on line of it's own)\n"; startBlockExpected = false; } // Has the line got an = in it? unsigned int equals = 0; for(; equals < line.size(); ++equals) { if(line[equals] == '=') { // found! break; } } if(equals < line.size()) { // Make key value pair unsigned int keyend = equals; while(keyend > 0 && iw(line[keyend-1])) { keyend--; } unsigned int valuestart = equals+1; while(valuestart < line.size() && iw(line[valuestart])) { valuestart++; } if(keyend > 0 && valuestart <= line.size()) { std::string key(line.substr(0, keyend)); std::string value(line.substr(valuestart)); //TRACE2("KEY: |%s|=|%s|\n", key.c_str(), value.c_str()); // Check for duplicate values if(rConfig.mKeys.find(key) != rConfig.mKeys.end()) { // Multi-values allowed here, but checked later on rConfig.mKeys[key] += MultiValueSeparator; rConfig.mKeys[key] += value; } else { // Store rConfig.mKeys[key] = value; } } else { rErrorMsg += "Invalid key in block "+rConfig.mName+"\n"; } } else { // Start of sub block blockName = line; startBlockExpected = true; } } } } // End of file? if(!RootLevel && rGetLine.IsEOF()) { // Error if EOF and this isn't the root level rErrorMsg += "File ended without terminating all subblocks\n"; } return true; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::KeyExists(const char *) // Purpose: Checks to see if a key exists // Created: 2003/07/23 // // -------------------------------------------------------------------------- bool Configuration::KeyExists(const char *pKeyName) const { if(pKeyName == 0) {THROW_EXCEPTION(CommonException, BadArguments)} return mKeys.find(pKeyName) != mKeys.end(); } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValue(const char *) // Purpose: Returns the value of a configuration variable // Created: 2003/07/23 // // -------------------------------------------------------------------------- const std::string &Configuration::GetKeyValue(const char *pKeyName) const { if(pKeyName == 0) {THROW_EXCEPTION(CommonException, BadArguments)} std::map::const_iterator i(mKeys.find(pKeyName)); if(i == mKeys.end()) { THROW_EXCEPTION(CommonException, ConfigNoKey) } else { return i->second; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValueInt(const char *) // Purpose: Gets a key value as an integer // Created: 2003/07/23 // // -------------------------------------------------------------------------- int Configuration::GetKeyValueInt(const char *pKeyName) const { if(pKeyName == 0) {THROW_EXCEPTION(CommonException, BadArguments)} std::map::const_iterator i(mKeys.find(pKeyName)); if(i == mKeys.end()) { THROW_EXCEPTION(CommonException, ConfigNoKey) } else { long value = ::strtol((i->second).c_str(), NULL, 0 /* C style handling */); if(value == LONG_MAX || value == LONG_MIN) { THROW_EXCEPTION(CommonException, ConfigBadIntValue) } return (int)value; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyValueBool(const char *) const // Purpose: Gets a key value as a boolean // Created: 17/2/04 // // -------------------------------------------------------------------------- bool Configuration::GetKeyValueBool(const char *pKeyName) const { if(pKeyName == 0) {THROW_EXCEPTION(CommonException, BadArguments)} std::map::const_iterator i(mKeys.find(pKeyName)); if(i == mKeys.end()) { THROW_EXCEPTION(CommonException, ConfigNoKey) } else { bool value = false; // Anything this is called for should have been verified as having a correct // string in the verification section. However, this does default to false // if it isn't in the string table. for(int l = 0; sValueBooleanStrings[l] != 0; ++l) { if(::strcasecmp((i->second).c_str(), sValueBooleanStrings[l]) == 0) { // Found. value = sValueBooleanValue[l]; break; } } return value; } } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetKeyNames() // Purpose: Returns list of key names // Created: 2003/07/24 // // -------------------------------------------------------------------------- std::vector Configuration::GetKeyNames() const { std::map::const_iterator i(mKeys.begin()); std::vector r; for(; i != mKeys.end(); ++i) { r.push_back(i->first); } return r; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::SubConfigurationExists(const char *) // Purpose: Checks to see if a sub configuration exists // Created: 2003/07/23 // // -------------------------------------------------------------------------- bool Configuration::SubConfigurationExists(const char *pSubName) const { if(pSubName == 0) {THROW_EXCEPTION(CommonException, BadArguments)} // Attempt to find it... std::list >::const_iterator i(mSubConfigurations.begin()); for(; i != mSubConfigurations.end(); ++i) { // This the one? if(i->first == pSubName) { // Yes. return true; } } // didn't find it. return false; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetSubConfiguration(const char *) // Purpose: Gets a sub configuration // Created: 2003/07/23 // // -------------------------------------------------------------------------- const Configuration &Configuration::GetSubConfiguration(const char *pSubName) const { if(pSubName == 0) {THROW_EXCEPTION(CommonException, BadArguments)} // Attempt to find it... std::list >::const_iterator i(mSubConfigurations.begin()); for(; i != mSubConfigurations.end(); ++i) { // This the one? if(i->first == pSubName) { // Yes. return i->second; } } THROW_EXCEPTION(CommonException, ConfigNoSubConfig) } // -------------------------------------------------------------------------- // // Function // Name: Configuration::GetSubConfigurationNames() // Purpose: Return list of sub configuration names // Created: 2003/07/24 // // -------------------------------------------------------------------------- std::vector Configuration::GetSubConfigurationNames() const { std::list >::const_iterator i(mSubConfigurations.begin()); std::vector r; for(; i != mSubConfigurations.end(); ++i) { r.push_back(i->first); } return r; } // -------------------------------------------------------------------------- // // Function // Name: Configuration::Verify(const Configuration &, const ConfigurationVerify &, const std::string &, std::string &) // Purpose: Return list of sub configuration names // Created: 2003/07/24 // // -------------------------------------------------------------------------- bool Configuration::Verify(Configuration &rConfig, const ConfigurationVerify &rVerify, const std::string &rLevel, std::string &rErrorMsg) { bool ok = true; // First... check the keys if(rVerify.mpKeys != 0) { const ConfigurationVerifyKey *pvkey = rVerify.mpKeys; bool todo = true; do { // Can the key be found? ASSERT(pvkey->mpName); if(rConfig.KeyExists(pvkey->mpName)) { // Get value const std::string &rval = rConfig.GetKeyValue(pvkey->mpName); const char *val = rval.c_str(); // Check it's a number? if((pvkey->Tests & ConfigTest_IsInt) == ConfigTest_IsInt) { // Test it... char *end; long r = ::strtol(val, &end, 0); if(r == LONG_MIN || r == LONG_MAX || end != (val + rval.size())) { // not a good value ok = false; rErrorMsg += rLevel + rConfig.mName +"." + pvkey->mpName + " (key) is not a valid integer.\n"; } } // Check it's a bool? if((pvkey->Tests & ConfigTest_IsBool) == ConfigTest_IsBool) { // See if it's one of the allowed strings. bool found = false; for(int l = 0; sValueBooleanStrings[l] != 0; ++l) { if(::strcasecmp(val, sValueBooleanStrings[l]) == 0) { // Found. found = true; break; } } // Error if it's not one of them. if(!found) { ok = false; rErrorMsg += rLevel + rConfig.mName +"." + pvkey->mpName + " (key) is not a valid boolean value.\n"; } } // Check for multi valued statments where they're not allowed if((pvkey->Tests & ConfigTest_MultiValueAllowed) == 0) { // Check to see if this key is a multi-value -- it shouldn't be if(rval.find(MultiValueSeparator) != rval.npos) { ok = false; rErrorMsg += rLevel + rConfig.mName +"." + pvkey->mpName + " (key) multi value not allowed (duplicated key?).\n"; } } } else { // Is it required to exist? if((pvkey->Tests & ConfigTest_Exists) == ConfigTest_Exists) { // Should exist, but doesn't. ok = false; rErrorMsg += rLevel + rConfig.mName + "." + pvkey->mpName + " (key) is missing.\n"; } else if(pvkey->mpDefaultValue) { rConfig.mKeys[std::string(pvkey->mpName)] = std::string(pvkey->mpDefaultValue); } } if((pvkey->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) { // No more! todo = false; } // next pvkey++; } while(todo); // Check for additional keys for(std::map::const_iterator i = rConfig.mKeys.begin(); i != rConfig.mKeys.end(); ++i) { // Is the name in the list? const ConfigurationVerifyKey *scan = rVerify.mpKeys; bool found = false; while(scan) { if(scan->mpName == i->first) { found = true; break; } // Next? if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) { break; } scan++; } if(!found) { // Shouldn't exist, but does. ok = false; rErrorMsg += rLevel + rConfig.mName + "." + i->first + " (key) is not a known key. Check spelling and placement.\n"; } } } // Then the sub configurations if(rVerify.mpSubConfigurations) { // Find the wildcard entry, if it exists, and check that required subconfigs are there const ConfigurationVerify *wildcardverify = 0; const ConfigurationVerify *scan = rVerify.mpSubConfigurations; while(scan) { ASSERT(scan->mpName); if(scan->mpName[0] == '*') { wildcardverify = scan; } // Required? if((scan->Tests & ConfigTest_Exists) == ConfigTest_Exists) { if(scan->mpName[0] == '*') { // Check something exists if(rConfig.mSubConfigurations.size() < 1) { // A sub config should exist, but doesn't. ok = false; rErrorMsg += rLevel + rConfig.mName + ".* (block) is missing (a block must be present).\n"; } } else { // Check real thing exists if(!rConfig.SubConfigurationExists(scan->mpName)) { // Should exist, but doesn't. ok = false; rErrorMsg += rLevel + rConfig.mName + "." + scan->mpName + " (block) is missing.\n"; } } } // Next? if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) { break; } scan++; } // Go through the sub configurations, one by one for(std::list >::const_iterator i(rConfig.mSubConfigurations.begin()); i != rConfig.mSubConfigurations.end(); ++i) { // Can this be found? const ConfigurationVerify *subverify = 0; const ConfigurationVerify *scan = rVerify.mpSubConfigurations; const char *name = i->first.c_str(); ASSERT(name); while(scan) { if(strcmp(scan->mpName, name) == 0) { // found it! subverify = scan; } // Next? if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) { break; } scan++; } // Use wildcard? if(subverify == 0) { subverify = wildcardverify; } // Verify if(subverify) { // override const-ness here... if(!Verify((Configuration&)i->second, *subverify, rConfig.mName + '.', rErrorMsg)) { ok = false; } } } } return ok; }