blob: bda68657c54d92587e02f14814136b7e55b9d323 [file] [log] [blame]
dburgess82c46ff2011-10-07 02:40:51 +00001/*
2* Copyright 2008, 2009, 2010 Free Software Foundation, Inc.
3* Copyright 2010 Kestrel Signal Processing, Inc.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +00004* Copyright 2011, 2012 Range Networks, Inc.
dburgess82c46ff2011-10-07 02:40:51 +00005*
6*
7* This software is distributed under the terms of the GNU Affero Public License.
8* See the COPYING file in the main directory for details.
9*
10* This use of this software may be subject to additional restrictions.
11* See the LEGAL file in the main directory for details.
12
13 This program is free software: you can redistribute it and/or modify
14 it under the terms of the GNU Affero General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU Affero General Public License for more details.
22
23 You should have received a copy of the GNU Affero General Public License
24 along with this program. If not, see <http://www.gnu.org/licenses/>.
25
26*/
27
28
29#include "Configuration.h"
kurtis.heimerl00913d72012-12-16 06:08:18 +000030#include "Logger.h"
dburgess82c46ff2011-10-07 02:40:51 +000031#include <fstream>
32#include <iostream>
33#include <string.h>
kurtis.heimerl00913d72012-12-16 06:08:18 +000034
kurtis.heimerl5a872472013-05-31 21:47:25 +000035#ifdef DEBUG_CONFIG
36#define debugLogEarly gLogEarly
37#else
38#define debugLogEarly
39#endif
40
dburgess82c46ff2011-10-07 02:40:51 +000041
42using namespace std;
43
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000044char gCmdName[20] = {0}; // Use a char* to avoid avoid static initialization of string, and race at startup.
dburgess82c46ff2011-10-07 02:40:51 +000045
46static const char* createConfigTable = {
47 "CREATE TABLE IF NOT EXISTS CONFIG ("
48 "KEYSTRING TEXT UNIQUE NOT NULL, "
49 "VALUESTRING TEXT, "
50 "STATIC INTEGER DEFAULT 0, "
51 "OPTIONAL INTEGER DEFAULT 0, "
52 "COMMENTS TEXT DEFAULT ''"
53 ")"
54};
55
56
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000057
58float ConfigurationRecord::floatNumber() const
dburgess82c46ff2011-10-07 02:40:51 +000059{
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000060 float val;
61 sscanf(mValue.c_str(),"%f",&val);
62 return val;
63}
64
65
kurtis.heimerl5a872472013-05-31 21:47:25 +000066ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema)
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000067{
kurtis.heimerl00913d72012-12-16 06:08:18 +000068 gLogEarly(LOG_INFO, "opening configuration table from path %s", filename);
dburgess82c46ff2011-10-07 02:40:51 +000069 // Connect to the database.
70 int rc = sqlite3_open(filename,&mDB);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000071 // (pat) When I used malloc here, sqlite3 sporadically crashes.
72 if (wCmdName) {
73 strncpy(gCmdName,wCmdName,18);
74 gCmdName[18] = 0;
75 strcat(gCmdName,":");
76 }
dburgess82c46ff2011-10-07 02:40:51 +000077 if (rc) {
kurtis.heimerl00913d72012-12-16 06:08:18 +000078 gLogEarly(LOG_EMERG, "cannot open configuration database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
dburgess82c46ff2011-10-07 02:40:51 +000079 sqlite3_close(mDB);
80 mDB = NULL;
81 return;
82 }
83 // Create the table, if needed.
84 if (!sqlite3_command(mDB,createConfigTable)) {
kurtis.heimerl00913d72012-12-16 06:08:18 +000085 gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
dburgess82c46ff2011-10-07 02:40:51 +000086 }
kurtis.heimerl5a872472013-05-31 21:47:25 +000087
88 // Build CommonLibs schema
89 ConfigurationKey *tmp;
90 tmp = new ConfigurationKey("Log.Alarms.Max","20",
91 "alarms",
92 ConfigurationKey::CUSTOMER,
93 ConfigurationKey::VALRANGE,
94 "10:20",// educated guess
95 false,
96 "Maximum number of alarms to remember inside the application."
97 );
98 mSchema[tmp->getName()] = *tmp;
99 free(tmp);
100
101 tmp = new ConfigurationKey("Log.File","",
102 "",
103 ConfigurationKey::DEVELOPER,
104 ConfigurationKey::FILEPATH_OPT,// audited
105 "",
106 false,
107 "Path to use for textfile based logging. "
108 "By default, this feature is disabled. "
109 "To enable, specify an absolute path to the file you wish to use, eg: /tmp/my-debug.log. "
110 "To disable again, execute \"unconfig Log.File\"."
111 );
112 mSchema[tmp->getName()] = *tmp;
113 free(tmp);
114
115 tmp = new ConfigurationKey("Log.Level","NOTICE",
116 "",
117 ConfigurationKey::CUSTOMER,
118 ConfigurationKey::CHOICE,
119 "EMERG|EMERGENCY - report serious faults associated with service failure or hardware damage,"
120 "ALERT|ALERT - report likely service disruption caused by misconfiguration or poor connectivity,"
121 "CRIT|CRITICAL - report anomalous events that are likely to degrade service,"
122 "ERR|ERROR - report internal errors of the software that may result in degradation of service in unusual circumstances,"
123 "WARNING|WARNING - report anomalous events that may indicate a degradation of normal service,"
124 "NOTICE|NOTICE - report anomalous events that probably do not affect service but may be of interest to network operators,"
125 "INFO|INFORMATION - report normal events,"
126 "DEBUG|DEBUG - only for use by developers and will degrade system performance",
127 false,
128 "Default logging level when no other level is defined for a file."
129 );
130 mSchema[tmp->getName()] = *tmp;
131 free(tmp);
132
133 // Add application specific schema
134 mSchema.insert(wSchema.begin(), wSchema.end());
135
136 // Init the cross checking callback to something predictable
137 mCrossCheck = NULL;
dburgess82c46ff2011-10-07 02:40:51 +0000138}
139
kurtis.heimerl5a872472013-05-31 21:47:25 +0000140string ConfigurationTable::getDefaultSQL(const std::string& program, const std::string& version)
141{
142 stringstream ss;
143 ConfigurationKeyMap::iterator mp;
dburgess82c46ff2011-10-07 02:40:51 +0000144
kurtis.heimerl5a872472013-05-31 21:47:25 +0000145 ss << "--" << endl;
146 ss << "-- This file was generated using: " << program << " --gensql" << endl;
147 ss << "-- binary version: " << version << endl;
148 ss << "--" << endl;
149 ss << "-- Future changes should not be put in this file directly but" << endl;
150 ss << "-- rather in the program's ConfigurationKey schema." << endl;
151 ss << "--" << endl;
152 ss << "PRAGMA foreign_keys=OFF;" << endl;
153 ss << "BEGIN TRANSACTION;" << endl;
154 ss << "CREATE TABLE CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT '');" << endl;
155
156 mp = mSchema.begin();
157 while (mp != mSchema.end()) {
158 ss << "INSERT INTO \"CONFIG\" VALUES(";
159 // name
160 ss << "'" << mp->first << "',";
161 // default
162 ss << "'" << mp->second.getDefaultValue() << "',";
163 // static
164 if (mp->second.isStatic()) {
165 ss << "1";
166 } else {
167 ss << "0";
168 }
169 ss << ",";
170 // optional
171 ss << "0,";
172 // description
173 ss << "'";
174 if (mp->second.getType() == ConfigurationKey::BOOLEAN) {
175 ss << "1=enabled, 0=disabled - ";
176 }
177 ss << mp->second.getDescription();
178 if (mp->second.isStatic()) {
179 ss << " Static.";
180 }
181 ss << "'";
182 ss << ");" << endl;
183 mp++;
184 }
185
186 ss << "COMMIT;" << endl;
187 ss << endl;
188
189 return ss.str();
190}
191
192string ConfigurationTable::getTeX(const std::string& program, const std::string& version)
193{
194 stringstream ss;
195 ConfigurationKeyMap::iterator mp;
196
197 ss << "% START AUTO-GENERATED CONTENT" << endl;
198 ss << "% -- these sections were generated using: " << program << " --gentex" << endl;
199 ss << "% -- binary version: " << version << endl;
200
201 ss << "\\subsection{Customer Site Parameters}" << endl;
202 ss << "These parameters must be changed to fit your site." << endl;
203 ss << "\\begin{itemize}" << endl;
204 mp = mSchema.begin();
205 while (mp != mSchema.end()) {
206 if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) {
207 ss << " \\item ";
208 // name
209 ss << mp->first << " -- ";
210 // description
211 ss << mp->second.getDescription();
212 ss << endl;
213 }
214 mp++;
215 }
216 ss << "\\end{itemize}" << endl;
217 ss << endl;
218
219 ss << "\\subsection{Customer Tuneable Parameters}" << endl;
220 ss << "These parameters can be changed to optimize your site." << endl;
221 ss << "\\begin{itemize}" << endl;
222 mp = mSchema.begin();
223 while (mp != mSchema.end()) {
224 if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE &&
225 (
226 mp->second.getVisibility() == ConfigurationKey::CUSTOMER ||
227 mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE ||
228 mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN
229 )) {
230 ss << " \\item ";
231 // name
232 ss << mp->first << " -- ";
233 // description
234 ss << mp->second.getDescription();
235 ss << endl;
236 }
237 mp++;
238 }
239 ss << "\\end{itemize}" << endl;
240 ss << endl;
241
242 ss << "\\subsection{Developer/Factory Parameters}" << endl;
243 ss << "These parameters should only be changed by when developing new code." << endl;
244 ss << "\\begin{itemize}" << endl;
245 mp = mSchema.begin();
246 while (mp != mSchema.end()) {
247 if (mp->second.getVisibility() == ConfigurationKey::FACTORY ||
248 mp->second.getVisibility() == ConfigurationKey::DEVELOPER) {
249 ss << " \\item ";
250 // name
251 ss << mp->first << " -- ";
252 // description
253 ss << mp->second.getDescription();
254 ss << endl;
255 }
256 mp++;
257 }
258 ss << "\\end{itemize}" << endl;
259 ss << "% END AUTO-GENERATED CONTENT" << endl;
260 ss << endl;
261
262 string tmp = Utils::replaceAll(ss.str(), "^", "\\^");
263 return Utils::replaceAll(tmp, "_", "\\_");
264}
dburgess82c46ff2011-10-07 02:40:51 +0000265
266bool ConfigurationTable::defines(const string& key)
267{
kurtis.heimerl5a872472013-05-31 21:47:25 +0000268 try {
269 ScopedLock lock(mLock);
270 return lookup(key).defined();
271 } catch (ConfigurationTableKeyNotFound) {
272 debugLogEarly(LOG_ALERT, "configuration parameter %s not found", key.c_str());
273 return false;
dburgess82c46ff2011-10-07 02:40:51 +0000274 }
dburgess82c46ff2011-10-07 02:40:51 +0000275}
276
kurtis.heimerl5a872472013-05-31 21:47:25 +0000277bool ConfigurationTable::keyDefinedInSchema(const std::string& name)
278{
279 return mSchema.find(name) == mSchema.end() ? false : true;
280}
281
282bool ConfigurationTable::isValidValue(const std::string& name, const std::string& val) {
283 bool ret = false;
284
285 ConfigurationKey key = mSchema[name];
286
287 switch (key.getType()) {
288 case ConfigurationKey::BOOLEAN: {
289 if (val == "1" || val == "0") {
290 ret = true;
291 }
292 break;
293 }
294
295 case ConfigurationKey::CHOICE_OPT: {
296 if (val.length() == 0) {
297 ret = true;
298 break;
299 }
300 }
301 case ConfigurationKey::CHOICE: {
302 int startPos = -1;
303 uint endPos = 0;
304
305 std::string tmp = key.getValidValues();
306
307 do {
308 startPos++;
309 if ((endPos = tmp.find('|', startPos)) != std::string::npos) {
310 if (val == tmp.substr(startPos, endPos-startPos)) {
311 ret = true;
312 break;
313 }
314 } else {
315 if (val == tmp.substr(startPos, tmp.find(',', startPos)-startPos)) {
316 ret = true;
317 break;
318 }
319 }
320
321 } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
322 break;
323 }
324
325 case ConfigurationKey::CIDR_OPT: {
326 if (val.length() == 0) {
327 ret = true;
328 break;
329 }
330 }
331 case ConfigurationKey::CIDR: {
332 uint delimiter;
333 std::string ip;
334 int cidr = -1;
335
336 delimiter = val.find('/');
337 if (delimiter != std::string::npos) {
338 ip = val.substr(0, delimiter);
339 std::stringstream(val.substr(delimiter+1)) >> cidr;
340 if (ConfigurationKey::isValidIP(ip) && 0 <= cidr && cidr <= 32) {
341 ret = true;
342 }
343 }
344 break;
345 }
346
347 case ConfigurationKey::FILEPATH_OPT: {
348 if (val.length() == 0) {
349 ret = true;
350 break;
351 }
352 }
353 case ConfigurationKey::FILEPATH: {
354 regex_t r;
355 const char* expression = "^[a-zA-Z0-9/_.-]+$";
356 int result = regcomp(&r, expression, REG_EXTENDED);
357 if (result) {
358 char msg[256];
359 regerror(result,&r,msg,255);
360 break;//abort();
361 }
362 if (regexec(&r, val.c_str(), 0, NULL, 0)==0) {
363 ret = true;
364 }
365 regfree(&r);
366 break;
367 }
368
369 case ConfigurationKey::IPADDRESS_OPT: {
370 if (val.length() == 0) {
371 ret = true;
372 break;
373 }
374 }
375 case ConfigurationKey::IPADDRESS: {
376 ret = ConfigurationKey::isValidIP(val);
377 break;
378 }
379
380 case ConfigurationKey::IPANDPORT: {
381 uint delimiter;
382 std::string ip;
383 int port = -1;
384
385 delimiter = val.find(':');
386 if (delimiter != std::string::npos) {
387 ip = val.substr(0, delimiter);
388 std::stringstream(val.substr(delimiter+1)) >> port;
389 if (ConfigurationKey::isValidIP(ip) && 1 <= port && port <= 65535) {
390 ret = true;
391 }
392 }
393 break;
394 }
395
396 case ConfigurationKey::MIPADDRESS_OPT: {
397 if (val.length() == 0) {
398 ret = true;
399 break;
400 }
401 }
402 case ConfigurationKey::MIPADDRESS: {
403 int startPos = -1;
404 uint endPos = 0;
405
406 do {
407 startPos++;
408 endPos = val.find(' ', startPos);
409 if (ConfigurationKey::isValidIP(val.substr(startPos, endPos-startPos))) {
410 ret = true;
411 } else {
412 ret = false;
413 break;
414 }
415
416 } while ((startPos = endPos) != (int)std::string::npos);
417 break;
418 }
419
420 case ConfigurationKey::PORT_OPT: {
421 if (val.length() == 0) {
422 ret = true;
423 break;
424 }
425 }
426 case ConfigurationKey::PORT: {
427 int intVal;
428
429 std::stringstream(val) >> intVal;
430
431 if (1 <= intVal && intVal <= 65535) {
432 ret = true;
433 }
434 break;
435 }
436
437 case ConfigurationKey::REGEX_OPT: {
438 if (val.length() == 0) {
439 ret = true;
440 break;
441 }
442 }
443 case ConfigurationKey::REGEX: {
444 regex_t r;
445 const char* expression = val.c_str();
446 int result = regcomp(&r, expression, REG_EXTENDED);
447 if (result) {
448 char msg[256];
449 regerror(result,&r,msg,255);
450 } else {
451 ret = true;
452 }
453 regfree(&r);
454 break;
455 }
456
457 case ConfigurationKey::STRING_OPT: {
458 if (val.length() == 0) {
459 ret = true;
460 break;
461 }
462 }
463 case ConfigurationKey::STRING: {
464 regex_t r;
465 const char* expression = key.getValidValues().c_str();
466 int result = regcomp(&r, expression, REG_EXTENDED);
467 if (result) {
468 char msg[256];
469 regerror(result,&r,msg,255);
470 break;//abort();
471 }
472 if (regexec(&r, val.c_str(), 0, NULL, 0)==0) {
473 ret = true;
474 }
475 regfree(&r);
476 break;
477 }
478
479 case ConfigurationKey::VALRANGE: {
480 regex_t r;
481 int result;
482 if (key.getValidValues().find('.') != std::string::npos) {
483 result = regcomp(&r, "^[0-9.-]+$", REG_EXTENDED);
484 } else {
485 result = regcomp(&r, "^[0-9-]+$", REG_EXTENDED);
486 }
487 if (result) {
488 char msg[256];
489 regerror(result,&r,msg,255);
490 break;//abort();
491 }
492 if (regexec(&r, val.c_str(), 0, NULL, 0)!=0) {
493 ret = false;
494 } else if (key.getValidValues().find('.') != std::string::npos) {
495 ret = ConfigurationKey::isInValRange<float>(key, val, false);
496 } else {
497 ret = ConfigurationKey::isInValRange<int>(key, val, true);
498 }
499
500 regfree(&r);
501 break;
502 }
503 }
504
505 return ret;
506}
507
508ConfigurationKeyMap ConfigurationTable::getSimilarKeys(const std::string& snippet) {
509 ConfigurationKeyMap tmp;
510
511 ConfigurationKeyMap::const_iterator mp = mSchema.begin();
512 while (mp != mSchema.end()) {
513 if (mp->first.find(snippet) != std::string::npos) {
514 tmp[mp->first] = mp->second;
515 }
516 mp++;
517 }
518
519 return tmp;
520}
dburgess82c46ff2011-10-07 02:40:51 +0000521
522const ConfigurationRecord& ConfigurationTable::lookup(const string& key)
523{
524 assert(mDB);
525 checkCacheAge();
526 // We assume the caller holds mLock.
527 // So it is OK to return a reference into the cache.
528
529 // Check the cache.
530 // This is cheap.
531 ConfigurationMap::const_iterator where = mCache.find(key);
532 if (where!=mCache.end()) {
533 if (where->second.defined()) return where->second;
dburgess82c46ff2011-10-07 02:40:51 +0000534 throw ConfigurationTableKeyNotFound(key);
535 }
536
537 // Check the database.
538 // This is more expensive.
539 char *value = NULL;
540 sqlite3_single_lookup(mDB,"CONFIG",
541 "KEYSTRING",key.c_str(),"VALUESTRING",value);
542
kurtis.heimerl5a872472013-05-31 21:47:25 +0000543 // value found, cache the result
544 if (value) {
545 mCache[key] = ConfigurationRecord(value);
546 // key definition found, cache the default
547 } else if (keyDefinedInSchema(key)) {
548 mCache[key] = ConfigurationRecord(mSchema[key].getDefaultValue());
549 // total miss, cache the error
550 } else {
dburgess82c46ff2011-10-07 02:40:51 +0000551 mCache[key] = ConfigurationRecord(false);
dburgess82c46ff2011-10-07 02:40:51 +0000552 throw ConfigurationTableKeyNotFound(key);
553 }
554
dburgess82c46ff2011-10-07 02:40:51 +0000555 free(value);
556
557 // Leave mLock locked. The caller holds it still.
558 return mCache[key];
559}
560
561
562
kurtis.heimerl5a872472013-05-31 21:47:25 +0000563bool ConfigurationTable::isStatic(const string& key)
dburgess82c46ff2011-10-07 02:40:51 +0000564{
kurtis.heimerl5a872472013-05-31 21:47:25 +0000565 if (keyDefinedInSchema(key)) {
566 return mSchema[key].isStatic();
567 } else {
568 return false;
569 }
dburgess82c46ff2011-10-07 02:40:51 +0000570}
571
572
573
574
575string ConfigurationTable::getStr(const string& key)
576{
577 // We need the lock because rec is a reference into the cache.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000578 try {
579 ScopedLock lock(mLock);
580 return lookup(key).value();
581 } catch (ConfigurationTableKeyNotFound) {
582 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000583 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000584 throw ConfigurationTableKeyNotFound(key);
585 }
dburgess82c46ff2011-10-07 02:40:51 +0000586}
587
dburgess82c46ff2011-10-07 02:40:51 +0000588
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000589bool ConfigurationTable::getBool(const string& key)
590{
591 try {
592 return getNum(key) != 0;
593 } catch (ConfigurationTableKeyNotFound) {
kurtis.heimerl5a872472013-05-31 21:47:25 +0000594 // Raise an alert and re-throw the exception.
595 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
596 throw ConfigurationTableKeyNotFound(key);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000597 }
598}
599
600
dburgess82c46ff2011-10-07 02:40:51 +0000601long ConfigurationTable::getNum(const string& key)
602{
603 // We need the lock because rec is a reference into the cache.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000604 try {
605 ScopedLock lock(mLock);
606 return lookup(key).number();
607 } catch (ConfigurationTableKeyNotFound) {
608 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000609 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000610 throw ConfigurationTableKeyNotFound(key);
611 }
dburgess82c46ff2011-10-07 02:40:51 +0000612}
613
614
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000615float ConfigurationTable::getFloat(const string& key)
616{
kurtis.heimerl5a872472013-05-31 21:47:25 +0000617 try {
618 ScopedLock lock(mLock);
619 return lookup(key).floatNumber();
620 } catch (ConfigurationTableKeyNotFound) {
621 // Raise an alert and re-throw the exception.
622 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
623 throw ConfigurationTableKeyNotFound(key);
624 }
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000625}
626
627std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key)
628{
629 // Look up the string.
630 char *line=NULL;
631 try {
632 ScopedLock lock(mLock);
633 const ConfigurationRecord& rec = lookup(key);
634 line = strdup(rec.value().c_str());
635 } catch (ConfigurationTableKeyNotFound) {
636 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000637 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000638 throw ConfigurationTableKeyNotFound(key);
639 }
640
641 assert(line);
642 char *lp = line;
643
644 // Parse the string.
645 std::vector<string> retVal;
646 while (lp) {
647 while (*lp==' ') lp++;
648 if (*lp == '\0') break;
649 char *tp = strsep(&lp," ");
650 if (!tp) break;
651 retVal.push_back(tp);
652 }
653 free(line);
654 return retVal;
655}
656
657
dburgess82c46ff2011-10-07 02:40:51 +0000658std::vector<unsigned> ConfigurationTable::getVector(const string& key)
659{
660 // Look up the string.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000661 char *line=NULL;
662 try {
663 ScopedLock lock(mLock);
664 const ConfigurationRecord& rec = lookup(key);
665 line = strdup(rec.value().c_str());
666 } catch (ConfigurationTableKeyNotFound) {
667 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000668 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000669 throw ConfigurationTableKeyNotFound(key);
670 }
671
672 assert(line);
673 char *lp = line;
674
dburgess82c46ff2011-10-07 02:40:51 +0000675 // Parse the string.
676 std::vector<unsigned> retVal;
dburgess82c46ff2011-10-07 02:40:51 +0000677 while (lp) {
678 // Watch for multiple or trailing spaces.
679 while (*lp==' ') lp++;
680 if (*lp=='\0') break;
681 retVal.push_back(strtol(lp,NULL,0));
682 strsep(&lp," ");
683 }
684 free(line);
685 return retVal;
686}
687
688
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000689bool ConfigurationTable::remove(const string& key)
690{
691 assert(mDB);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000692
693 ScopedLock lock(mLock);
694 // Clear the cache entry and the database.
695 ConfigurationMap::iterator where = mCache.find(key);
696 if (where!=mCache.end()) mCache.erase(where);
697 // Really remove it.
698 string cmd = "DELETE FROM CONFIG WHERE KEYSTRING=='"+key+"'";
699 return sqlite3_command(mDB,cmd.c_str());
700}
701
702
dburgess82c46ff2011-10-07 02:40:51 +0000703
704void ConfigurationTable::find(const string& pat, ostream& os) const
705{
706 // Prepare the statement.
707 string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG WHERE KEYSTRING LIKE \"%" + pat + "%\"";
708 sqlite3_stmt *stmt;
709 if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return;
710 // Read the result.
711 int src = sqlite3_run_query(mDB,stmt);
712 while (src==SQLITE_ROW) {
713 const char* value = (const char*)sqlite3_column_text(stmt,1);
714 os << sqlite3_column_text(stmt,0) << " ";
kurtis.heimerl5a872472013-05-31 21:47:25 +0000715 int len = 0;
716 if (value) {
717 len = strlen(value);
718 }
719 if (len && value) os << value << endl;
720 else os << "(disabled)" << endl;
dburgess82c46ff2011-10-07 02:40:51 +0000721 src = sqlite3_run_query(mDB,stmt);
722 }
723 sqlite3_finalize(stmt);
724}
725
726
kurtis.heimerl5a872472013-05-31 21:47:25 +0000727ConfigurationRecordMap ConfigurationTable::getAllPairs() const
728{
729 ConfigurationRecordMap tmp;
730
731 // Prepare the statement.
732 string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG";
733 sqlite3_stmt *stmt;
734 if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return tmp;
735 // Read the result.
736 int src = sqlite3_run_query(mDB,stmt);
737 while (src==SQLITE_ROW) {
738 const char* key = (const char*)sqlite3_column_text(stmt,0);
739 const char* value = (const char*)sqlite3_column_text(stmt,1);
740 if (key && value) {
741 tmp[string(key)] = ConfigurationRecord(value);
742 } else if (key && !value) {
743 tmp[string(key)] = ConfigurationRecord(false);
744 }
745 src = sqlite3_run_query(mDB,stmt);
746 }
747 sqlite3_finalize(stmt);
748
749 return tmp;
750}
751
dburgess82c46ff2011-10-07 02:40:51 +0000752bool ConfigurationTable::set(const string& key, const string& value)
753{
754 assert(mDB);
755 ScopedLock lock(mLock);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000756 string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)";
dburgess82c46ff2011-10-07 02:40:51 +0000757 bool success = sqlite3_command(mDB,cmd.c_str());
758 // Cache the result.
759 if (success) mCache[key] = ConfigurationRecord(value);
760 return success;
761}
762
763bool ConfigurationTable::set(const string& key, long value)
764{
765 char buffer[30];
766 sprintf(buffer,"%ld",value);
767 return set(key,buffer);
768}
769
770
771bool ConfigurationTable::set(const string& key)
772{
773 assert(mDB);
774 ScopedLock lock(mLock);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000775 string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",NULL,1)";
dburgess82c46ff2011-10-07 02:40:51 +0000776 bool success = sqlite3_command(mDB,cmd.c_str());
777 if (success) mCache[key] = ConfigurationRecord(true);
778 return success;
779}
780
781
782void ConfigurationTable::checkCacheAge()
783{
784 // mLock is set by caller
785 static time_t timeOfLastPurge = 0;
786 time_t now = time(NULL);
787 // purge every 3 seconds
788 // purge period cannot be configuration parameter
789 if (now - timeOfLastPurge < 3) return;
790 timeOfLastPurge = now;
791 // this is purge() without the lock
792 ConfigurationMap::iterator mp = mCache.begin();
793 while (mp != mCache.end()) {
794 ConfigurationMap::iterator prev = mp;
795 mp++;
796 mCache.erase(prev);
797 }
798}
799
800
801void ConfigurationTable::purge()
802{
803 ScopedLock lock(mLock);
804 ConfigurationMap::iterator mp = mCache.begin();
805 while (mp != mCache.end()) {
806 ConfigurationMap::iterator prev = mp;
807 mp++;
808 mCache.erase(prev);
809 }
810}
811
812
813void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char const *,sqlite3_int64))
814{
815 assert(mDB);
816 sqlite3_update_hook(mDB,func,NULL);
817}
818
819
kurtis.heimerl5a872472013-05-31 21:47:25 +0000820void ConfigurationTable::setCrossCheckHook(vector<string> (*wCrossCheck)(const string&))
821{
822 mCrossCheck = wCrossCheck;
823}
824
825
826vector<string> ConfigurationTable::crossCheck(const string& key) {
827 vector<string> ret;
828
829 if (mCrossCheck != NULL) {
830 ret = mCrossCheck(key);
831 }
832
833 return ret;
834}
dburgess82c46ff2011-10-07 02:40:51 +0000835
836void HashString::computeHash()
837{
838 // FIXME -- Someone needs to review this hash function.
839 const char* cstr = c_str();
840 mHash = 0;
841 for (unsigned i=0; i<size(); i++) {
842 mHash = mHash ^ (mHash >> 32);
843 mHash = mHash*127 + cstr[i];
844 }
845}
846
847
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000848void SimpleKeyValue::addItem(const char* pair_orig)
849{
850 char *pair = strdup(pair_orig);
851 char *key = pair;
852 char *mark = strchr(pair,'=');
853 if (!mark) return;
854 *mark = '\0';
855 char *value = mark+1;
856 mMap[key] = value;
857 free(pair);
858}
859
860
861
862const char* SimpleKeyValue::get(const char* key) const
863{
864 HashStringMap::const_iterator p = mMap.find(key);
865 if (p==mMap.end()) return NULL;
866 return p->second.c_str();
867}
868
869
870void SimpleKeyValue::addItems(const char* pairs_orig)
871{
872 char *pairs = strdup(pairs_orig);
873 char *thisPair;
874 while ((thisPair=strsep(&pairs," "))!=NULL) {
875 addItem(thisPair);
876 }
877 free(pairs);
878}
879
880
kurtis.heimerl5a872472013-05-31 21:47:25 +0000881bool ConfigurationKey::isValidIP(const std::string& ip) {
882 struct sockaddr_in sa;
883 return inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr)) != 0;
884}
885
886
887void ConfigurationKey::getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping) {
888 uint delimiter;
889 int startPos;
890 uint endPos;
891
892 std::string tmp = key.getValidValues();
893 stepping = "1";
894
895 // grab steps if they're defined
896 startPos = tmp.find('(');
897 if (startPos != (int)std::string::npos) {
898 endPos = tmp.find(')');
899 stepping = tmp.substr(startPos+1, endPos-startPos-1);
900 tmp = tmp.substr(0, startPos);
901 }
902 startPos = 0;
903
904 delimiter = tmp.find(':', startPos);
905 min = tmp.substr(startPos, delimiter-startPos);
906 max = tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1);
907}
908
909
910template<class T> bool ConfigurationKey::isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger) {
911 bool ret = false;
912
913 T convVal;
914 T min;
915 T max;
916 T steps;
917 std::string strMin;
918 std::string strMax;
919 std::string strSteps;
920
921 std::stringstream(val) >> convVal;
922
923 ConfigurationKey::getMinMaxStepping(key, strMin, strMax, strSteps);
924 std::stringstream(strMin) >> min;
925 std::stringstream(strMax) >> max;
926 std::stringstream(strSteps) >> steps;
927
928 // TODO : only ranges checked, steps not enforced
929 if (isInteger) {
930 if (val.find('.') == std::string::npos && min <= convVal && convVal <= max) {
931 ret = true;
932 }
933 } else {
934 if (min <= convVal && convVal <= max) {
935 ret = true;
936 }
937 }
938
939 return ret;
940}
941
942const std::string ConfigurationKey::getARFCNsString() {
943 stringstream ss;
944 int i;
945 float downlink;
946 float uplink;
947
948 // 128:251 GSM850
949 downlink = 869.2;
950 uplink = 824.2;
951 for (i = 128; i <= 251; i++) {
952 ss << i << "|GSM850 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
953 downlink += 0.2;
954 uplink += 0.2;
955 }
956
957 // 1:124 PGSM900
958 downlink = 935.2;
959 uplink = 890.2;
960 for (i = 1; i <= 124; i++) {
961 ss << i << "|PGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
962 downlink += 0.2;
963 uplink += 0.2;
964 }
965
966 // 512:885 DCS1800
967 downlink = 1805.2;
968 uplink = 1710.2;
969 for (i = 512; i <= 885; i++) {
970 ss << i << "|DCS1800 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
971 downlink += 0.2;
972 uplink += 0.2;
973 }
974
975 // 512:810 PCS1900
976 downlink = 1930.2;
977 uplink = 1850.2;
978 for (i = 512; i <= 810; i++) {
979 ss << i << "|PCS1900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
980 downlink += 0.2;
981 uplink += 0.2;
982 }
983
984 ss << endl;
985
986 return ss.str();
987}
988
989const std::string ConfigurationKey::visibilityLevelToString(const ConfigurationKey::VisibilityLevel& visibility) {
990 std::string ret = "UNKNOWN ERROR";
991
992 switch (visibility) {
993 case ConfigurationKey::CUSTOMER:
994 ret = "customer - can be freely changed by the customer without any detriment to their system";
995 break;
996 case ConfigurationKey::CUSTOMERSITE:
997 ret = "customer site - these values are different for each BTS and should not be left default";
998 break;
999 case ConfigurationKey::CUSTOMERTUNE:
1000 ret = "customer tune - should only be changed to tune an installation to better suit the physical environment or MS usage pattern";
1001 break;
1002 case ConfigurationKey::CUSTOMERWARN:
1003 ret = "customer warn - a warning will be presented and confirmation required before changing this sensitive setting";
1004 break;
1005 case ConfigurationKey::DEVELOPER:
1006 ret = "developer - should only be changed by developers to debug/optimize the implementation";
1007 break;
1008 case ConfigurationKey::FACTORY:
1009 ret = "factory - set once at the factory, should never be changed";
1010 break;
1011 }
1012
1013 return ret;
1014}
1015
1016const std::string ConfigurationKey::typeToString(const ConfigurationKey::Type& type) {
1017 std::string ret = "UNKNOWN ERROR";
1018
1019 switch (type) {
1020 case BOOLEAN:
1021 ret = "boolean";
1022 break;
1023 case CHOICE_OPT:
1024 ret = "multiple choice (optional)";
1025 break;
1026 case CHOICE:
1027 ret = "multiple choice";
1028 break;
1029 case CIDR_OPT:
1030 ret = "CIDR notation (optional)";
1031 break;
1032 case CIDR:
1033 ret = "CIDR notation";
1034 break;
1035 case FILEPATH_OPT:
1036 ret = "file path (optional)";
1037 break;
1038 case FILEPATH:
1039 ret = "file path";
1040 break;
1041 case IPADDRESS_OPT:
1042 ret = "IP address (optional)";
1043 break;
1044 case IPADDRESS:
1045 ret = "IP address";
1046 break;
1047 case IPANDPORT:
1048 ret = "IP address and port";
1049 break;
1050 case MIPADDRESS_OPT:
1051 ret = "space-separated list of IP addresses (optional)";
1052 break;
1053 case MIPADDRESS:
1054 ret = "space-separated list of IP addresses";
1055 break;
1056 case PORT_OPT:
1057 ret = "IP port (optional)";
1058 break;
1059 case PORT:
1060 ret = "IP port";
1061 break;
1062 case REGEX_OPT:
1063 ret = "regular expression (optional)";
1064 break;
1065 case REGEX:
1066 ret = "regular expression";
1067 break;
1068 case STRING_OPT:
1069 ret = "string (optional)";
1070 break;
1071 case STRING:
1072 ret = "string";
1073 break;
1074 case VALRANGE:
1075 ret = "value range";
1076 break;
1077 }
1078
1079 return ret;
1080}
1081
1082void ConfigurationKey::printKey(const ConfigurationKey &key, const std::string& currentValue, ostream& os) {
1083 os << key.getName() << " ";
1084 if (!currentValue.length()) {
1085 os << "(disabled)";
1086 } else {
1087 os << currentValue;
1088 }
1089 if (currentValue.compare(key.getDefaultValue()) == 0) {
1090 os << " [default]";
1091 }
1092 os << endl;
1093}
1094
1095void ConfigurationKey::printDescription(const ConfigurationKey &key, ostream& os) {
1096 std::string tmp;
1097
1098 os << " - description: " << key.getDescription() << std::endl;
1099 if (key.getUnits().length()) {
1100 os << " - units: " << key.getUnits() << std::endl;
1101 }
1102 os << " - type: " << ConfigurationKey::typeToString(key.getType()) << std::endl;
1103 if (key.getDefaultValue().length()) {
1104 os << " - default value: " << key.getDefaultValue() << std::endl;
1105 }
1106 os << " - visibility level: " << ConfigurationKey::visibilityLevelToString(key.getVisibility()) << std::endl;
1107 os << " - static: " << key.isStatic() << std::endl;
1108
1109 tmp = key.getValidValues();
1110 if (key.getType() == ConfigurationKey::VALRANGE) {
1111 int startPos = tmp.find('(');
1112 uint delimiter = 0;
1113 if (startPos != (int)std::string::npos) {
1114 tmp = tmp.substr(0, startPos);
1115 }
1116 startPos = -1;
1117
1118 do {
1119 startPos++;
1120 delimiter = tmp.find(':', startPos);
1121 os << " - valid values: " << "from " << tmp.substr(startPos, delimiter-startPos) << " to "
1122 << tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1) << std::endl;
1123
1124 } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
1125
1126 } else if (key.getType() == ConfigurationKey::CHOICE) {
1127 int startPos = -1;
1128 uint endPos = 0;
1129
1130 do {
1131 startPos++;
1132 if ((endPos = tmp.find('|', startPos)) != std::string::npos) {
1133 os << " - valid values: " << tmp.substr(startPos, endPos-startPos);
1134 os << " = " << tmp.substr(endPos+1, tmp.find(',', endPos)-endPos-1) << std::endl;
1135 } else {
1136 os << " - valid values: " << tmp.substr(startPos, tmp.find(',', startPos)-startPos) << std::endl;
1137 }
1138
1139 } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
1140
1141 } else if (key.getType() == ConfigurationKey::BOOLEAN) {
1142 os << " - valid values: 0 = disabled" << std::endl;
1143 os << " - valid values: 1 = enabled" << std::endl;
1144
1145 } else if (key.getType() == ConfigurationKey::STRING) {
1146 os << " - valid val regex: " << tmp << std::endl;
1147
1148 } else if (key.getValidValues().length()) {
1149 os << " - raw valid values: " << tmp << std::endl;
1150 }
1151}
1152
dburgess82c46ff2011-10-07 02:40:51 +00001153
1154// vim: ts=4 sw=4