From 5a87247fdf2768a6408e0b87c210cebda85bc996 Mon Sep 17 00:00:00 2001 From: "kurtis.heimerl" Date: Fri, 31 May 2013 21:47:25 +0000 Subject: syncing commonlibs with Many thanks to Michael Iedema for these patches, makes config a lot better. git-svn-id: http://wush.net/svn/range/software/public/openbts/trunk@5655 19bc5d8c-e614-43d4-8b26-e1612bc8e597 --- CommonLibs/BitVector.cpp | 28 +- CommonLibs/BitVector.h | 23 ++ CommonLibs/Configuration.cpp | 872 ++++++++++++++++++++++++++++++++++----- CommonLibs/Configuration.h | 160 +++++-- CommonLibs/ConfigurationTest.cpp | 52 ++- CommonLibs/Interthread.h | 156 ++++++- CommonLibs/InterthreadTest.cpp | 3 +- CommonLibs/LinkedLists.cpp | 16 +- CommonLibs/LinkedLists.h | 81 +++- CommonLibs/LogTest.cpp | 3 +- CommonLibs/Logger.cpp | 98 ++++- CommonLibs/Logger.h | 41 +- CommonLibs/Makefile.am | 28 +- CommonLibs/MemoryLeak.h | 111 +++++ CommonLibs/ScalarTypes.h | 136 ++++++ CommonLibs/Sockets.cpp | 9 + CommonLibs/Threads.cpp | 5 +- CommonLibs/Threads.h | 8 +- CommonLibs/Timeval.h | 1 + CommonLibs/URLEncode.cpp | 25 +- CommonLibs/URLEncodeTest.cpp | 17 + CommonLibs/Utils.cpp | 211 ++++++++++ CommonLibs/Utils.h | 148 +++++++ CommonLibs/Vector.h | 13 + CommonLibs/VectorTest.cpp | 4 + 25 files changed, 2034 insertions(+), 215 deletions(-) create mode 100644 CommonLibs/MemoryLeak.h create mode 100644 CommonLibs/ScalarTypes.h create mode 100644 CommonLibs/URLEncodeTest.cpp create mode 100644 CommonLibs/Utils.cpp create mode 100644 CommonLibs/Utils.h diff --git a/CommonLibs/BitVector.cpp b/CommonLibs/BitVector.cpp index 54a3edc..7487834 100644 --- a/CommonLibs/BitVector.cpp +++ b/CommonLibs/BitVector.cpp @@ -29,6 +29,7 @@ #include "BitVector.h" #include #include +#include using namespace std; @@ -274,9 +275,6 @@ void BitVector::unmap(const unsigned *map, size_t mapSize, BitVector& dest) cons - - - ostream& operator<<(ostream& os, const BitVector& hv) { for (size_t i=0; i { void fillFieldReversed(size_t writeIndex, uint64_t value, unsigned length); void writeField(size_t& writeIndex, uint64_t value, unsigned length); void writeFieldReversed(size_t& writeIndex, uint64_t value, unsigned length); + void write0(size_t& writeIndex) { writeField(writeIndex,0,1); } + void write1(size_t& writeIndex) { writeField(writeIndex,1,1); } + //@} /** Sum of bits. */ @@ -333,11 +336,26 @@ class BitVector : public Vector { /** Make a hexdump string. */ void hex(std::ostream&) const; + std::string hexstr() const; /** Unpack from a hexdump string. * @returns true on success, false on error. */ bool unhex(const char*); + void set(BitVector other) // That's right. No ampersand. + { + clear(); + mData=other.mData; + mStart=other.mStart; + mEnd=other.mEnd; + other.mData=NULL; + } + + void settfb(int i, int j) const + { + mStart[i] = j; + } + }; @@ -412,6 +430,11 @@ class SoftVector: public Vector { /** Decode soft symbols with the GSM rate-1/2 Viterbi decoder. */ void decode(ViterbiR2O4 &decoder, BitVector& target) const; + // (pat) How good is the SoftVector in the sense of the bits being solid? + // Result of 1 is perfect and 0 means all the bits were 0.5 + // If plow is non-NULL, also return the lowest energy bit. + float getEnergy(float *low=0) const; + /** Fill with "unknown" values. */ void unknown() { fill(0.5F); } diff --git a/CommonLibs/Configuration.cpp b/CommonLibs/Configuration.cpp index 5dcc277..bda6865 100644 --- a/CommonLibs/Configuration.cpp +++ b/CommonLibs/Configuration.cpp @@ -32,6 +32,12 @@ #include #include +#ifdef DEBUG_CONFIG +#define debugLogEarly gLogEarly +#else +#define debugLogEarly +#endif + using namespace std; @@ -57,7 +63,7 @@ float ConfigurationRecord::floatNumber() const } -ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName) +ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema) { gLogEarly(LOG_INFO, "opening configuration table from path %s", filename); // Connect to the database. @@ -78,35 +84,440 @@ ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdNam 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; + free(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; + free(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; + free(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(); +} -bool ConfigurationTable::defines(const string& key) +string ConfigurationTable::getTeX(const std::string& program, const std::string& version) { - assert(mDB); - ScopedLock lock(mLock); + 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; - // Check the cache. - checkCacheAge(); - ConfigurationMap::const_iterator where = mCache.find(key); - if (where!=mCache.end()) return where->second.defined(); + string tmp = Utils::replaceAll(ss.str(), "^", "\\^"); + return Utils::replaceAll(tmp, "_", "\\_"); +} - // Check the database. - char *value = NULL; - sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"VALUESTRING",value); +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; + } +} - // Cache the result. - if (value) { - mCache[key] = ConfigurationRecord(value); - free(value); - return true; +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(key, val, false); + } else { + ret = ConfigurationKey::isInValRange(key, val, true); + } + + regfree(&r); + break; + } } - - mCache[key] = ConfigurationRecord(false); - return false; + + 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) { @@ -129,15 +540,18 @@ const ConfigurationRecord& ConfigurationTable::lookup(const string& key) sqlite3_single_lookup(mDB,"CONFIG", "KEYSTRING",key.c_str(),"VALUESTRING",value); - // Nothing defined? - if (!value) { - // Cache the failure. + // 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); } - // Cache the result. - mCache[key] = ConfigurationRecord(value); free(value); // Leave mLock locked. The caller holds it still. @@ -146,22 +560,13 @@ const ConfigurationRecord& ConfigurationTable::lookup(const string& key) -bool ConfigurationTable::isStatic(const string& key) const -{ - assert(mDB); - unsigned stat; - bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"STATIC",stat); - if (success) return (bool)stat; - return false; -} - -bool ConfigurationTable::isRequired(const string& key) const +bool ConfigurationTable::isStatic(const string& key) { - assert(mDB); - unsigned optional; - bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"OPTIONAL",optional); - if (success) return !((bool)optional); - return false; + if (keyDefinedInSchema(key)) { + return mSchema[key].isStatic(); + } else { + return false; + } } @@ -175,30 +580,20 @@ string ConfigurationTable::getStr(const string& key) return lookup(key).value(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } -string ConfigurationTable::getStr(const string& key, const char* defaultValue) -{ - try { - ScopedLock lock(mLock); - return lookup(key).value(); - } catch (ConfigurationTableKeyNotFound) { - gLogEarly(LOG_NOTICE, "deinfing missing parameter %s with value %s", key.c_str(),defaultValue); - set(key,defaultValue); - return string(defaultValue); - } -} - bool ConfigurationTable::getBool(const string& key) { try { return getNum(key) != 0; } catch (ConfigurationTableKeyNotFound) { - return false; + // Raise an alert and re-throw the exception. + debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + throw ConfigurationTableKeyNotFound(key); } } @@ -211,32 +606,24 @@ long ConfigurationTable::getNum(const string& key) return lookup(key).number(); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } } -long ConfigurationTable::getNum(const string& key, long defaultValue) +float ConfigurationTable::getFloat(const string& key) { try { ScopedLock lock(mLock); - return lookup(key).number(); + return lookup(key).floatNumber(); } catch (ConfigurationTableKeyNotFound) { - gLogEarly(LOG_NOTICE, "deinfing missing parameter %s with value %ld", key.c_str(),defaultValue); - set(key,defaultValue); - return defaultValue; + // 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) -{ - // We need the lock because rec is a reference into the cache. - ScopedLock lock(mLock); - return lookup(key).floatNumber(); -} - std::vector ConfigurationTable::getVectorOfStrings(const string& key) { // Look up the string. @@ -247,7 +634,7 @@ std::vector ConfigurationTable::getVectorOfStrings(const string& key) line = strdup(rec.value().c_str()); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } @@ -268,17 +655,6 @@ std::vector ConfigurationTable::getVectorOfStrings(const string& key) } -std::vector ConfigurationTable::getVectorOfStrings(const string& key, const char* defaultValue){ - try { - return getVectorOfStrings(key); - } catch (ConfigurationTableKeyNotFound) { - set(key,defaultValue); - return getVectorOfStrings(key); - } -} - - - std::vector ConfigurationTable::getVector(const string& key) { // Look up the string. @@ -289,7 +665,7 @@ std::vector ConfigurationTable::getVector(const string& key) line = strdup(rec.value().c_str()); } catch (ConfigurationTableKeyNotFound) { // Raise an alert and re-throw the exception. - gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); + debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str()); throw ConfigurationTableKeyNotFound(key); } @@ -310,25 +686,9 @@ std::vector ConfigurationTable::getVector(const string& key) } -bool ConfigurationTable::unset(const string& key) -{ - assert(mDB); - if (!defines(key)) return true; - if (isRequired(key)) return false; - - ScopedLock lock(mLock); - // Clear the cache entry and the database. - ConfigurationMap::iterator where = mCache.find(key); - if (where!=mCache.end()) mCache.erase(where); - // Don't delete it; just set VALUESTRING to NULL. - string cmd = "UPDATE CONFIG SET VALUESTRING=NULL WHERE KEYSTRING=='"+key+"'"; - return sqlite3_command(mDB,cmd.c_str()); -} - bool ConfigurationTable::remove(const string& key) { assert(mDB); - if (isRequired(key)) return false; ScopedLock lock(mLock); // Clear the cache entry and the database. @@ -352,14 +712,43 @@ void ConfigurationTable::find(const string& pat, ostream& os) const while (src==SQLITE_ROW) { const char* value = (const char*)sqlite3_column_text(stmt,1); os << sqlite3_column_text(stmt,0) << " "; - if (value) os << value << endl; - else os << "(null)" << endl; + 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); @@ -428,6 +817,21 @@ void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char } +void ConfigurationTable::setCrossCheckHook(vector (*wCrossCheck)(const string&)) +{ + mCrossCheck = wCrossCheck; +} + + +vector ConfigurationTable::crossCheck(const string& key) { + vector ret; + + if (mCrossCheck != NULL) { + ret = mCrossCheck(key); + } + + return ret; +} void HashString::computeHash() { @@ -474,5 +878,277 @@ void SimpleKeyValue::addItems(const char* pairs_orig) } +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 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 diff --git a/CommonLibs/Configuration.h b/CommonLibs/Configuration.h index bc37a49..cd4838e 100644 --- a/CommonLibs/Configuration.h +++ b/CommonLibs/Configuration.h @@ -33,10 +33,14 @@ #include #include +#include +#include +#include #include #include #include +#include #include #include @@ -165,8 +169,10 @@ class HashString : public std::string { }; +typedef std::map ConfigurationRecordMap; typedef std::map ConfigurationMap; - +class ConfigurationKey; +typedef std::map ConfigurationKeyMap; /** A class for maintaining a configuration key-value table, @@ -180,40 +186,44 @@ class ConfigurationTable { sqlite3* mDB; ///< database connection ConfigurationMap mCache; ///< cache of recently access configuration values mutable Mutex mLock; ///< control for multithreaded access to the cache + std::vector (*mCrossCheck)(const std::string&); ///< cross check callback pointer public: + ConfigurationKeyMap mSchema;///< definition of configuration default values and validation logic + + ConfigurationTable(const char* filename = ":memory:", const char *wCmdName = 0, ConfigurationKeyMap wSchema = ConfigurationKeyMap()); - ConfigurationTable(const char* filename = ":memory:", const char *wCmdName = 0); + /** Generate an up-to-date example sql file for new installs. */ + std::string getDefaultSQL(const std::string& program, const std::string& version); + + /** Generate an up-to-date TeX snippet. */ + std::string getTeX(const std::string& program, const std::string& version); /** Return true if the key is used in the table. */ bool defines(const std::string& key); - /** Return true if this key is identified as static. */ - bool isStatic(const std::string& key) const; + /** Return true if the application's schema knows about this key. */ + bool keyDefinedInSchema(const std::string& name); - /** Return true if this key is identified as required (!optional). */ - bool isRequired(const std::string& key) const; - - /** - Get a string parameter from the table. - Throw ConfigurationTableKeyNotFound if not found. - */ - std::string getStr(const std::string& key); + /** Return true if the provided value validates correctly against the defined schema. */ + bool isValidValue(const std::string& name, const std::string& val); + /** Return true if the provided value validates correctly against the defined schema. */ + bool isValidValue(const std::string& name, const int val) { std::stringstream ss; ss << val; return isValidValue(name, ss.str()); } - /** - Get a string parameter from the table. - Define the parameter to the default value if not found. - */ - std::string getStr(const std::string& key, const char* defaultValue); + /** Return a map of all similar keys in the defined schema. */ + ConfigurationKeyMap getSimilarKeys(const std::string& snippet); + /** Return true if this key is identified as static. */ + bool isStatic(const std::string& key); /** - Get a numeric parameter from the table. + Get a string parameter from the table. Throw ConfigurationTableKeyNotFound if not found. */ - long getNum(const std::string& key); + std::string getStr(const std::string& key); + /** Get a boolean from the table. @@ -223,20 +233,15 @@ class ConfigurationTable { /** Get a numeric parameter from the table. - Define the parameter to the default value if not found. + Throw ConfigurationTableKeyNotFound if not found. */ - long getNum(const std::string& key, long defaultValue); + long getNum(const std::string& key); /** Get a vector of strings from the table. */ std::vector getVectorOfStrings(const std::string& key); - /** - Get a vector of strings from the table, with a default value.. - */ - std::vector getVectorOfStrings(const std::string& key, const char* defaultValue); - /** Get a float from the table. Throw ConfigurationTableKeyNotFound if not found. @@ -261,14 +266,6 @@ class ConfigurationTable { /** Create an entry in the table, no value though. */ bool set(const std::string& key); - /** - Set a corresponding value to NULL. - Will not alter required values. - @param key The key of the item to be nulled-out. - @return true if anything was actually nulled-out. - */ - bool unset(const std::string& key); - /** Remove an entry from the table. Will not alter required values. @@ -280,9 +277,18 @@ class ConfigurationTable { /** Search the table, dumping to a stream. */ void find(const std::string& pattern, std::ostream&) const; + /** Return all key/value pairs stored in the ConfigurationTable */ + ConfigurationRecordMap getAllPairs() const; + /** Define the callback to purge the cache whenever the database changes. */ void setUpdateHook(void(*)(void *,int ,char const *,char const *,sqlite3_int64)); + /** Define the callback for cross checking. */ + void setCrossCheckHook(std::vector (*wCrossCheck)(const std::string&)); + + /** Execute the application specific value cross checking logic. */ + std::vector crossCheck(const std::string& key); + /** purege cache if it exceeds a certain age */ void checkCacheAge(); @@ -323,6 +329,92 @@ class SimpleKeyValue { }; +class ConfigurationKey { + + public: + + enum VisibilityLevel + { + CUSTOMER, + CUSTOMERSITE, + CUSTOMERTUNE, + CUSTOMERWARN, + DEVELOPER, + FACTORY + }; + + enum Type + { + BOOLEAN, + CHOICE_OPT, + CHOICE, + CIDR_OPT, + CIDR, + FILEPATH_OPT, + FILEPATH, + IPADDRESS_OPT, + IPADDRESS, + IPANDPORT, + MIPADDRESS_OPT, + MIPADDRESS, + PORT_OPT, + PORT, + REGEX_OPT, + REGEX, + STRING_OPT, + STRING, + VALRANGE + }; + + private: + + std::string mName; + std::string mDefaultValue; + std::string mUnits; + VisibilityLevel mVisibility; + Type mType; + std::string mValidValues; + bool mIsStatic; + std::string mDescription; + + + public: + + ConfigurationKey(const std::string& wName, const std::string& wDefaultValue, const std::string& wUnits, const VisibilityLevel wVisibility, const Type wType, const std::string& wValidValues, bool wIsStatic, const std::string& wDescription): + mName(wName), + mDefaultValue(wDefaultValue), + mUnits(wUnits), + mVisibility(wVisibility), + mType(wType), + mValidValues(wValidValues), + mIsStatic(wIsStatic), + mDescription(wDescription) + { } + + ConfigurationKey() + { } + + const std::string& getName() const { return mName; } + const std::string& getDefaultValue() const { return mDefaultValue; } + void updateDefaultValue(const std::string& newValue) { mDefaultValue = newValue; } + void updateDefaultValue(const int newValue) { std::stringstream ss; ss << newValue; updateDefaultValue(ss.str()); } + const std::string& getUnits() const { return mUnits; } + const VisibilityLevel& getVisibility() const { return mVisibility; } + const Type& getType() const { return mType; } + const std::string& getValidValues() const { return mValidValues; } + bool isStatic() const { return mIsStatic; } + const std::string& getDescription() const { return mDescription; } + + static bool isValidIP(const std::string& ip); + static void getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping); + template static bool isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger); + static const std::string visibilityLevelToString(const VisibilityLevel& visibility); + static const std::string typeToString(const ConfigurationKey::Type& type); + static void printKey(const ConfigurationKey &key, const std::string& currentValue, std::ostream& os); + static void printDescription(const ConfigurationKey &key, std::ostream& os); + static const std::string getARFCNsString(); +}; + #endif diff --git a/CommonLibs/ConfigurationTest.cpp b/CommonLibs/ConfigurationTest.cpp index 3a2045f..2fd43e9 100644 --- a/CommonLibs/ConfigurationTest.cpp +++ b/CommonLibs/ConfigurationTest.cpp @@ -32,7 +32,8 @@ using namespace std; -ConfigurationTable gConfig("exampleconfig.db","test"); +ConfigurationKeyMap getConfigurationKeys(); +ConfigurationTable gConfig("exampleconfig.db","test", getConfigurationKeys()); void purgeConfig(void*,int,char const*, char const*, sqlite3_int64) { @@ -46,7 +47,7 @@ int main(int argc, char *argv[]) gConfig.setUpdateHook(purgeConfig); - const char *keys[5] = {"key1", "key2", "key3", "key4", "key5"}; + char *keys[5] = {"key1", "key2", "key3", "key4", "key5"}; for (int i=0; i<5; i++) { gConfig.set(keys[i],i); @@ -57,7 +58,6 @@ int main(int argc, char *argv[]) cout << "table[" << keys[i] << "]=" << gConfig.getNum(keys[i]) << endl; } - gConfig.unset("key1"); for (int i=0; i<5; i++) { cout << "defined table[" << keys[i] << "]=" << gConfig.defines(keys[i]) << endl; } @@ -78,8 +78,8 @@ int main(int argc, char *argv[]) gConfig.set("booltest",0); cout << "bool " << gConfig.getBool("booltest") << endl; - gConfig.getStr("newstring","new string value"); - gConfig.getNum("numnumber",42); + gConfig.getStr("newstring"); + gConfig.getNum("numnumber"); SimpleKeyValue pairs; @@ -94,7 +94,6 @@ int main(int argc, char *argv[]) cout << "search fkey:" << endl; gConfig.find("fkey",cout); - gConfig.unset("fkey"); cout << "search fkey:" << endl; gConfig.find("fkey",cout); gConfig.remove("fkey"); @@ -107,3 +106,44 @@ int main(int argc, char *argv[]) cout << "ConfigurationTableKeyNotFound exception successfully caught." << endl; } } + +ConfigurationKeyMap getConfigurationKeys() +{ + ConfigurationKeyMap map; + ConfigurationKey *tmp; + + tmp = new ConfigurationKey("booltest","0", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::BOOLEAN, + "", + false, + "" + ); + map[tmp->getName()] = *tmp; + free(tmp); + + tmp = new ConfigurationKey("numnumber","42", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::VALRANGE, + "0-100", + false, + "" + ); + map[tmp->getName()] = *tmp; + free(tmp); + + tmp = new ConfigurationKey("newstring","new string value", + "", + ConfigurationKey::DEVELOPER, + ConfigurationKey::STRING, + "", + false, + "" + ); + map[tmp->getName()] = *tmp; + free(tmp); + + return map; +} diff --git a/CommonLibs/Interthread.h b/CommonLibs/Interthread.h index 023ac14..42e6f7f 100644 --- a/CommonLibs/Interthread.h +++ b/CommonLibs/Interthread.h @@ -42,15 +42,21 @@ /** Pointer FIFO for interthread operations. */ -template class InterthreadQueue { +// (pat) The elements in the queue are type T*, and +// the Fifo class implements the underlying queue. +// The default is class PointerFIFO, which does not place any restrictions on the type of T, +// and is implemented by allocating auxilliary structures for the queue, +// or SingleLinkedList, which implements the queue using an internal pointer in type T, +// which must implement the functional interface of class SingleLinkListNode, +// namely: functions T*next() and void setNext(T*). +template class InterthreadQueue { protected: - PointerFIFO mQ; + Fifo mQ; mutable Mutex mLock; mutable Signal mWriteSignal; - public: /** Delete contents. */ @@ -78,6 +84,12 @@ template class InterthreadQueue { return mQ.size(); } + size_t totalSize() const // pat added + { + ScopedLock lock(mLock); + return mQ.totalSize(); + } + /** Blocking read. @return Pointer to object (will not be NULL). @@ -93,6 +105,13 @@ template class InterthreadQueue { return retVal; } + /** Non-blocking peek at the first element; returns NULL if empty. */ + T* front() + { + ScopedLock lock(mLock); + return (T*) mQ.front(); + } + /** Blocking read with a timeout. @param timeout The read timeout in ms. @@ -127,7 +146,132 @@ template class InterthreadQueue { mWriteSignal.signal(); } + /** Non-block write to the front of the queue. */ + void write_front(T* val) // pat added + { + ScopedLock lock(mLock); + mQ.push_front(val); + mWriteSignal.signal(); + } +}; + +// (pat) Identical to above but with the threading problem fixed. +template class InterthreadQueue2 { + + protected: + + Fifo mQ; + mutable Mutex mLock; + mutable Signal mWriteSignal; + + public: + + /** Delete contents. */ + void clear() + { + ScopedLock lock(mLock); + while (mQ.size()>0) delete (T*)mQ.get(); + } + + /** Empty the queue, but don't delete. */ + void flushNoDelete() + { + ScopedLock lock(mLock); + while (mQ.size()>0) mQ.get(); + } + + + ~InterthreadQueue2() + { clear(); } + + size_t size() const + { + ScopedLock lock(mLock); + return mQ.size(); + } + + size_t totalSize() const // pat added + { + ScopedLock lock(mLock); + return mQ.totalSize(); + } + + /** + Blocking read. + @return Pointer to object (will not be NULL). + */ + T* read() + { + ScopedLock lock(mLock); + T* retVal = (T*)mQ.get(); + while (retVal==NULL) { + mWriteSignal.wait(mLock); + retVal = (T*)mQ.get(); + } + return retVal; + } + + /** Non-blocking peek at the first element; returns NULL if empty. */ + T* front() + { + ScopedLock lock(mLock); + return (T*) mQ.front(); + } + + /** + Blocking read with a timeout. + @param timeout The read timeout in ms. + @return Pointer to object or NULL on timeout. + */ + T* read(unsigned timeout) + { + if (timeout==0) return readNoBlock(); + Timeval waitTime(timeout); + ScopedLock lock(mLock); + while ((mQ.size()==0) && (!waitTime.passed())) + mWriteSignal.wait(mLock,waitTime.remaining()); + T* retVal = (T*)mQ.get(); + return retVal; + } + + /** + Non-blocking read. + @return Pointer to object or NULL if FIFO is empty. + */ + T* readNoBlock() + { + ScopedLock lock(mLock); + return (T*)mQ.get(); + } + + /** Non-blocking write. */ + void write(T* val) + { + // (pat) The Mutex mLock must be released before signaling the mWriteSignal condition. + // This is an implicit requirement of pthread_cond_wait() called from signal(). + // If you do not do that, the InterthreadQueue read() function cannot start + // because the mutex is still locked by the thread calling the write(), + // so the read() thread yields its immediate execution opportunity. + // This recurs (and the InterthreadQueue fills up with data) + // until the read thread's accumulated temporary priority causes it to + // get a second pre-emptive activation over the writing thread, + // resulting in bursts of activity by the read thread. + { ScopedLock lock(mLock); + mQ.put(val); + } + mWriteSignal.signal(); + } + + /** Non-block write to the front of the queue. */ + void write_front(T* val) // pat added + { + // (pat) See comments above. + { ScopedLock lock(mLock); + mQ.push_front(val); + } + mWriteSignal.signal(); + } }; @@ -214,12 +358,17 @@ template class InterthreadQueueWithWait { /** Non-blocking write. */ void write(T* val) { + // (pat) 8-14: Taking out the threading problem fix temporarily for David to use in the field. ScopedLock lock(mLock); mQ.put(val); mWriteSignal.signal(); } /** Wait until the queue falls below a low water mark. */ + // (pat) This function suffers from the same problem as documented + // at InterthreadQueue.write(), but I am not fixing it because I cannot test it. + // The caller of this function will eventually get to run, just not immediately + // after the mReadSignal condition is fulfilled. void wait(size_t sz=0) { ScopedLock lock(mLock); @@ -484,6 +633,7 @@ template , class Cmp = PointerCompare > cl /** Non-blocking write. */ void write(T* val) { + // (pat) 8-14: Taking out the threading problem fix temporarily for David to use in the field. ScopedLock lock(mLock); mQ.push(val); mWriteSignal.signal(); diff --git a/CommonLibs/InterthreadTest.cpp b/CommonLibs/InterthreadTest.cpp index 1712689..03445d9 100644 --- a/CommonLibs/InterthreadTest.cpp +++ b/CommonLibs/InterthreadTest.cpp @@ -82,7 +82,8 @@ void* mapReader(void*) for (int i=0; i<20; i++) { int *p = gMap.read(i); COUT("map read " << *p); - delete p; + // InterthreadMap will delete the pointers + // delete p; } return NULL; } diff --git a/CommonLibs/LinkedLists.cpp b/CommonLibs/LinkedLists.cpp index ba0f0cc..35a8541 100644 --- a/CommonLibs/LinkedLists.cpp +++ b/CommonLibs/LinkedLists.cpp @@ -29,7 +29,17 @@ #include "LinkedLists.h" - +void PointerFIFO::push_front(void* val) // by pat +{ + // Pat added this routine for completeness, but never used or tested. + // The first person to use this routine should remove this assert. + ListNode *node = allocate(); + node->data(val); + node->next(mHead); + mHead = node; + if (!mTail) mTail=node; + mSize++; +} void PointerFIFO::put(void* val) { @@ -58,7 +68,6 @@ void* PointerFIFO::get() } - ListNode *PointerFIFO::allocate() { if (mFreeList==NULL) return new ListNode; @@ -72,6 +81,3 @@ void PointerFIFO::release(ListNode* wNode) wNode->next(mFreeList); mFreeList = wNode; } - - - diff --git a/CommonLibs/LinkedLists.h b/CommonLibs/LinkedLists.h index 2cb83ee..31fb9c5 100644 --- a/CommonLibs/LinkedLists.h +++ b/CommonLibs/LinkedLists.h @@ -1,6 +1,8 @@ /* * Copyright 2008 Free Software Foundation, Inc. * +* This software is distributed under multiple licenses; see the COPYING file in the main directory for licensing information for this specific distribuion. +* * This software is distributed under the terms of the GNU Affero Public License. * See the COPYING file in the main directory for details. * @@ -28,6 +30,7 @@ #define LINKEDLISTS_H #include +#include @@ -54,7 +57,7 @@ class ListNode { /** A fast FIFO for pointer-based storage. */ class PointerFIFO { - private: + protected: ListNode* mHead; ///< points to next item out ListNode* mTail; ///< points to last item in @@ -69,9 +72,12 @@ class PointerFIFO { {} unsigned size() const { return mSize; } + unsigned totalSize() const { return 0; } // Not used in this version. - /** Put an item into the FIFO. */ + /** Put an item into the FIFO at the back of the queue. */ void put(void* val); + /** Push an item on the front of the FIFO. */ + void push_front(void*val); // pat added. /** Take an item from the FIFO. @@ -79,6 +85,9 @@ class PointerFIFO { */ void* get(); + /** Peek at front item without removal. */ + void *front() { return mHead ? mHead->data() : 0; } // pat added + private: @@ -90,6 +99,74 @@ class PointerFIFO { }; +// This is the default type for SingleLinkList Node element; +// You can derive your class directly from this, but then you must add type casts +// all over the place. +class SingleLinkListNode +{ public: + SingleLinkListNode *mNext; + SingleLinkListNode *next() {return mNext;} + void setNext(SingleLinkListNode *item) {mNext=item;} + SingleLinkListNode() : mNext(0) {} + virtual unsigned size() { return 0; } +}; + +// A single-linked lists of elements with internal pointers. +// The methods must match those from SingleLinkListNode. +// This class also assumes the Node has a size() method, and accumulates +// the total size of elements in the list in totalSize(). +template +class SingleLinkList +{ + Node *mHead, *mTail; + unsigned mSize; // Number of elements in list. + unsigned mTotalSize; // Total of size() method of elements in list. + + public: + SingleLinkList() : mHead(0), mTail(0), mSize(0), mTotalSize(0) {} + unsigned size() const { return mSize; } + unsigned totalSize() const { return mTotalSize; } + + Node *pop_back() { assert(0); } // Not efficient with this type of list. + + Node *pop_front() + { + if (!mHead) return NULL; + Node *result = mHead; + mHead = mHead->next(); + if (mTail == result) { mTail = NULL; assert(mHead == NULL); } + result->setNext(NULL); // be neat + mSize--; + mTotalSize -= result->size(); + return result; + } + + void push_front(Node *item) + { + item->setNext(mHead); + mHead = item; + if (!mTail) { mTail = item; } + mSize++; + mTotalSize += item->size(); + } + + void push_back(Node *item) + { + item->setNext(NULL); + if (mTail) { mTail->setNext(item); } + mTail = item; + if (!mHead) mHead = item; + mSize++; + mTotalSize += item->size(); + } + Node *front() { return mHead; } + Node *back() { return mTail; } + + // Interface to InterthreadQueue so it can used SingleLinkList as the Fifo. + void put(void *val) { push_back((Node*)val); } + void *get() { return pop_front(); } +}; + diff --git a/CommonLibs/LogTest.cpp b/CommonLibs/LogTest.cpp index e9f73b0..20959a0 100644 --- a/CommonLibs/LogTest.cpp +++ b/CommonLibs/LogTest.cpp @@ -37,7 +37,7 @@ void printAlarms() { std::ostream_iterator output( std::cout, "\n" ); std::list alarms = gGetLoggerAlarms(); - std::cout << "#alarms = " << alarms.size() << std::endl; + std::cout << "# alarms = " << alarms.size() << std::endl; std::copy( alarms.begin(), alarms.end(), output ); } @@ -55,7 +55,6 @@ int main(int argc, char *argv[]) LOG(DEBUG) << " testing the logger."; std::cout << "\n\n\n"; std::cout << "testing Alarms\n"; - LOG(ALERT) << " testing the logger alarm."; std::cout << "you should see three lines:" << std::endl; printAlarms(); std::cout << "----------- generating 20 alarms ----------" << std::endl; diff --git a/CommonLibs/Logger.cpp b/CommonLibs/Logger.cpp index 36b7696..06e91f6 100644 --- a/CommonLibs/Logger.cpp +++ b/CommonLibs/Logger.cpp @@ -1,6 +1,7 @@ /* * Copyright 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. @@ -32,6 +33,7 @@ #include "Configuration.h" #include "Logger.h" +#include "Threads.h" // pat added using namespace std; @@ -49,36 +51,71 @@ void addAlarm(const string&); +// (pat) If Log messages are printed before the classes in this module are inited +// (which happens when static classes have constructors that do work) +// the OpenBTS just crashes. +// Prevent that by setting sLoggerInited to true when this module is inited. +static bool sLoggerInited = 0; +static struct CheckLoggerInitStatus { + CheckLoggerInitStatus() { sLoggerInited = 1; } +} sCheckloggerInitStatus; + + /** Names of the logging levels. */ const char *levelNames[] = { "EMERG", "ALERT", "CRIT", "ERR", "WARNING", "NOTICE", "INFO", "DEBUG" }; int numLevels = 8; +bool gLogToConsole = 0; +FILE *gLogToFile = NULL; +Mutex gLogToLock; -/** Given a string, return the corresponding level name. */ -int lookupLevel(const string& name) +int levelStringToInt(const string& name) { // Reverse search, since the numerically larger levels are more common. for (int i=numLevels-1; i>=0; i--) { if (name == levelNames[i]) return i; } - // This should never be called with a bogus name. - LOG(ERR) << "undefined logging level " << name << "defaulting to ERR"; - return LOG_ERR; + + // Common substitutions. + if (name=="INFORMATION") return 6; + if (name=="WARN") return 4; + if (name=="ERROR") return 3; + if (name=="CRITICAL") return 2; + if (name=="EMERGENCY") return 0; + + // Unknown level. + return -1; +} + +/** Given a string, return the corresponding level name. */ +int lookupLevel(const string& key) +{ + string val = gConfig.getStr(key); + int level = levelStringToInt(val); + + if (level == -1) { + string defaultLevel = gConfig.mSchema["Log.Level"].getDefaultValue(); + level = levelStringToInt(defaultLevel); + _LOG(CRIT) << "undefined logging level (" << key << " = \"" << val << "\") defaulting to \"" << defaultLevel << ".\" Valid levels are: EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO or DEBUG"; + gConfig.set(key, defaultLevel); + } + + return level; } int getLoggingLevel(const char* filename) { // Default level? - if (!filename) return lookupLevel(gConfig.getStr("Log.Level")); + if (!filename) return lookupLevel("Log.Level"); // This can afford to be inefficient since it is not called that often. const string keyName = string("Log.Level.") + string(filename); - if (gConfig.defines(keyName)) return lookupLevel(gConfig.getStr(keyName)); - return lookupLevel(gConfig.getStr("Log.Level")); + if (gConfig.defines(keyName)) return lookupLevel(keyName); + return lookupLevel("Log.Level"); } @@ -113,6 +150,7 @@ int gGetLoggingLevel(const char* filename) } // Look it up in the config table and cache it. // FIXME: Figure out why unlock and lock below fix the config table deadlock. + // (pat) Probably because getLoggingLevel may call LOG recursively via lookupLevel(). sLogCacheLock.unlock(); int level = getLoggingLevel(filename); sLogCacheLock.lock(); @@ -155,12 +193,30 @@ Log::~Log() // Anything at or above LOG_CRIT is an "alarm". // Save alarms in the local list and echo them to stderr. if (mPriority <= LOG_CRIT) { - addAlarm(mStream.str().c_str()); + if (sLoggerInited) addAlarm(mStream.str().c_str()); cerr << mStream.str() << endl; } // Current logging level was already checked by the macro. // So just log. syslog(mPriority, "%s", mStream.str().c_str()); + // pat added for easy debugging. + if (gLogToConsole||gLogToFile) { + int mlen = mStream.str().size(); + int neednl = (mlen==0 || mStream.str()[mlen-1] != '\n'); + gLogToLock.lock(); + if (gLogToConsole) { + // The COUT() macro prevents messages from stomping each other but adds uninteresting thread numbers, + // so just use std::cout. + std::cout << mStream.str(); + if (neednl) std::cout<<"\n"; + } + if (gLogToFile) { + fputs(mStream.str().c_str(),gLogToFile); + if (neednl) {fputc('\n',gLogToFile);} + fflush(gLogToFile); + } + gLogToLock.unlock(); + } } @@ -182,18 +238,26 @@ ostringstream& Log::get() void gLogInit(const char* name, const char* level, int facility) { - // Set the level. + // Set the level if one has been specified. if (level) { gConfig.set("Log.Level",level); - } else { - if (!gConfig.defines("Log.Level")) { - gConfig.set("Log.Level","WARNING"); - } } - // Define other logging parameters in the global config. - if (!gConfig.defines("Log.Alarms.Max")) { - gConfig.set("Log.Alarms.Max",DEFAULT_MAX_ALARMS); + // Pat added, tired of the syslog facility. + // Both the transceiver and OpenBTS use this same facility, but only OpenBTS/OpenNodeB may use this log file: + string str = gConfig.getStr("Log.File"); + if (gLogToFile==0 && str.length() && 0==strncmp(gCmdName,"Open",4)) { + const char *fn = str.c_str(); + if (fn && *fn && strlen(fn)>3) { // strlen because a garbage char is getting in sometimes. + gLogToFile = fopen(fn,"w"); // New log file each time we start. + if (gLogToFile) { + time_t now; + time(&now); + fprintf(gLogToFile,"Starting at %s",ctime(&now)); + fflush(gLogToFile); + std::cout << "Logging to file: " << fn << "\n"; + } + } } // Open the log connection. diff --git a/CommonLibs/Logger.h b/CommonLibs/Logger.h index 6b1c44d..58dfa22 100644 --- a/CommonLibs/Logger.h +++ b/CommonLibs/Logger.h @@ -23,6 +23,11 @@ */ +// (pat) WARNING is stupidly defined in /usr/local/include/osipparser2/osip_const.h. +// This must be outside the #ifndef LOGGER_H to fix it as long as Logger.h included after the above file. +#ifdef WARNING +#undef WARNING +#endif #ifndef LOGGER_H #define LOGGER_H @@ -34,21 +39,42 @@ #include #include #include -#include "Threads.h" - #define _LOG(level) \ Log(LOG_##level).get() << pthread_self() \ - << " " __FILE__ ":" << __LINE__ << ":" << __FUNCTION__ << ": " + << timestr() << " " __FILE__ ":" << __LINE__ << ":" << __FUNCTION__ << ": " + +#define IS_LOG_LEVEL(wLevel) (gGetLoggingLevel(__FILE__)>=LOG_##wLevel) #ifdef NDEBUG #define LOG(wLevel) \ - if (LOG_##wLevel!=LOG_DEBUG && gGetLoggingLevel(__FILE__)>=LOG_##wLevel) _LOG(wLevel) + if (LOG_##wLevel!=LOG_DEBUG && IS_LOG_LEVEL(wLevel)) _LOG(wLevel) #else #define LOG(wLevel) \ - if (gGetLoggingLevel(__FILE__)>=LOG_##wLevel) _LOG(wLevel) + if (IS_LOG_LEVEL(wLevel)) _LOG(wLevel) #endif +// pat: And for your edification here are the 'levels' as defined in syslog.h: +// LOG_EMERG 0 system is unusable +// LOG_ALERT 1 action must be taken immediately +// LOG_CRIT 2 critical conditions +// LOG_ERR 3 error conditions +// LOG_WARNING 4 warning conditions +// LOG_NOTICE 5 normal, but significant, condition +// LOG_INFO 6 informational message +// LOG_DEBUG 7 debug-level message + +// (pat) added - print out a var and its name. +// Use like this: int descriptive_name; LOG(INFO)< +#include "ScalarTypes.h" +#include "Logger.h" + +namespace Utils { + +struct MemStats { + // Enumerates the classes that are checked. + // Redundancies are ok, for example, we check BitVector and also + // several descendants of BitVector. + enum MemoryNames { + mZeroIsUnused, + mVector, + mVectorData, + mBitVector, + mByteVector, + mByteVectorData, + mRLCRawBlock, + mRLCUplinkDataBlock, + mRLCMessage, + mRLCMsgPacketDownlinkDummyControlBlock, // Redundant with RLCMessage + mTBF, + mLlcEngine, + mSgsnDownlinkMsg, + mRachInfo, + mPdpPdu, + mFECDispatchInfo, + mL3Frame, + msignalVector, + mSoftVector, + mScramblingCode, + mURlcDownSdu, + mURlcPdu, + // Must be last: + mMax, + }; + int mMemTotal[mMax]; // In elements, not bytes. + int mMemNow[mMax]; + const char *mMemName[mMax]; + MemStats(); + void memChkNew(MemoryNames memIndex, const char *id); + void memChkDel(MemoryNames memIndex, const char *id); + void text(std::ostream &os); + // We would prefer to use an unordered_map, but that requires special compile switches. + // What a super great language. + typedef std::map MemMapType; + MemMapType mMemMap; +}; +extern struct MemStats gMemStats; +extern int gMemLeakDebug; + +// This is a memory leak detector. +// Use by putting RN_MEMCHKNEW and RN_MEMCHKDEL in class constructors/destructors, +// or use the DEFINE_MEMORY_LEAK_DETECTOR class and add the defined class +// as an ancestor to the class to be memory leak checked. + +struct MemLabel { + std::string mccKey; + virtual ~MemLabel() { + Int_z &tmp = Utils::gMemStats.mMemMap[mccKey]; tmp = tmp - 1; + } +}; + +#if RN_DISABLE_MEMORY_LEAK_TEST +#define RN_MEMCHKNEW(type) +#define RN_MEMCHKDEL(type) +#define RN_MEMLOG(type,ptr) +#define DEFINE_MEMORY_LEAK_DETECTOR_CLASS(subClass,checkerClass) \ + struct checkerClass {}; +#else + +#define RN_MEMCHKNEW(type) { Utils::gMemStats.memChkNew(Utils::MemStats::m##type,#type); } +#define RN_MEMCHKDEL(type) { Utils::gMemStats.memChkDel(Utils::MemStats::m##type,#type); } + +#define RN_MEMLOG(type,ptr) { \ + static std::string key = format("%s_%s:%d",#type,__FILE__,__LINE__); \ + (ptr)->/* MemCheck##type:: */ mccKey = key; \ + Utils::gMemStats.mMemMap[key]++; \ + } + +// TODO: The above assumes that checkclass is MemCheck ## subClass +#define DEFINE_MEMORY_LEAK_DETECTOR_CLASS(subClass,checkerClass) \ + struct checkerClass : public virtual Utils::MemLabel { \ + checkerClass() { RN_MEMCHKNEW(subClass); } \ + virtual ~checkerClass() { \ + RN_MEMCHKDEL(subClass); \ + } \ + }; + +#endif + +} // namespace Utils + +#endif diff --git a/CommonLibs/ScalarTypes.h b/CommonLibs/ScalarTypes.h new file mode 100644 index 0000000..077d889 --- /dev/null +++ b/CommonLibs/ScalarTypes.h @@ -0,0 +1,136 @@ +/* +* Copyright 2011 Range Networks, Inc. +* All Rights Reserved. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + 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. +*/ + +#ifndef SCALARTYPES_H +#define SCALARTYPES_H +#include // For size_t +#include +//#include "GSMCommon.h" // Was included for Z100Timer + +// We dont bother to define *= /= etc.; you'll have to convert: a*=b; to: a=a*b; +#define _INITIALIZED_SCALAR_BASE_FUNCS(Classname,Basetype,Init) \ + Classname() : value(Init) {} \ + Classname(Basetype wvalue) { value = wvalue; } /* Can set from basetype. */ \ + operator Basetype(void) const { return value; } /* Converts from basetype. */ \ + Basetype operator=(Basetype wvalue) { return value = wvalue; } \ + Basetype* operator&() { return &value; } + +#define _INITIALIZED_SCALAR_ARITH_FUNCS(Basetype) \ + Basetype operator++() { return ++value; } \ + Basetype operator++(int) { return value++; } \ + Basetype operator--() { return --value; } \ + Basetype operator--(int) { return value--; } \ + Basetype operator+=(Basetype wvalue) { return value = value + wvalue; } \ + Basetype operator-=(Basetype wvalue) { return value = value - wvalue; } + +#define _INITIALIZED_SCALAR_FUNCS(Classname,Basetype,Init) \ + _INITIALIZED_SCALAR_BASE_FUNCS(Classname,Basetype,Init) \ + _INITIALIZED_SCALAR_ARITH_FUNCS(Basetype) + + +#define _DECLARE_SCALAR_TYPE(Classname_i,Classname_z,Basetype) \ + template \ + struct Classname_i { \ + Basetype value; \ + _INITIALIZED_SCALAR_FUNCS(Classname_i,Basetype,Init) \ + }; \ + typedef Classname_i<0> Classname_z; + + +// Usage: +// Where 'classname' is one of the types listed below, then: +// classname_z specifies a zero initialized type; +// classname_i initializes the type to the specified value. +// We also define Float_z. +_DECLARE_SCALAR_TYPE(Int_i, Int_z, int) +_DECLARE_SCALAR_TYPE(Char_i, Char_z, signed char) +_DECLARE_SCALAR_TYPE(Int16_i, Int16_z, int16_t) +_DECLARE_SCALAR_TYPE(Int32_i, Int32_z, int32_t) +_DECLARE_SCALAR_TYPE(UInt_i, UInt_z, unsigned) +_DECLARE_SCALAR_TYPE(UChar_i, UChar_z, unsigned char) +_DECLARE_SCALAR_TYPE(UInt16_i, UInt16_z, uint16_t) +_DECLARE_SCALAR_TYPE(UInt32_i, UInt32_z, uint32_t) +_DECLARE_SCALAR_TYPE(Size_t_i, Size_t_z, size_t) + +// Bool is special because it cannot accept some arithmetic funcs +//_DECLARE_SCALAR_TYPE(Bool_i, Bool_z, bool) +template +struct Bool_i { + bool value; + _INITIALIZED_SCALAR_BASE_FUNCS(Bool_i,bool,Init) +}; +typedef Bool_i<0> Bool_z; + +// float is special, because C++ does not permit the template initalization: +struct Float_z { + float value; + _INITIALIZED_SCALAR_FUNCS(Float_z,float,0) +}; +struct Double_z { + double value; + _INITIALIZED_SCALAR_FUNCS(Double_z,double,0) +}; + + +class ItemWithValueAndWidth { + public: + virtual unsigned getValue() const = 0; + virtual unsigned getWidth() const = 0; +}; + +// A Range Networks Field with a specified width. +// See RLCMessages.h for examples. +template +class Field_i : public ItemWithValueAndWidth +{ + public: + unsigned value; + _INITIALIZED_SCALAR_FUNCS(Field_i,unsigned,Init) + unsigned getWidth() const { return Width; } + unsigned getValue() const { return value; } +}; + +// Synonym for Field_i, but no way to do it. +template +class Field_z : public ItemWithValueAndWidth +{ + public: + unsigned value; + _INITIALIZED_SCALAR_FUNCS(Field_z,unsigned,Init) + unsigned getWidth() const { return Width; } + unsigned getValue() const { return value; } +}; + +// This is an uninitialized field. +template +class Field : public ItemWithValueAndWidth +{ + public: + unsigned value; + _INITIALIZED_SCALAR_FUNCS(Field,unsigned,Init) + unsigned getWidth() const { return Width; } + unsigned getValue() const { return value; } +}; + + +// A Z100Timer with an initial value specified. +//template +//class Z100Timer_i : public GSM::Z100Timer { +// public: +// Z100Timer_i() : GSM::Z100Timer(Init) {} +//}; + +#endif diff --git a/CommonLibs/Sockets.cpp b/CommonLibs/Sockets.cpp index dc110c8..dd7527c 100644 --- a/CommonLibs/Sockets.cpp +++ b/CommonLibs/Sockets.cpp @@ -41,6 +41,10 @@ #include + + + + bool resolveAddress(struct sockaddr_in *address, const char *hostAndPort) { assert(address); @@ -255,6 +259,11 @@ void UDPSocket::open(unsigned short localPort) throw SocketError(); } + // pat added: This lets the socket be reused immediately, which is needed if OpenBTS crashes. + int on = 1; + setsockopt(mSocketFD, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + + // bind struct sockaddr_in address; size_t length = sizeof(address); diff --git a/CommonLibs/Threads.cpp b/CommonLibs/Threads.cpp index 7cc8b92..de6520b 100644 --- a/CommonLibs/Threads.cpp +++ b/CommonLibs/Threads.cpp @@ -107,8 +107,9 @@ void Thread::start(void *(*task)(void*), void *arg) { assert(mThread==((pthread_t)0)); bool res; - res = pthread_attr_init(&mAttrib); - assert(!res); + // (pat) Moved initialization to constructor to avoid crash in destructor. + //res = pthread_attr_init(&mAttrib); + //assert(!res); res = pthread_attr_setstacksize(&mAttrib, mStackSize); assert(!res); res = pthread_create(&mThread, &mAttrib, task, arg); diff --git a/CommonLibs/Threads.h b/CommonLibs/Threads.h index eba8e89..a38a73a 100644 --- a/CommonLibs/Threads.h +++ b/CommonLibs/Threads.h @@ -155,12 +155,16 @@ class Thread { public: /** Create a thread in a non-running state. */ - Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) { mStackSize=wStackSize;} + Thread(size_t wStackSize = (65536*4)):mThread((pthread_t)0) { + pthread_attr_init(&mAttrib); // (pat) moved this here. + mStackSize=wStackSize; + } /** Destroy the Thread. It should be stopped and joined. */ + // (pat) If the Thread is destroyed without being started, then mAttrib is undefined. Oops. ~Thread() { pthread_attr_destroy(&mAttrib); } @@ -168,7 +172,7 @@ class Thread { void start(void *(*task)(void*), void *arg); /** Join a thread that will stop on its own. */ - void join() { int s = pthread_join(mThread,NULL); assert(!s); } + void join() { int s = pthread_join(mThread,NULL); assert(!s); mThread = 0; } }; diff --git a/CommonLibs/Timeval.h b/CommonLibs/Timeval.h index f528819..c497864 100644 --- a/CommonLibs/Timeval.h +++ b/CommonLibs/Timeval.h @@ -32,6 +32,7 @@ #include + /** A wrapper on usleep to sleep for milliseconds. */ inline void msleep(long v) { usleep(v*1000); } diff --git a/CommonLibs/URLEncode.cpp b/CommonLibs/URLEncode.cpp index 025ef60..cdf38dd 100644 --- a/CommonLibs/URLEncode.cpp +++ b/CommonLibs/URLEncode.cpp @@ -1,27 +1,4 @@ -/* -* Copyright 2011 Free Software Foundation, 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 . - -*/ +/* Copyright 2011, Range Networks, Inc. */ #include #include diff --git a/CommonLibs/URLEncodeTest.cpp b/CommonLibs/URLEncodeTest.cpp new file mode 100644 index 0000000..dbc4630 --- /dev/null +++ b/CommonLibs/URLEncodeTest.cpp @@ -0,0 +1,17 @@ + +#include "URLEncode.h" +#include +#include + + +using namespace std; + + +int main(int argc, char *argv[]) +{ + + string test = string("Testing: !@#$%^&*() " __DATE__ " " __TIME__); + cout << test << endl; + cout << URLEncode(test) << endl; +} + diff --git a/CommonLibs/Utils.cpp b/CommonLibs/Utils.cpp new file mode 100644 index 0000000..1da95fa --- /dev/null +++ b/CommonLibs/Utils.cpp @@ -0,0 +1,211 @@ +/* +* Copyright 2011 Range Networks, Inc. +* All Rights Reserved. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + 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. +*/ + +#include // For usleep +#include // For gettimeofday +#include // For vsnprintf +#include // For ostream +#include // For ostringstream +#include // For strcpy +//#include "GSMCommon.h" +#include "Utils.h" +#include "MemoryLeak.h" + +namespace Utils { + +MemStats gMemStats; +int gMemLeakDebug = 0; + +MemStats::MemStats() +{ + memset(mMemNow,0,sizeof(mMemNow)); + memset(mMemTotal,0,sizeof(mMemTotal)); + memset(mMemName,0,sizeof(mMemName)); +} + +void MemStats::text(std::ostream &os) +{ + os << "Structs current total:\n"; + for (int i = 0; i < mMax; i++) { + os << "\t" << (mMemName[i] ? mMemName[i] : "unknown") << " " << mMemNow[i] << " " << mMemTotal[i] << "\n"; + } +} + +void MemStats::memChkNew(MemoryNames memIndex, const char *id) +{ + /*std::cout << "new " #type "\n";*/ + mMemNow[memIndex]++; + mMemTotal[memIndex]++; + mMemName[memIndex] = id; +} + +void MemStats::memChkDel(MemoryNames memIndex, const char *id) +{ + /*std::cout << "del " #type "\n";*/ + mMemNow[memIndex]--; + if (mMemNow[memIndex] < 0) { + LOG(ERR) << "Memory underflow on type "<= (300-4)) { strcpy(&buf[(300-4)],"..."); } + os << buf; + return os; +} + +std::string format(const char *fmt, ...) +{ + va_list ap; + char buf[300]; + va_start(ap,fmt); + int n = vsnprintf(buf,300,fmt,ap); + va_end(ap); + if (n >= (300-4)) { strcpy(&buf[(300-4)],"..."); } + return std::string(buf); +} + +// Return time in seconds with high resolution. +// Note: In the past I found this to be a surprisingly expensive system call in linux. +double timef() +{ + struct timeval tv; + gettimeofday(&tv,NULL); + return tv.tv_usec / 1000000.0 + tv.tv_sec; +} + +const std::string timestr() +{ + struct timeval tv; + struct tm tm; + gettimeofday(&tv,NULL); + localtime_r(&tv.tv_sec,&tm); + unsigned tenths = tv.tv_usec / 100000; // Rounding down is ok. + return format(" %02d:%02d:%02d.%1d",tm.tm_hour,tm.tm_min,tm.tm_sec,tenths); +} + +// High resolution sleep for the specified time. +// Return FALSE if time is already past. +void sleepf(double howlong) +{ + if (howlong <= 0.00001) return; // Less than 10 usecs, forget it. + usleep((useconds_t) (1000000.0 * howlong)); +} + +//bool sleepuntil(double untilwhen) +//{ + //double now = timef(); + //double howlong = untilwhen - now; // Fractional time in seconds. + // We are not worrying about overflow because all times should be in the near future. + //if (howlong <= 0.00001) return false; // Less than 10 usecs, forget it. + //sleepf(sleeptime); +//} + +std::string Text2Str::str() const +{ + std::ostringstream ss; + text(ss); + return ss.str(); +} + +std::ostream& operator<<(std::ostream& os, const Text2Str *val) +{ + std::ostringstream ss; + if (val) { + val->text(ss); + os << ss.str(); + } else { + os << "(null)"; + } + return os; +} + +// Greatest Common Denominator. +// This is by Doug Brown. +int gcd(int x, int y) +{ + if (x > y) { + return x % y == 0 ? y : gcd(y, x % y); + } else { + return y % x == 0 ? x : gcd(x, y % x); + } +} + + +// Split a C string into an argc,argv array in place; the input string is modified. +// Returns argc, and places results in argv, up to maxargc elements. +// The final argv receives the rest of the input string from maxargc on, +// even if it contains additional splitchars. +// The correct idiom for use is to make a copy of your string, like this: +// char *copy = strcpy((char*)alloca(the_string.length()+1),the_string.c_str()); +// char *argv[2]; +// int argc = cstrSplit(copy,argv,2,NULL); +// If you want to detect the error of too many arguments, add 1 to argv, like this: +// char *argv[3]; +// int argc = cstrSplit(copy,argv,3,NULL); +// if (argc == 3) { error("too many arguments"; } +int cstrSplit(char *in, char **pargv,int maxargc, const char *splitchars) +{ + if (splitchars == NULL) { splitchars = " \t\r\n"; } // Default is any space. + int argc = 0; + while (argc < maxargc) { + while (*in && strchr(splitchars,*in)) {in++;} // scan past any splitchars + if (! *in) return argc; // return if finished. + pargv[argc++] = in; // save ptr to start of arg. + in = strpbrk(in,splitchars); // go to end of arg. + if (!in) return argc; // return if finished. + *in++ = 0; // zero terminate this arg. + } + return argc; +} + +std::ostream& operator<<(std::ostream& os, const Statistic &stat) { stat.text(os); return os; } +std::ostream& operator<<(std::ostream& os, const Statistic &stat) { stat.text(os); return os; } +std::ostream& operator<<(std::ostream& os, const Statistic &stat) { stat.text(os); return os; } +std::ostream& operator<<(std::ostream& os, const Statistic &stat) { stat.text(os); return os; } + +std::string replaceAll(const std::string input, const std::string search, const std::string replace) +{ + std::string output = input; + int 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; +} + +}; diff --git a/CommonLibs/Utils.h b/CommonLibs/Utils.h new file mode 100644 index 0000000..0bc738e --- /dev/null +++ b/CommonLibs/Utils.h @@ -0,0 +1,148 @@ +/* +* Copyright 2011 Range Networks, Inc. +* All Rights Reserved. +* +* This software is distributed under multiple licenses; +* see the COPYING file in the main directory for licensing +* information for this specific distribuion. +* +* This use of this software may be subject to additional restrictions. +* See the LEGAL file in the main directory for details. + + 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. +*/ + +#ifndef GPRSUTILS_H +#define GPRSUTILS_H +#include +#include +#include +#include +#include // for sqrtf +#include "Logger.h" + + +namespace Utils { + +extern double timef(); // high resolution time +extern const std::string timestr(); // A timestamp to print in messages. +extern void sleepf(double howlong); // high resolution sleep +extern int gcd(int x, int y); + +// It is irritating to create a string just to interface to the brain-damaged +// C++ stream class, but this is only used for debug messages. +std::string format(const char *fmt, ...) __attribute__((format (printf,1,2))); + +int cstrSplit(char *in, char **pargv,int maxargc, const char *splitchars=NULL); + +// For classes with a text() function, provide a function to return a String, +// and also a standard << stream function that takes a pointer to the object. +// We dont provide the function that takes a reference to the object +// because it is too highly overloaded and generally doesnt work. +class Text2Str { + public: + virtual void text(std::ostream &os) const = 0; + std::string str() const; +}; +std::ostream& operator<<(std::ostream& os, const Text2Str *val); + +#if 0 +// Generic Activity Timer. Lots of controls to make everybody happy. +class ATimer { + double mStart; + //bool mActive; + double mLimitTime; + public: + ATimer() : mStart(0), mLimitTime(0) { } + ATimer(double wLimitTime) : mStart(0), mLimitTime(wLimitTime) { } + void start() { mStart=timef(); } + void stop() { mStart=0; } + bool active() { return !!mStart; } + double elapsed() { return timef() - mStart; } + bool expired() { return elapsed() > mLimitTime; } +}; +#endif + + +struct BitSet { + unsigned mBits; + void setBit(unsigned whichbit) { mBits |= 1< struct Statistic { + Type mCurrent, mMin, mMax; // min,max optional initialization so you can print before adding any values. + unsigned mCnt; + double mSum; + //double mSum2; // sum of squares. + // (Type) cast needed in case Type is an enum, stupid language. + Statistic() : mCurrent((Type)0), mMin((Type)0), mMax((Type)0), mCnt(0), mSum(0) /*,mSum2(0)*/ {} + // Set the current value and add a statisical point. + void addPoint(Type val) { + mCurrent = val; + if (mCnt == 0 || val < mMin) {mMin = val;} + if (mCnt == 0 || val > mMax) {mMax = val;} + mCnt++; + mSum += val; + //mSum2 += val * val; + } + Type getCurrent() const { // Return current value. + return mCnt ? mCurrent : 0; + } + double getAvg() const { // Return average. + return mCnt==0 ? 0 : mSum/mCnt; + }; + //float getSD() const { // Return standard deviation. Use low precision square root function. + // return mCnt==0 ? 0 : sqrtf(mCnt * mSum2 - mSum*mSum) / mCnt; + //} + + void text(std::ostream &os) const { // Print everything in parens. + os << "("< &stat); +std::ostream& operator<<(std::ostream& os, const Statistic &stat); +std::ostream& operator<<(std::ostream& os, const Statistic &stat); +std::ostream& operator<<(std::ostream& os, const Statistic &stat); + + +// Yes, they botched and left this out: +std::ostream& operator<<(std::ostream& os, std::ostringstream& ss); + +std::ostream &osprintf(std::ostream &os, const char *fmt, ...) __attribute__((format (printf,2,3))); + +std::string replaceAll(const std::string input, const std::string search, const std::string replace); + +}; // namespace + +using namespace Utils; + +#endif diff --git a/CommonLibs/Vector.h b/CommonLibs/Vector.h index 62cb6fb..38dc8d5 100644 --- a/CommonLibs/Vector.h +++ b/CommonLibs/Vector.h @@ -32,6 +32,10 @@ #include #include #include +// We cant use Logger.h in this file... +extern int gVectorDebug; +#define BVDEBUG(msg) if (gVectorDebug) {std::cout << msg;} + /** @@ -59,6 +63,14 @@ template class Vector { public: + /**** + char *inspect() { + static char buf[100]; + sprintf(buf," mData=%p mStart=%p mEnd=%p ",mData,mStart,mEnd); + return buf; + } + ***/ + /** Return the size of the Vector. */ size_t size() const { @@ -246,6 +258,7 @@ template class Vector { T* begin() { return mStart; } const T* end() const { return mEnd; } T* end() { return mEnd; } + bool isOwner() { return !!mData; } // Do we own any memory ourselves? //@} diff --git a/CommonLibs/VectorTest.cpp b/CommonLibs/VectorTest.cpp index 6958889..ad5c473 100644 --- a/CommonLibs/VectorTest.cpp +++ b/CommonLibs/VectorTest.cpp @@ -28,6 +28,10 @@ #include "Vector.h" #include +// We must have a gConfig now to include Vector. +#include "Configuration.h" +ConfigurationTable gConfig; + using namespace std; typedef Vector TestVector; -- cgit v1.2.3