summaryrefslogtreecommitdiff
path: root/lib/common/Configuration.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/common/Configuration.cpp')
-rwxr-xr-xlib/common/Configuration.cpp741
1 files changed, 741 insertions, 0 deletions
diff --git a/lib/common/Configuration.cpp b/lib/common/Configuration.cpp
new file mode 100755
index 00000000..def93571
--- /dev/null
+++ b/lib/common/Configuration.cpp
@@ -0,0 +1,741 @@
+// --------------------------------------------------------------------------
+//
+// File
+// Name: Configuration.cpp
+// Purpose: Reading configuration files
+// Created: 2003/07/23
+//
+// --------------------------------------------------------------------------
+
+#include "Box.h"
+
+#include <stdlib.h>
+#include <limits.h>
+
+#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> 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<O_RDONLY> file(Filename);
+
+ // GetLine object
+ FdGetLine getline(file);
+
+ // Object to create
+ Configuration *pconfig = new Configuration(std::string("<root>"));
+
+ 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<Configuration>(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<Configuration>(0);
+ }
+ }
+ }
+ catch(...)
+ {
+ // Clean up
+ delete pconfig;
+ pconfig = 0;
+ throw;
+ }
+
+ // Success. Return result.
+ return std::auto_ptr<Configuration>(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<std::string, Configuration>(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<std::string, std::string>::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<std::string, std::string>::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<std::string, std::string>::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<std::string> Configuration::GetKeyNames() const
+{
+ std::map<std::string, std::string>::const_iterator i(mKeys.begin());
+
+ std::vector<std::string> 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<std::pair<std::string, Configuration> >::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<std::pair<std::string, Configuration> >::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<std::string> Configuration::GetSubConfigurationNames() const
+{
+ std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin());
+
+ std::vector<std::string> 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<std::string, std::string>::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<std::pair<std::string, Configuration> >::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;
+}
+
+