| /* |
| * Copyright 2008, 2009, 2010 Free Software Foundation, Inc. |
| * Copyright 2010 Kestrel Signal Processing, Inc. |
| * Copyright 2011, 2012 Range Networks, Inc. |
| * |
| * |
| * This software is distributed under the terms of the GNU Affero Public License. |
| * See the COPYING file in the main directory for details. |
| * |
| * This use of this software may be subject to additional restrictions. |
| * See the LEGAL file in the main directory for details. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published by |
| the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| */ |
| |
| |
| #include "Configuration.h" |
| #include "Logger.h" |
| #include <fstream> |
| #include <iostream> |
| #include <string.h> |
| |
| #ifdef DEBUG_CONFIG |
| #define debugLogEarly gLogEarly |
| #else |
| #define debugLogEarly(x,y,z) |
| #endif |
| |
| |
| using namespace std; |
| |
| char gCmdName[20] = {0}; // Use a char* to avoid avoid static initialization of string, and race at startup. |
| |
| static const char* createConfigTable = { |
| "CREATE TABLE IF NOT EXISTS CONFIG (" |
| "KEYSTRING TEXT UNIQUE NOT NULL, " |
| "VALUESTRING TEXT, " |
| "STATIC INTEGER DEFAULT 0, " |
| "OPTIONAL INTEGER DEFAULT 0, " |
| "COMMENTS TEXT DEFAULT ''" |
| ")" |
| }; |
| |
| static std::string replaceAll(const std::string input, const std::string search, const std::string replace) |
| { |
| std::string output = input; |
| size_t index = 0; |
| |
| while (true) { |
| index = output.find(search, index); |
| if (index == std::string::npos) { |
| break; |
| } |
| |
| output.replace(index, replace.length(), replace); |
| index += replace.length(); |
| } |
| |
| return output; |
| } |
| |
| |
| float ConfigurationRecord::floatNumber() const |
| { |
| float val; |
| sscanf(mValue.c_str(),"%f",&val); |
| return val; |
| } |
| |
| |
| ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema) |
| { |
| gLogEarly(LOG_INFO, "opening configuration table from path %s", filename); |
| // Connect to the database. |
| int rc = sqlite3_open(filename,&mDB); |
| // (pat) When I used malloc here, sqlite3 sporadically crashes. |
| if (wCmdName) { |
| strncpy(gCmdName,wCmdName,18); |
| gCmdName[18] = 0; |
| strcat(gCmdName,":"); |
| } |
| if (rc) { |
| gLogEarly(LOG_EMERG, "cannot open configuration database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); |
| sqlite3_close(mDB); |
| mDB = NULL; |
| return; |
| } |
| // Create the table, if needed. |
| if (!sqlite3_command(mDB,createConfigTable)) { |
| gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB)); |
| } |
| |
| // Build CommonLibs schema |
| ConfigurationKey *tmp; |
| tmp = new ConfigurationKey("Log.Alarms.Max","20", |
| "alarms", |
| ConfigurationKey::CUSTOMER, |
| ConfigurationKey::VALRANGE, |
| "10:20",// educated guess |
| false, |
| "Maximum number of alarms to remember inside the application." |
| ); |
| mSchema[tmp->getName()] = *tmp; |
| delete tmp; |
| |
| tmp = new ConfigurationKey("Log.File","", |
| "", |
| ConfigurationKey::DEVELOPER, |
| ConfigurationKey::FILEPATH_OPT,// audited |
| "", |
| false, |
| "Path to use for textfile based logging. " |
| "By default, this feature is disabled. " |
| "To enable, specify an absolute path to the file you wish to use, eg: /tmp/my-debug.log. " |
| "To disable again, execute \"unconfig Log.File\"." |
| ); |
| mSchema[tmp->getName()] = *tmp; |
| delete tmp; |
| |
| tmp = new ConfigurationKey("Log.Level","NOTICE", |
| "", |
| ConfigurationKey::CUSTOMER, |
| ConfigurationKey::CHOICE, |
| "EMERG|EMERGENCY - report serious faults associated with service failure or hardware damage," |
| "ALERT|ALERT - report likely service disruption caused by misconfiguration or poor connectivity," |
| "CRIT|CRITICAL - report anomalous events that are likely to degrade service," |
| "ERR|ERROR - report internal errors of the software that may result in degradation of service in unusual circumstances," |
| "WARNING|WARNING - report anomalous events that may indicate a degradation of normal service," |
| "NOTICE|NOTICE - report anomalous events that probably do not affect service but may be of interest to network operators," |
| "INFO|INFORMATION - report normal events," |
| "DEBUG|DEBUG - only for use by developers and will degrade system performance", |
| false, |
| "Default logging level when no other level is defined for a file." |
| ); |
| mSchema[tmp->getName()] = *tmp; |
| delete tmp; |
| |
| // Add application specific schema |
| mSchema.insert(wSchema.begin(), wSchema.end()); |
| |
| // Init the cross checking callback to something predictable |
| mCrossCheck = NULL; |
| } |
| |
| string ConfigurationTable::getDefaultSQL(const std::string& program, const std::string& version) |
| { |
| stringstream ss; |
| ConfigurationKeyMap::iterator mp; |
| |
| ss << "--" << endl; |
| ss << "-- This file was generated using: " << program << " --gensql" << endl; |
| ss << "-- binary version: " << version << endl; |
| ss << "--" << endl; |
| ss << "-- Future changes should not be put in this file directly but" << endl; |
| ss << "-- rather in the program's ConfigurationKey schema." << endl; |
| ss << "--" << endl; |
| ss << "PRAGMA foreign_keys=OFF;" << endl; |
| ss << "BEGIN TRANSACTION;" << endl; |
| ss << "CREATE TABLE CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT '');" << endl; |
| |
| mp = mSchema.begin(); |
| while (mp != mSchema.end()) { |
| ss << "INSERT INTO \"CONFIG\" VALUES("; |
| // name |
| ss << "'" << mp->first << "',"; |
| // default |
| ss << "'" << mp->second.getDefaultValue() << "',"; |
| // static |
| if (mp->second.isStatic()) { |
| ss << "1"; |
| } else { |
| ss << "0"; |
| } |
| ss << ","; |
| // optional |
| ss << "0,"; |
| // description |
| ss << "'"; |
| if (mp->second.getType() == ConfigurationKey::BOOLEAN) { |
| ss << "1=enabled, 0=disabled - "; |
| } |
| ss << mp->second.getDescription(); |
| if (mp->second.isStatic()) { |
| ss << " Static."; |
| } |
| ss << "'"; |
| ss << ");" << endl; |
| mp++; |
| } |
| |
| ss << "COMMIT;" << endl; |
| ss << endl; |
| |
| return ss.str(); |
| } |
| |
| string ConfigurationTable::getTeX(const std::string& program, const std::string& version) |
| { |
| stringstream ss; |
| ConfigurationKeyMap::iterator mp; |
| |
| ss << "% START AUTO-GENERATED CONTENT" << endl; |
| ss << "% -- these sections were generated using: " << program << " --gentex" << endl; |
| ss << "% -- binary version: " << version << endl; |
| |
| ss << "\\subsection{Customer Site Parameters}" << endl; |
| ss << "These parameters must be changed to fit your site." << endl; |
| ss << "\\begin{itemize}" << endl; |
| mp = mSchema.begin(); |
| while (mp != mSchema.end()) { |
| if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) { |
| ss << " \\item "; |
| // name |
| ss << mp->first << " -- "; |
| // description |
| ss << mp->second.getDescription(); |
| ss << endl; |
| } |
| mp++; |
| } |
| ss << "\\end{itemize}" << endl; |
| ss << endl; |
| |
| ss << "\\subsection{Customer Tuneable Parameters}" << endl; |
| ss << "These parameters can be changed to optimize your site." << endl; |
| ss << "\\begin{itemize}" << endl; |
| mp = mSchema.begin(); |
| while (mp != mSchema.end()) { |
| if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE && |
| ( |
| mp->second.getVisibility() == ConfigurationKey::CUSTOMER || |
| mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE || |
| mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN |
| )) { |
| ss << " \\item "; |
| // name |
| ss << mp->first << " -- "; |
| // description |
| ss << mp->second.getDescription(); |
| ss << endl; |
| } |
| mp++; |
| } |
| ss << "\\end{itemize}" << endl; |
| ss << endl; |
| |
| ss << "\\subsection{Developer/Factory Parameters}" << endl; |
| ss << "These parameters should only be changed by when developing new code." << endl; |
| ss << "\\begin{itemize}" << endl; |
| mp = mSchema.begin(); |
| while (mp != mSchema.end()) { |
| if (mp->second.getVisibility() == ConfigurationKey::FACTORY || |
| mp->second.getVisibility() == ConfigurationKey::DEVELOPER) { |
| ss << " \\item "; |
| // name |
| ss << mp->first << " -- "; |
| // description |
| ss << mp->second.getDescription(); |
| ss << endl; |
| } |
| mp++; |
| } |
| ss << "\\end{itemize}" << endl; |
| ss << "% END AUTO-GENERATED CONTENT" << endl; |
| ss << endl; |
| |
| string tmp = replaceAll(ss.str(), "^", "\\^"); |
| return replaceAll(tmp, "_", "\\_"); |
| } |
| |
| bool ConfigurationTable::defines(const string& key) |
| { |
| try { |
| ScopedLock lock(mLock); |
| return lookup(key).defined(); |
| } catch (ConfigurationTableKeyNotFound) { |
| debugLogEarly(LOG_ALERT, "configuration parameter %s not found", key.c_str()); |
| return false; |
| } |
| } |
| |
| bool ConfigurationTable::keyDefinedInSchema(const std::string& name) |
| { |
| return mSchema.find(name) == mSchema.end() ? false : true; |
| } |
| |
| bool ConfigurationTable::isValidValue(const std::string& name, const std::string& val) { |
| bool ret = false; |
| |
| ConfigurationKey key = mSchema[name]; |
| |
| switch (key.getType()) { |
| case ConfigurationKey::BOOLEAN: { |
| if (val == "1" || val == "0") { |
| ret = true; |
| } |
| break; |
| } |
| |
| case ConfigurationKey::CHOICE_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::CHOICE: { |
| int startPos = -1; |
| uint endPos = 0; |
| |
| std::string tmp = key.getValidValues(); |
| |
| do { |
| startPos++; |
| if ((endPos = tmp.find('|', startPos)) != std::string::npos) { |
| if (val == tmp.substr(startPos, endPos-startPos)) { |
| ret = true; |
| break; |
| } |
| } else { |
| if (val == tmp.substr(startPos, tmp.find(',', startPos)-startPos)) { |
| ret = true; |
| break; |
| } |
| } |
| |
| } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos); |
| break; |
| } |
| |
| case ConfigurationKey::CIDR_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::CIDR: { |
| uint delimiter; |
| std::string ip; |
| int cidr = -1; |
| |
| delimiter = val.find('/'); |
| if (delimiter != std::string::npos) { |
| ip = val.substr(0, delimiter); |
| std::stringstream(val.substr(delimiter+1)) >> cidr; |
| if (ConfigurationKey::isValidIP(ip) && 0 <= cidr && cidr <= 32) { |
| ret = true; |
| } |
| } |
| break; |
| } |
| |
| case ConfigurationKey::FILEPATH_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::FILEPATH: { |
| regex_t r; |
| const char* expression = "^[a-zA-Z0-9/_.-]+$"; |
| int result = regcomp(&r, expression, REG_EXTENDED); |
| if (result) { |
| char msg[256]; |
| regerror(result,&r,msg,255); |
| break;//abort(); |
| } |
| if (regexec(&r, val.c_str(), 0, NULL, 0)==0) { |
| ret = true; |
| } |
| regfree(&r); |
| break; |
| } |
| |
| case ConfigurationKey::IPADDRESS_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::IPADDRESS: { |
| ret = ConfigurationKey::isValidIP(val); |
| break; |
| } |
| |
| case ConfigurationKey::IPANDPORT: { |
| uint delimiter; |
| std::string ip; |
| int port = -1; |
| |
| delimiter = val.find(':'); |
| if (delimiter != std::string::npos) { |
| ip = val.substr(0, delimiter); |
| std::stringstream(val.substr(delimiter+1)) >> port; |
| if (ConfigurationKey::isValidIP(ip) && 1 <= port && port <= 65535) { |
| ret = true; |
| } |
| } |
| break; |
| } |
| |
| case ConfigurationKey::MIPADDRESS_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::MIPADDRESS: { |
| int startPos = -1; |
| uint endPos = 0; |
| |
| do { |
| startPos++; |
| endPos = val.find(' ', startPos); |
| if (ConfigurationKey::isValidIP(val.substr(startPos, endPos-startPos))) { |
| ret = true; |
| } else { |
| ret = false; |
| break; |
| } |
| |
| } while ((startPos = endPos) != (int)std::string::npos); |
| break; |
| } |
| |
| case ConfigurationKey::PORT_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::PORT: { |
| int intVal; |
| |
| std::stringstream(val) >> intVal; |
| |
| if (1 <= intVal && intVal <= 65535) { |
| ret = true; |
| } |
| break; |
| } |
| |
| case ConfigurationKey::REGEX_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::REGEX: { |
| regex_t r; |
| const char* expression = val.c_str(); |
| int result = regcomp(&r, expression, REG_EXTENDED); |
| if (result) { |
| char msg[256]; |
| regerror(result,&r,msg,255); |
| } else { |
| ret = true; |
| } |
| regfree(&r); |
| break; |
| } |
| |
| case ConfigurationKey::STRING_OPT: { |
| if (val.length() == 0) { |
| ret = true; |
| break; |
| } |
| } |
| case ConfigurationKey::STRING: { |
| regex_t r; |
| const char* expression = key.getValidValues().c_str(); |
| int result = regcomp(&r, expression, REG_EXTENDED); |
| if (result) { |
| char msg[256]; |
| regerror(result,&r,msg,255); |
| break;//abort(); |
| } |
| if (regexec(&r, val.c_str(), 0, NULL, 0)==0) { |
| ret = true; |
| } |
| regfree(&r); |
| break; |
| } |
| |
| case ConfigurationKey::VALRANGE: { |
| regex_t r; |
| int result; |
| if (key.getValidValues().find('.') != std::string::npos) { |
| result = regcomp(&r, "^[0-9.-]+$", REG_EXTENDED); |
| } else { |
| result = regcomp(&r, "^[0-9-]+$", REG_EXTENDED); |
| } |
| if (result) { |
| char msg[256]; |
| regerror(result,&r,msg,255); |
| break;//abort(); |
| } |
| if (regexec(&r, val.c_str(), 0, NULL, 0)!=0) { |
| ret = false; |
| } else if (key.getValidValues().find('.') != std::string::npos) { |
| ret = ConfigurationKey::isInValRange<float>(key, val, false); |
| } else { |
| ret = ConfigurationKey::isInValRange<int>(key, val, true); |
| } |
| |
| regfree(&r); |
| break; |
| } |
| } |
| |
| return ret; |
| } |
| |
| ConfigurationKeyMap ConfigurationTable::getSimilarKeys(const std::string& snippet) { |
| ConfigurationKeyMap tmp; |
| |
| ConfigurationKeyMap::const_iterator mp = mSchema.begin(); |
| while (mp != mSchema.end()) { |
| if (mp->first.find(snippet) != std::string::npos) { |
| tmp[mp->first] = mp->second; |
| } |
| mp++; |
| } |
| |
| return tmp; |
| } |
| |
| const ConfigurationRecord& ConfigurationTable::lookup(const string& key) |
| { |
| assert(mDB); |
| checkCacheAge(); |
| // We assume the caller holds mLock. |
| // So it is OK to return a reference into the cache. |
| |
| // Check the cache. |
| // This is cheap. |
| ConfigurationMap::const_iterator where = mCache.find(key); |
| if (where!=mCache.end()) { |
| if (where->second.defined()) return where->second; |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| |
| // Check the database. |
| // This is more expensive. |
| char *value = NULL; |
| sqlite3_single_lookup(mDB,"CONFIG", |
| "KEYSTRING",key.c_str(),"VALUESTRING",value); |
| |
| // value found, cache the result |
| if (value) { |
| mCache[key] = ConfigurationRecord(value); |
| // key definition found, cache the default |
| } else if (keyDefinedInSchema(key)) { |
| mCache[key] = ConfigurationRecord(mSchema[key].getDefaultValue()); |
| // total miss, cache the error |
| } else { |
| mCache[key] = ConfigurationRecord(false); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| |
| free(value); |
| |
| // Leave mLock locked. The caller holds it still. |
| return mCache[key]; |
| } |
| |
| |
| |
| bool ConfigurationTable::isStatic(const string& key) |
| { |
| if (keyDefinedInSchema(key)) { |
| return mSchema[key].isStatic(); |
| } else { |
| return false; |
| } |
| } |
| |
| |
| |
| |
| string ConfigurationTable::getStr(const string& key) |
| { |
| // We need the lock because rec is a reference into the cache. |
| try { |
| ScopedLock lock(mLock); |
| return lookup(key).value(); |
| } catch (ConfigurationTableKeyNotFound) { |
| // Raise an alert and re-throw the exception. |
| debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| } |
| |
| |
| bool ConfigurationTable::getBool(const string& key) |
| { |
| try { |
| return getNum(key) != 0; |
| } catch (ConfigurationTableKeyNotFound) { |
| // Raise an alert and re-throw the exception. |
| debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| } |
| |
| |
| long ConfigurationTable::getNum(const string& key) |
| { |
| // We need the lock because rec is a reference into the cache. |
| try { |
| ScopedLock lock(mLock); |
| return lookup(key).number(); |
| } catch (ConfigurationTableKeyNotFound) { |
| // Raise an alert and re-throw the exception. |
| debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| } |
| |
| |
| float ConfigurationTable::getFloat(const string& key) |
| { |
| try { |
| ScopedLock lock(mLock); |
| return lookup(key).floatNumber(); |
| } catch (ConfigurationTableKeyNotFound) { |
| // Raise an alert and re-throw the exception. |
| debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| } |
| |
| std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key) |
| { |
| // Look up the string. |
| char *line=NULL; |
| try { |
| ScopedLock lock(mLock); |
| const ConfigurationRecord& rec = lookup(key); |
| line = strdup(rec.value().c_str()); |
| } catch (ConfigurationTableKeyNotFound) { |
| // Raise an alert and re-throw the exception. |
| debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| |
| assert(line); |
| char *lp = line; |
| |
| // Parse the string. |
| std::vector<string> retVal; |
| while (lp) { |
| while (*lp==' ') lp++; |
| if (*lp == '\0') break; |
| char *tp = strsep(&lp," "); |
| if (!tp) break; |
| retVal.push_back(tp); |
| } |
| free(line); |
| return retVal; |
| } |
| |
| |
| std::vector<unsigned> ConfigurationTable::getVector(const string& key) |
| { |
| // Look up the string. |
| char *line=NULL; |
| try { |
| ScopedLock lock(mLock); |
| const ConfigurationRecord& rec = lookup(key); |
| line = strdup(rec.value().c_str()); |
| } catch (ConfigurationTableKeyNotFound) { |
| // Raise an alert and re-throw the exception. |
| debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); |
| throw ConfigurationTableKeyNotFound(key); |
| } |
| |
| assert(line); |
| char *lp = line; |
| |
| // Parse the string. |
| std::vector<unsigned> retVal; |
| while (lp) { |
| // Watch for multiple or trailing spaces. |
| while (*lp==' ') lp++; |
| if (*lp=='\0') break; |
| retVal.push_back(strtol(lp,NULL,0)); |
| strsep(&lp," "); |
| } |
| free(line); |
| return retVal; |
| } |
| |
| |
| bool ConfigurationTable::remove(const string& key) |
| { |
| assert(mDB); |
| |
| ScopedLock lock(mLock); |
| // Clear the cache entry and the database. |
| ConfigurationMap::iterator where = mCache.find(key); |
| if (where!=mCache.end()) mCache.erase(where); |
| // Really remove it. |
| string cmd = "DELETE FROM CONFIG WHERE KEYSTRING=='"+key+"'"; |
| return sqlite3_command(mDB,cmd.c_str()); |
| } |
| |
| |
| |
| void ConfigurationTable::find(const string& pat, ostream& os) const |
| { |
| // Prepare the statement. |
| string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG WHERE KEYSTRING LIKE \"%" + pat + "%\""; |
| sqlite3_stmt *stmt; |
| if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return; |
| // Read the result. |
| int src = sqlite3_run_query(mDB,stmt); |
| while (src==SQLITE_ROW) { |
| const char* value = (const char*)sqlite3_column_text(stmt,1); |
| os << sqlite3_column_text(stmt,0) << " "; |
| int len = 0; |
| if (value) { |
| len = strlen(value); |
| } |
| if (len && value) os << value << endl; |
| else os << "(disabled)" << endl; |
| src = sqlite3_run_query(mDB,stmt); |
| } |
| sqlite3_finalize(stmt); |
| } |
| |
| |
| ConfigurationRecordMap ConfigurationTable::getAllPairs() const |
| { |
| ConfigurationRecordMap tmp; |
| |
| // Prepare the statement. |
| string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG"; |
| sqlite3_stmt *stmt; |
| if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return tmp; |
| // Read the result. |
| int src = sqlite3_run_query(mDB,stmt); |
| while (src==SQLITE_ROW) { |
| const char* key = (const char*)sqlite3_column_text(stmt,0); |
| const char* value = (const char*)sqlite3_column_text(stmt,1); |
| if (key && value) { |
| tmp[string(key)] = ConfigurationRecord(value); |
| } else if (key && !value) { |
| tmp[string(key)] = ConfigurationRecord(false); |
| } |
| src = sqlite3_run_query(mDB,stmt); |
| } |
| sqlite3_finalize(stmt); |
| |
| return tmp; |
| } |
| |
| bool ConfigurationTable::set(const string& key, const string& value) |
| { |
| assert(mDB); |
| ScopedLock lock(mLock); |
| string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)"; |
| bool success = sqlite3_command(mDB,cmd.c_str()); |
| // Cache the result. |
| if (success) mCache[key] = ConfigurationRecord(value); |
| return success; |
| } |
| |
| bool ConfigurationTable::set(const string& key, long value) |
| { |
| char buffer[30]; |
| sprintf(buffer,"%ld",value); |
| return set(key,buffer); |
| } |
| |
| |
| bool ConfigurationTable::set(const string& key) |
| { |
| assert(mDB); |
| ScopedLock lock(mLock); |
| string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",NULL,1)"; |
| bool success = sqlite3_command(mDB,cmd.c_str()); |
| if (success) mCache[key] = ConfigurationRecord(true); |
| return success; |
| } |
| |
| |
| void ConfigurationTable::checkCacheAge() |
| { |
| // mLock is set by caller |
| static time_t timeOfLastPurge = 0; |
| time_t now = time(NULL); |
| // purge every 3 seconds |
| // purge period cannot be configuration parameter |
| if (now - timeOfLastPurge < 3) return; |
| timeOfLastPurge = now; |
| // this is purge() without the lock |
| ConfigurationMap::iterator mp = mCache.begin(); |
| while (mp != mCache.end()) { |
| ConfigurationMap::iterator prev = mp; |
| mp++; |
| mCache.erase(prev); |
| } |
| } |
| |
| |
| void ConfigurationTable::purge() |
| { |
| ScopedLock lock(mLock); |
| ConfigurationMap::iterator mp = mCache.begin(); |
| while (mp != mCache.end()) { |
| ConfigurationMap::iterator prev = mp; |
| mp++; |
| mCache.erase(prev); |
| } |
| } |
| |
| |
| void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char const *,sqlite3_int64)) |
| { |
| assert(mDB); |
| sqlite3_update_hook(mDB,func,NULL); |
| } |
| |
| |
| void ConfigurationTable::setCrossCheckHook(vector<string> (*wCrossCheck)(const string&)) |
| { |
| mCrossCheck = wCrossCheck; |
| } |
| |
| |
| vector<string> ConfigurationTable::crossCheck(const string& key) { |
| vector<string> ret; |
| |
| if (mCrossCheck != NULL) { |
| ret = mCrossCheck(key); |
| } |
| |
| return ret; |
| } |
| |
| void HashString::computeHash() |
| { |
| // FIXME -- Someone needs to review this hash function. |
| const char* cstr = c_str(); |
| mHash = 0; |
| for (unsigned i=0; i<size(); i++) { |
| mHash = mHash ^ (mHash >> 32); |
| mHash = mHash*127 + cstr[i]; |
| } |
| } |
| |
| |
| void SimpleKeyValue::addItem(const char* pair_orig) |
| { |
| char *pair = strdup(pair_orig); |
| char *key = pair; |
| char *mark = strchr(pair,'='); |
| if (!mark) return; |
| *mark = '\0'; |
| char *value = mark+1; |
| mMap[key] = value; |
| free(pair); |
| } |
| |
| |
| |
| const char* SimpleKeyValue::get(const char* key) const |
| { |
| HashStringMap::const_iterator p = mMap.find(key); |
| if (p==mMap.end()) return NULL; |
| return p->second.c_str(); |
| } |
| |
| |
| void SimpleKeyValue::addItems(const char* pairs_orig) |
| { |
| char *pairs = strdup(pairs_orig); |
| char *thisPair; |
| while ((thisPair=strsep(&pairs," "))!=NULL) { |
| addItem(thisPair); |
| } |
| free(pairs); |
| } |
| |
| |
| bool ConfigurationKey::isValidIP(const std::string& ip) { |
| struct sockaddr_in sa; |
| return inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr)) != 0; |
| } |
| |
| |
| void ConfigurationKey::getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping) { |
| uint delimiter; |
| int startPos; |
| uint endPos; |
| |
| std::string tmp = key.getValidValues(); |
| stepping = "1"; |
| |
| // grab steps if they're defined |
| startPos = tmp.find('('); |
| if (startPos != (int)std::string::npos) { |
| endPos = tmp.find(')'); |
| stepping = tmp.substr(startPos+1, endPos-startPos-1); |
| tmp = tmp.substr(0, startPos); |
| } |
| startPos = 0; |
| |
| delimiter = tmp.find(':', startPos); |
| min = tmp.substr(startPos, delimiter-startPos); |
| max = tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1); |
| } |
| |
| |
| template<class T> bool ConfigurationKey::isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger) { |
| bool ret = false; |
| |
| T convVal; |
| T min; |
| T max; |
| T steps; |
| std::string strMin; |
| std::string strMax; |
| std::string strSteps; |
| |
| std::stringstream(val) >> convVal; |
| |
| ConfigurationKey::getMinMaxStepping(key, strMin, strMax, strSteps); |
| std::stringstream(strMin) >> min; |
| std::stringstream(strMax) >> max; |
| std::stringstream(strSteps) >> steps; |
| |
| // TODO : only ranges checked, steps not enforced |
| if (isInteger) { |
| if (val.find('.') == std::string::npos && min <= convVal && convVal <= max) { |
| ret = true; |
| } |
| } else { |
| if (min <= convVal && convVal <= max) { |
| ret = true; |
| } |
| } |
| |
| return ret; |
| } |
| |
| const std::string ConfigurationKey::getARFCNsString() { |
| stringstream ss; |
| int i; |
| float downlink; |
| float uplink; |
| |
| // 128:251 GSM850 |
| downlink = 869.2; |
| uplink = 824.2; |
| for (i = 128; i <= 251; i++) { |
| ss << i << "|GSM850 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; |
| downlink += 0.2; |
| uplink += 0.2; |
| } |
| |
| // 1:124 PGSM900 |
| downlink = 935.2; |
| uplink = 890.2; |
| for (i = 1; i <= 124; i++) { |
| ss << i << "|PGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; |
| downlink += 0.2; |
| uplink += 0.2; |
| } |
| |
| // 512:885 DCS1800 |
| downlink = 1805.2; |
| uplink = 1710.2; |
| for (i = 512; i <= 885; i++) { |
| ss << i << "|DCS1800 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; |
| downlink += 0.2; |
| uplink += 0.2; |
| } |
| |
| // 512:810 PCS1900 |
| downlink = 1930.2; |
| uplink = 1850.2; |
| for (i = 512; i <= 810; i++) { |
| ss << i << "|PCS1900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,"; |
| downlink += 0.2; |
| uplink += 0.2; |
| } |
| |
| ss << endl; |
| |
| return ss.str(); |
| } |
| |
| const std::string ConfigurationKey::visibilityLevelToString(const ConfigurationKey::VisibilityLevel& visibility) { |
| std::string ret = "UNKNOWN ERROR"; |
| |
| switch (visibility) { |
| case ConfigurationKey::CUSTOMER: |
| ret = "customer - can be freely changed by the customer without any detriment to their system"; |
| break; |
| case ConfigurationKey::CUSTOMERSITE: |
| ret = "customer site - these values are different for each BTS and should not be left default"; |
| break; |
| case ConfigurationKey::CUSTOMERTUNE: |
| ret = "customer tune - should only be changed to tune an installation to better suit the physical environment or MS usage pattern"; |
| break; |
| case ConfigurationKey::CUSTOMERWARN: |
| ret = "customer warn - a warning will be presented and confirmation required before changing this sensitive setting"; |
| break; |
| case ConfigurationKey::DEVELOPER: |
| ret = "developer - should only be changed by developers to debug/optimize the implementation"; |
| break; |
| case ConfigurationKey::FACTORY: |
| ret = "factory - set once at the factory, should never be changed"; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| const std::string ConfigurationKey::typeToString(const ConfigurationKey::Type& type) { |
| std::string ret = "UNKNOWN ERROR"; |
| |
| switch (type) { |
| case BOOLEAN: |
| ret = "boolean"; |
| break; |
| case CHOICE_OPT: |
| ret = "multiple choice (optional)"; |
| break; |
| case CHOICE: |
| ret = "multiple choice"; |
| break; |
| case CIDR_OPT: |
| ret = "CIDR notation (optional)"; |
| break; |
| case CIDR: |
| ret = "CIDR notation"; |
| break; |
| case FILEPATH_OPT: |
| ret = "file path (optional)"; |
| break; |
| case FILEPATH: |
| ret = "file path"; |
| break; |
| case IPADDRESS_OPT: |
| ret = "IP address (optional)"; |
| break; |
| case IPADDRESS: |
| ret = "IP address"; |
| break; |
| case IPANDPORT: |
| ret = "IP address and port"; |
| break; |
| case MIPADDRESS_OPT: |
| ret = "space-separated list of IP addresses (optional)"; |
| break; |
| case MIPADDRESS: |
| ret = "space-separated list of IP addresses"; |
| break; |
| case PORT_OPT: |
| ret = "IP port (optional)"; |
| break; |
| case PORT: |
| ret = "IP port"; |
| break; |
| case REGEX_OPT: |
| ret = "regular expression (optional)"; |
| break; |
| case REGEX: |
| ret = "regular expression"; |
| break; |
| case STRING_OPT: |
| ret = "string (optional)"; |
| break; |
| case STRING: |
| ret = "string"; |
| break; |
| case VALRANGE: |
| ret = "value range"; |
| break; |
| } |
| |
| return ret; |
| } |
| |
| void ConfigurationKey::printKey(const ConfigurationKey &key, const std::string& currentValue, ostream& os) { |
| os << key.getName() << " "; |
| if (!currentValue.length()) { |
| os << "(disabled)"; |
| } else { |
| os << currentValue; |
| } |
| if (currentValue.compare(key.getDefaultValue()) == 0) { |
| os << " [default]"; |
| } |
| os << endl; |
| } |
| |
| void ConfigurationKey::printDescription(const ConfigurationKey &key, ostream& os) { |
| std::string tmp; |
| |
| os << " - description: " << key.getDescription() << std::endl; |
| if (key.getUnits().length()) { |
| os << " - units: " << key.getUnits() << std::endl; |
| } |
| os << " - type: " << ConfigurationKey::typeToString(key.getType()) << std::endl; |
| if (key.getDefaultValue().length()) { |
| os << " - default value: " << key.getDefaultValue() << std::endl; |
| } |
| os << " - visibility level: " << ConfigurationKey::visibilityLevelToString(key.getVisibility()) << std::endl; |
| os << " - static: " << key.isStatic() << std::endl; |
| |
| tmp = key.getValidValues(); |
| if (key.getType() == ConfigurationKey::VALRANGE) { |
| int startPos = tmp.find('('); |
| uint delimiter = 0; |
| if (startPos != (int)std::string::npos) { |
| tmp = tmp.substr(0, startPos); |
| } |
| startPos = -1; |
| |
| do { |
| startPos++; |
| delimiter = tmp.find(':', startPos); |
| os << " - valid values: " << "from " << tmp.substr(startPos, delimiter-startPos) << " to " |
| << tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1) << std::endl; |
| |
| } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos); |
| |
| } else if (key.getType() == ConfigurationKey::CHOICE) { |
| int startPos = -1; |
| uint endPos = 0; |
| |
| do { |
| startPos++; |
| if ((endPos = tmp.find('|', startPos)) != std::string::npos) { |
| os << " - valid values: " << tmp.substr(startPos, endPos-startPos); |
| os << " = " << tmp.substr(endPos+1, tmp.find(',', endPos)-endPos-1) << std::endl; |
| } else { |
| os << " - valid values: " << tmp.substr(startPos, tmp.find(',', startPos)-startPos) << std::endl; |
| } |
| |
| } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos); |
| |
| } else if (key.getType() == ConfigurationKey::BOOLEAN) { |
| os << " - valid values: 0 = disabled" << std::endl; |
| os << " - valid values: 1 = enabled" << std::endl; |
| |
| } else if (key.getType() == ConfigurationKey::STRING) { |
| os << " - valid val regex: " << tmp << std::endl; |
| |
| } else if (key.getValidValues().length()) { |
| os << " - raw valid values: " << tmp << std::endl; |
| } |
| } |
| |
| |
| // vim: ts=4 sw=4 |