blob: 14e5400566db0c59024df2128cb28199d4baed4d [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
Alexander Chemerisf84232d2015-07-30 19:59:56 -040038#define debugLogEarly(x,y,z)
kurtis.heimerl5a872472013-05-31 21:47:25 +000039#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
Alexander Chemeris4793f462017-03-17 18:35:48 -070056static std::string replaceAll(const std::string input, const std::string search, const std::string replace)
57{
58 std::string output = input;
Tom Tsou15da7e12017-04-03 18:54:02 -070059 size_t index = 0;
Alexander Chemeris4793f462017-03-17 18:35:48 -070060
61 while (true) {
62 index = output.find(search, index);
63 if (index == std::string::npos) {
64 break;
65 }
66
67 output.replace(index, replace.length(), replace);
68 index += replace.length();
69 }
70
71 return output;
72}
dburgess82c46ff2011-10-07 02:40:51 +000073
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000074
75float ConfigurationRecord::floatNumber() const
dburgess82c46ff2011-10-07 02:40:51 +000076{
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000077 float val;
78 sscanf(mValue.c_str(),"%f",&val);
79 return val;
80}
81
82
kurtis.heimerl5a872472013-05-31 21:47:25 +000083ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName, ConfigurationKeyMap wSchema)
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000084{
kurtis.heimerl00913d72012-12-16 06:08:18 +000085 gLogEarly(LOG_INFO, "opening configuration table from path %s", filename);
dburgess82c46ff2011-10-07 02:40:51 +000086 // Connect to the database.
87 int rc = sqlite3_open(filename,&mDB);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000088 // (pat) When I used malloc here, sqlite3 sporadically crashes.
89 if (wCmdName) {
90 strncpy(gCmdName,wCmdName,18);
91 gCmdName[18] = 0;
92 strcat(gCmdName,":");
93 }
dburgess82c46ff2011-10-07 02:40:51 +000094 if (rc) {
kurtis.heimerl00913d72012-12-16 06:08:18 +000095 gLogEarly(LOG_EMERG, "cannot open configuration database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
dburgess82c46ff2011-10-07 02:40:51 +000096 sqlite3_close(mDB);
97 mDB = NULL;
98 return;
99 }
100 // Create the table, if needed.
101 if (!sqlite3_command(mDB,createConfigTable)) {
kurtis.heimerl00913d72012-12-16 06:08:18 +0000102 gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
dburgess82c46ff2011-10-07 02:40:51 +0000103 }
kurtis.heimerl5a872472013-05-31 21:47:25 +0000104
105 // Build CommonLibs schema
106 ConfigurationKey *tmp;
107 tmp = new ConfigurationKey("Log.Alarms.Max","20",
108 "alarms",
109 ConfigurationKey::CUSTOMER,
110 ConfigurationKey::VALRANGE,
111 "10:20",// educated guess
112 false,
113 "Maximum number of alarms to remember inside the application."
114 );
115 mSchema[tmp->getName()] = *tmp;
Alexander Chemeris73dbccd2017-03-23 18:51:17 -0700116 delete tmp;
kurtis.heimerl5a872472013-05-31 21:47:25 +0000117
118 tmp = new ConfigurationKey("Log.File","",
119 "",
120 ConfigurationKey::DEVELOPER,
121 ConfigurationKey::FILEPATH_OPT,// audited
122 "",
123 false,
124 "Path to use for textfile based logging. "
125 "By default, this feature is disabled. "
126 "To enable, specify an absolute path to the file you wish to use, eg: /tmp/my-debug.log. "
127 "To disable again, execute \"unconfig Log.File\"."
128 );
129 mSchema[tmp->getName()] = *tmp;
Alexander Chemeris73dbccd2017-03-23 18:51:17 -0700130 delete tmp;
kurtis.heimerl5a872472013-05-31 21:47:25 +0000131
132 tmp = new ConfigurationKey("Log.Level","NOTICE",
133 "",
134 ConfigurationKey::CUSTOMER,
135 ConfigurationKey::CHOICE,
136 "EMERG|EMERGENCY - report serious faults associated with service failure or hardware damage,"
137 "ALERT|ALERT - report likely service disruption caused by misconfiguration or poor connectivity,"
138 "CRIT|CRITICAL - report anomalous events that are likely to degrade service,"
139 "ERR|ERROR - report internal errors of the software that may result in degradation of service in unusual circumstances,"
140 "WARNING|WARNING - report anomalous events that may indicate a degradation of normal service,"
141 "NOTICE|NOTICE - report anomalous events that probably do not affect service but may be of interest to network operators,"
142 "INFO|INFORMATION - report normal events,"
143 "DEBUG|DEBUG - only for use by developers and will degrade system performance",
144 false,
145 "Default logging level when no other level is defined for a file."
146 );
147 mSchema[tmp->getName()] = *tmp;
Alexander Chemeris73dbccd2017-03-23 18:51:17 -0700148 delete tmp;
kurtis.heimerl5a872472013-05-31 21:47:25 +0000149
150 // Add application specific schema
151 mSchema.insert(wSchema.begin(), wSchema.end());
152
153 // Init the cross checking callback to something predictable
154 mCrossCheck = NULL;
dburgess82c46ff2011-10-07 02:40:51 +0000155}
156
kurtis.heimerl5a872472013-05-31 21:47:25 +0000157string ConfigurationTable::getDefaultSQL(const std::string& program, const std::string& version)
158{
159 stringstream ss;
160 ConfigurationKeyMap::iterator mp;
dburgess82c46ff2011-10-07 02:40:51 +0000161
kurtis.heimerl5a872472013-05-31 21:47:25 +0000162 ss << "--" << endl;
163 ss << "-- This file was generated using: " << program << " --gensql" << endl;
164 ss << "-- binary version: " << version << endl;
165 ss << "--" << endl;
166 ss << "-- Future changes should not be put in this file directly but" << endl;
167 ss << "-- rather in the program's ConfigurationKey schema." << endl;
168 ss << "--" << endl;
169 ss << "PRAGMA foreign_keys=OFF;" << endl;
170 ss << "BEGIN TRANSACTION;" << endl;
171 ss << "CREATE TABLE CONFIG ( KEYSTRING TEXT UNIQUE NOT NULL, VALUESTRING TEXT, STATIC INTEGER DEFAULT 0, OPTIONAL INTEGER DEFAULT 0, COMMENTS TEXT DEFAULT '');" << endl;
172
173 mp = mSchema.begin();
174 while (mp != mSchema.end()) {
175 ss << "INSERT INTO \"CONFIG\" VALUES(";
176 // name
177 ss << "'" << mp->first << "',";
178 // default
179 ss << "'" << mp->second.getDefaultValue() << "',";
180 // static
181 if (mp->second.isStatic()) {
182 ss << "1";
183 } else {
184 ss << "0";
185 }
186 ss << ",";
187 // optional
188 ss << "0,";
189 // description
190 ss << "'";
191 if (mp->second.getType() == ConfigurationKey::BOOLEAN) {
192 ss << "1=enabled, 0=disabled - ";
193 }
194 ss << mp->second.getDescription();
195 if (mp->second.isStatic()) {
196 ss << " Static.";
197 }
198 ss << "'";
199 ss << ");" << endl;
200 mp++;
201 }
202
203 ss << "COMMIT;" << endl;
204 ss << endl;
205
206 return ss.str();
207}
208
209string ConfigurationTable::getTeX(const std::string& program, const std::string& version)
210{
211 stringstream ss;
212 ConfigurationKeyMap::iterator mp;
213
214 ss << "% START AUTO-GENERATED CONTENT" << endl;
215 ss << "% -- these sections were generated using: " << program << " --gentex" << endl;
216 ss << "% -- binary version: " << version << endl;
217
218 ss << "\\subsection{Customer Site Parameters}" << endl;
219 ss << "These parameters must be changed to fit your site." << endl;
220 ss << "\\begin{itemize}" << endl;
221 mp = mSchema.begin();
222 while (mp != mSchema.end()) {
223 if (mp->second.getVisibility() == ConfigurationKey::CUSTOMERSITE) {
224 ss << " \\item ";
225 // name
226 ss << mp->first << " -- ";
227 // description
228 ss << mp->second.getDescription();
229 ss << endl;
230 }
231 mp++;
232 }
233 ss << "\\end{itemize}" << endl;
234 ss << endl;
235
236 ss << "\\subsection{Customer Tuneable Parameters}" << endl;
237 ss << "These parameters can be changed to optimize your site." << endl;
238 ss << "\\begin{itemize}" << endl;
239 mp = mSchema.begin();
240 while (mp != mSchema.end()) {
241 if (mp->second.getVisibility() != ConfigurationKey::CUSTOMERSITE &&
242 (
243 mp->second.getVisibility() == ConfigurationKey::CUSTOMER ||
244 mp->second.getVisibility() == ConfigurationKey::CUSTOMERTUNE ||
245 mp->second.getVisibility() == ConfigurationKey::CUSTOMERWARN
246 )) {
247 ss << " \\item ";
248 // name
249 ss << mp->first << " -- ";
250 // description
251 ss << mp->second.getDescription();
252 ss << endl;
253 }
254 mp++;
255 }
256 ss << "\\end{itemize}" << endl;
257 ss << endl;
258
259 ss << "\\subsection{Developer/Factory Parameters}" << endl;
260 ss << "These parameters should only be changed by when developing new code." << endl;
261 ss << "\\begin{itemize}" << endl;
262 mp = mSchema.begin();
263 while (mp != mSchema.end()) {
264 if (mp->second.getVisibility() == ConfigurationKey::FACTORY ||
265 mp->second.getVisibility() == ConfigurationKey::DEVELOPER) {
266 ss << " \\item ";
267 // name
268 ss << mp->first << " -- ";
269 // description
270 ss << mp->second.getDescription();
271 ss << endl;
272 }
273 mp++;
274 }
275 ss << "\\end{itemize}" << endl;
276 ss << "% END AUTO-GENERATED CONTENT" << endl;
277 ss << endl;
278
Alexander Chemeris4793f462017-03-17 18:35:48 -0700279 string tmp = replaceAll(ss.str(), "^", "\\^");
280 return replaceAll(tmp, "_", "\\_");
kurtis.heimerl5a872472013-05-31 21:47:25 +0000281}
dburgess82c46ff2011-10-07 02:40:51 +0000282
283bool ConfigurationTable::defines(const string& key)
284{
kurtis.heimerl5a872472013-05-31 21:47:25 +0000285 try {
286 ScopedLock lock(mLock);
287 return lookup(key).defined();
288 } catch (ConfigurationTableKeyNotFound) {
289 debugLogEarly(LOG_ALERT, "configuration parameter %s not found", key.c_str());
290 return false;
dburgess82c46ff2011-10-07 02:40:51 +0000291 }
dburgess82c46ff2011-10-07 02:40:51 +0000292}
293
kurtis.heimerl5a872472013-05-31 21:47:25 +0000294bool ConfigurationTable::keyDefinedInSchema(const std::string& name)
295{
296 return mSchema.find(name) == mSchema.end() ? false : true;
297}
298
299bool ConfigurationTable::isValidValue(const std::string& name, const std::string& val) {
300 bool ret = false;
301
302 ConfigurationKey key = mSchema[name];
303
304 switch (key.getType()) {
305 case ConfigurationKey::BOOLEAN: {
306 if (val == "1" || val == "0") {
307 ret = true;
308 }
309 break;
310 }
311
312 case ConfigurationKey::CHOICE_OPT: {
313 if (val.length() == 0) {
314 ret = true;
315 break;
316 }
317 }
318 case ConfigurationKey::CHOICE: {
319 int startPos = -1;
320 uint endPos = 0;
321
322 std::string tmp = key.getValidValues();
323
324 do {
325 startPos++;
326 if ((endPos = tmp.find('|', startPos)) != std::string::npos) {
327 if (val == tmp.substr(startPos, endPos-startPos)) {
328 ret = true;
329 break;
330 }
331 } else {
332 if (val == tmp.substr(startPos, tmp.find(',', startPos)-startPos)) {
333 ret = true;
334 break;
335 }
336 }
337
338 } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
339 break;
340 }
341
342 case ConfigurationKey::CIDR_OPT: {
343 if (val.length() == 0) {
344 ret = true;
345 break;
346 }
347 }
348 case ConfigurationKey::CIDR: {
349 uint delimiter;
350 std::string ip;
351 int cidr = -1;
352
353 delimiter = val.find('/');
354 if (delimiter != std::string::npos) {
355 ip = val.substr(0, delimiter);
356 std::stringstream(val.substr(delimiter+1)) >> cidr;
357 if (ConfigurationKey::isValidIP(ip) && 0 <= cidr && cidr <= 32) {
358 ret = true;
359 }
360 }
361 break;
362 }
363
364 case ConfigurationKey::FILEPATH_OPT: {
365 if (val.length() == 0) {
366 ret = true;
367 break;
368 }
369 }
370 case ConfigurationKey::FILEPATH: {
371 regex_t r;
372 const char* expression = "^[a-zA-Z0-9/_.-]+$";
373 int result = regcomp(&r, expression, REG_EXTENDED);
374 if (result) {
375 char msg[256];
376 regerror(result,&r,msg,255);
377 break;//abort();
378 }
379 if (regexec(&r, val.c_str(), 0, NULL, 0)==0) {
380 ret = true;
381 }
382 regfree(&r);
383 break;
384 }
385
386 case ConfigurationKey::IPADDRESS_OPT: {
387 if (val.length() == 0) {
388 ret = true;
389 break;
390 }
391 }
392 case ConfigurationKey::IPADDRESS: {
393 ret = ConfigurationKey::isValidIP(val);
394 break;
395 }
396
397 case ConfigurationKey::IPANDPORT: {
398 uint delimiter;
399 std::string ip;
400 int port = -1;
401
402 delimiter = val.find(':');
403 if (delimiter != std::string::npos) {
404 ip = val.substr(0, delimiter);
405 std::stringstream(val.substr(delimiter+1)) >> port;
406 if (ConfigurationKey::isValidIP(ip) && 1 <= port && port <= 65535) {
407 ret = true;
408 }
409 }
410 break;
411 }
412
413 case ConfigurationKey::MIPADDRESS_OPT: {
414 if (val.length() == 0) {
415 ret = true;
416 break;
417 }
418 }
419 case ConfigurationKey::MIPADDRESS: {
420 int startPos = -1;
421 uint endPos = 0;
422
423 do {
424 startPos++;
425 endPos = val.find(' ', startPos);
426 if (ConfigurationKey::isValidIP(val.substr(startPos, endPos-startPos))) {
427 ret = true;
428 } else {
429 ret = false;
430 break;
431 }
432
433 } while ((startPos = endPos) != (int)std::string::npos);
434 break;
435 }
436
437 case ConfigurationKey::PORT_OPT: {
438 if (val.length() == 0) {
439 ret = true;
440 break;
441 }
442 }
443 case ConfigurationKey::PORT: {
444 int intVal;
445
446 std::stringstream(val) >> intVal;
447
448 if (1 <= intVal && intVal <= 65535) {
449 ret = true;
450 }
451 break;
452 }
453
454 case ConfigurationKey::REGEX_OPT: {
455 if (val.length() == 0) {
456 ret = true;
457 break;
458 }
459 }
460 case ConfigurationKey::REGEX: {
461 regex_t r;
462 const char* expression = val.c_str();
463 int result = regcomp(&r, expression, REG_EXTENDED);
464 if (result) {
465 char msg[256];
466 regerror(result,&r,msg,255);
467 } else {
468 ret = true;
469 }
470 regfree(&r);
471 break;
472 }
473
474 case ConfigurationKey::STRING_OPT: {
475 if (val.length() == 0) {
476 ret = true;
477 break;
478 }
479 }
480 case ConfigurationKey::STRING: {
481 regex_t r;
482 const char* expression = key.getValidValues().c_str();
483 int result = regcomp(&r, expression, REG_EXTENDED);
484 if (result) {
485 char msg[256];
486 regerror(result,&r,msg,255);
487 break;//abort();
488 }
489 if (regexec(&r, val.c_str(), 0, NULL, 0)==0) {
490 ret = true;
491 }
492 regfree(&r);
493 break;
494 }
495
496 case ConfigurationKey::VALRANGE: {
497 regex_t r;
498 int result;
499 if (key.getValidValues().find('.') != std::string::npos) {
500 result = regcomp(&r, "^[0-9.-]+$", REG_EXTENDED);
501 } else {
502 result = regcomp(&r, "^[0-9-]+$", REG_EXTENDED);
503 }
504 if (result) {
505 char msg[256];
506 regerror(result,&r,msg,255);
507 break;//abort();
508 }
509 if (regexec(&r, val.c_str(), 0, NULL, 0)!=0) {
510 ret = false;
511 } else if (key.getValidValues().find('.') != std::string::npos) {
512 ret = ConfigurationKey::isInValRange<float>(key, val, false);
513 } else {
514 ret = ConfigurationKey::isInValRange<int>(key, val, true);
515 }
516
517 regfree(&r);
518 break;
519 }
520 }
521
522 return ret;
523}
524
525ConfigurationKeyMap ConfigurationTable::getSimilarKeys(const std::string& snippet) {
526 ConfigurationKeyMap tmp;
527
528 ConfigurationKeyMap::const_iterator mp = mSchema.begin();
529 while (mp != mSchema.end()) {
530 if (mp->first.find(snippet) != std::string::npos) {
531 tmp[mp->first] = mp->second;
532 }
533 mp++;
534 }
535
536 return tmp;
537}
dburgess82c46ff2011-10-07 02:40:51 +0000538
539const ConfigurationRecord& ConfigurationTable::lookup(const string& key)
540{
541 assert(mDB);
542 checkCacheAge();
543 // We assume the caller holds mLock.
544 // So it is OK to return a reference into the cache.
545
546 // Check the cache.
547 // This is cheap.
548 ConfigurationMap::const_iterator where = mCache.find(key);
549 if (where!=mCache.end()) {
550 if (where->second.defined()) return where->second;
dburgess82c46ff2011-10-07 02:40:51 +0000551 throw ConfigurationTableKeyNotFound(key);
552 }
553
554 // Check the database.
555 // This is more expensive.
556 char *value = NULL;
557 sqlite3_single_lookup(mDB,"CONFIG",
558 "KEYSTRING",key.c_str(),"VALUESTRING",value);
559
kurtis.heimerl5a872472013-05-31 21:47:25 +0000560 // value found, cache the result
561 if (value) {
562 mCache[key] = ConfigurationRecord(value);
563 // key definition found, cache the default
564 } else if (keyDefinedInSchema(key)) {
565 mCache[key] = ConfigurationRecord(mSchema[key].getDefaultValue());
566 // total miss, cache the error
567 } else {
dburgess82c46ff2011-10-07 02:40:51 +0000568 mCache[key] = ConfigurationRecord(false);
dburgess82c46ff2011-10-07 02:40:51 +0000569 throw ConfigurationTableKeyNotFound(key);
570 }
571
dburgess82c46ff2011-10-07 02:40:51 +0000572 free(value);
573
574 // Leave mLock locked. The caller holds it still.
575 return mCache[key];
576}
577
578
579
kurtis.heimerl5a872472013-05-31 21:47:25 +0000580bool ConfigurationTable::isStatic(const string& key)
dburgess82c46ff2011-10-07 02:40:51 +0000581{
kurtis.heimerl5a872472013-05-31 21:47:25 +0000582 if (keyDefinedInSchema(key)) {
583 return mSchema[key].isStatic();
584 } else {
585 return false;
586 }
dburgess82c46ff2011-10-07 02:40:51 +0000587}
588
589
590
591
592string ConfigurationTable::getStr(const string& key)
593{
594 // We need the lock because rec is a reference into the cache.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000595 try {
596 ScopedLock lock(mLock);
597 return lookup(key).value();
598 } catch (ConfigurationTableKeyNotFound) {
599 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000600 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000601 throw ConfigurationTableKeyNotFound(key);
602 }
dburgess82c46ff2011-10-07 02:40:51 +0000603}
604
dburgess82c46ff2011-10-07 02:40:51 +0000605
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000606bool ConfigurationTable::getBool(const string& key)
607{
608 try {
609 return getNum(key) != 0;
610 } catch (ConfigurationTableKeyNotFound) {
kurtis.heimerl5a872472013-05-31 21:47:25 +0000611 // Raise an alert and re-throw the exception.
612 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
613 throw ConfigurationTableKeyNotFound(key);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000614 }
615}
616
617
dburgess82c46ff2011-10-07 02:40:51 +0000618long ConfigurationTable::getNum(const string& key)
619{
620 // We need the lock because rec is a reference into the cache.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000621 try {
622 ScopedLock lock(mLock);
623 return lookup(key).number();
624 } catch (ConfigurationTableKeyNotFound) {
625 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000626 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000627 throw ConfigurationTableKeyNotFound(key);
628 }
dburgess82c46ff2011-10-07 02:40:51 +0000629}
630
631
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000632float ConfigurationTable::getFloat(const string& key)
633{
kurtis.heimerl5a872472013-05-31 21:47:25 +0000634 try {
635 ScopedLock lock(mLock);
636 return lookup(key).floatNumber();
637 } catch (ConfigurationTableKeyNotFound) {
638 // Raise an alert and re-throw the exception.
639 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
640 throw ConfigurationTableKeyNotFound(key);
641 }
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000642}
643
644std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key)
645{
646 // Look up the string.
647 char *line=NULL;
648 try {
649 ScopedLock lock(mLock);
650 const ConfigurationRecord& rec = lookup(key);
651 line = strdup(rec.value().c_str());
652 } catch (ConfigurationTableKeyNotFound) {
653 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000654 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000655 throw ConfigurationTableKeyNotFound(key);
656 }
657
658 assert(line);
659 char *lp = line;
660
661 // Parse the string.
662 std::vector<string> retVal;
663 while (lp) {
664 while (*lp==' ') lp++;
665 if (*lp == '\0') break;
666 char *tp = strsep(&lp," ");
667 if (!tp) break;
668 retVal.push_back(tp);
669 }
670 free(line);
671 return retVal;
672}
673
674
dburgess82c46ff2011-10-07 02:40:51 +0000675std::vector<unsigned> ConfigurationTable::getVector(const string& key)
676{
677 // Look up the string.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000678 char *line=NULL;
679 try {
680 ScopedLock lock(mLock);
681 const ConfigurationRecord& rec = lookup(key);
682 line = strdup(rec.value().c_str());
683 } catch (ConfigurationTableKeyNotFound) {
684 // Raise an alert and re-throw the exception.
kurtis.heimerl5a872472013-05-31 21:47:25 +0000685 debugLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000686 throw ConfigurationTableKeyNotFound(key);
687 }
688
689 assert(line);
690 char *lp = line;
691
dburgess82c46ff2011-10-07 02:40:51 +0000692 // Parse the string.
693 std::vector<unsigned> retVal;
dburgess82c46ff2011-10-07 02:40:51 +0000694 while (lp) {
695 // Watch for multiple or trailing spaces.
696 while (*lp==' ') lp++;
697 if (*lp=='\0') break;
698 retVal.push_back(strtol(lp,NULL,0));
699 strsep(&lp," ");
700 }
701 free(line);
702 return retVal;
703}
704
705
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000706bool ConfigurationTable::remove(const string& key)
707{
708 assert(mDB);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000709
710 ScopedLock lock(mLock);
711 // Clear the cache entry and the database.
712 ConfigurationMap::iterator where = mCache.find(key);
713 if (where!=mCache.end()) mCache.erase(where);
714 // Really remove it.
715 string cmd = "DELETE FROM CONFIG WHERE KEYSTRING=='"+key+"'";
716 return sqlite3_command(mDB,cmd.c_str());
717}
718
719
dburgess82c46ff2011-10-07 02:40:51 +0000720
721void ConfigurationTable::find(const string& pat, ostream& os) const
722{
723 // Prepare the statement.
724 string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG WHERE KEYSTRING LIKE \"%" + pat + "%\"";
725 sqlite3_stmt *stmt;
726 if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return;
727 // Read the result.
728 int src = sqlite3_run_query(mDB,stmt);
729 while (src==SQLITE_ROW) {
730 const char* value = (const char*)sqlite3_column_text(stmt,1);
731 os << sqlite3_column_text(stmt,0) << " ";
kurtis.heimerl5a872472013-05-31 21:47:25 +0000732 int len = 0;
733 if (value) {
734 len = strlen(value);
735 }
736 if (len && value) os << value << endl;
737 else os << "(disabled)" << endl;
dburgess82c46ff2011-10-07 02:40:51 +0000738 src = sqlite3_run_query(mDB,stmt);
739 }
740 sqlite3_finalize(stmt);
741}
742
743
kurtis.heimerl5a872472013-05-31 21:47:25 +0000744ConfigurationRecordMap ConfigurationTable::getAllPairs() const
745{
746 ConfigurationRecordMap tmp;
747
748 // Prepare the statement.
749 string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG";
750 sqlite3_stmt *stmt;
751 if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return tmp;
752 // Read the result.
753 int src = sqlite3_run_query(mDB,stmt);
754 while (src==SQLITE_ROW) {
755 const char* key = (const char*)sqlite3_column_text(stmt,0);
756 const char* value = (const char*)sqlite3_column_text(stmt,1);
757 if (key && value) {
758 tmp[string(key)] = ConfigurationRecord(value);
759 } else if (key && !value) {
760 tmp[string(key)] = ConfigurationRecord(false);
761 }
762 src = sqlite3_run_query(mDB,stmt);
763 }
764 sqlite3_finalize(stmt);
765
766 return tmp;
767}
768
dburgess82c46ff2011-10-07 02:40:51 +0000769bool ConfigurationTable::set(const string& key, const string& value)
770{
771 assert(mDB);
772 ScopedLock lock(mLock);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000773 string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)";
dburgess82c46ff2011-10-07 02:40:51 +0000774 bool success = sqlite3_command(mDB,cmd.c_str());
775 // Cache the result.
776 if (success) mCache[key] = ConfigurationRecord(value);
777 return success;
778}
779
780bool ConfigurationTable::set(const string& key, long value)
781{
782 char buffer[30];
783 sprintf(buffer,"%ld",value);
784 return set(key,buffer);
785}
786
787
788bool ConfigurationTable::set(const string& key)
789{
790 assert(mDB);
791 ScopedLock lock(mLock);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000792 string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",NULL,1)";
dburgess82c46ff2011-10-07 02:40:51 +0000793 bool success = sqlite3_command(mDB,cmd.c_str());
794 if (success) mCache[key] = ConfigurationRecord(true);
795 return success;
796}
797
798
799void ConfigurationTable::checkCacheAge()
800{
801 // mLock is set by caller
802 static time_t timeOfLastPurge = 0;
803 time_t now = time(NULL);
804 // purge every 3 seconds
805 // purge period cannot be configuration parameter
806 if (now - timeOfLastPurge < 3) return;
807 timeOfLastPurge = now;
808 // this is purge() without the lock
809 ConfigurationMap::iterator mp = mCache.begin();
810 while (mp != mCache.end()) {
811 ConfigurationMap::iterator prev = mp;
812 mp++;
813 mCache.erase(prev);
814 }
815}
816
817
818void ConfigurationTable::purge()
819{
820 ScopedLock lock(mLock);
821 ConfigurationMap::iterator mp = mCache.begin();
822 while (mp != mCache.end()) {
823 ConfigurationMap::iterator prev = mp;
824 mp++;
825 mCache.erase(prev);
826 }
827}
828
829
830void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char const *,sqlite3_int64))
831{
832 assert(mDB);
833 sqlite3_update_hook(mDB,func,NULL);
834}
835
836
kurtis.heimerl5a872472013-05-31 21:47:25 +0000837void ConfigurationTable::setCrossCheckHook(vector<string> (*wCrossCheck)(const string&))
838{
839 mCrossCheck = wCrossCheck;
840}
841
842
843vector<string> ConfigurationTable::crossCheck(const string& key) {
844 vector<string> ret;
845
846 if (mCrossCheck != NULL) {
847 ret = mCrossCheck(key);
848 }
849
850 return ret;
851}
dburgess82c46ff2011-10-07 02:40:51 +0000852
853void HashString::computeHash()
854{
855 // FIXME -- Someone needs to review this hash function.
856 const char* cstr = c_str();
857 mHash = 0;
858 for (unsigned i=0; i<size(); i++) {
859 mHash = mHash ^ (mHash >> 32);
860 mHash = mHash*127 + cstr[i];
861 }
862}
863
864
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000865void SimpleKeyValue::addItem(const char* pair_orig)
866{
867 char *pair = strdup(pair_orig);
868 char *key = pair;
869 char *mark = strchr(pair,'=');
870 if (!mark) return;
871 *mark = '\0';
872 char *value = mark+1;
873 mMap[key] = value;
874 free(pair);
875}
876
877
878
879const char* SimpleKeyValue::get(const char* key) const
880{
881 HashStringMap::const_iterator p = mMap.find(key);
882 if (p==mMap.end()) return NULL;
883 return p->second.c_str();
884}
885
886
887void SimpleKeyValue::addItems(const char* pairs_orig)
888{
889 char *pairs = strdup(pairs_orig);
890 char *thisPair;
891 while ((thisPair=strsep(&pairs," "))!=NULL) {
892 addItem(thisPair);
893 }
894 free(pairs);
895}
896
897
kurtis.heimerl5a872472013-05-31 21:47:25 +0000898bool ConfigurationKey::isValidIP(const std::string& ip) {
899 struct sockaddr_in sa;
900 return inet_pton(AF_INET, ip.c_str(), &(sa.sin_addr)) != 0;
901}
902
903
904void ConfigurationKey::getMinMaxStepping(const ConfigurationKey &key, std::string &min, std::string &max, std::string &stepping) {
905 uint delimiter;
906 int startPos;
907 uint endPos;
908
909 std::string tmp = key.getValidValues();
910 stepping = "1";
911
912 // grab steps if they're defined
913 startPos = tmp.find('(');
914 if (startPos != (int)std::string::npos) {
915 endPos = tmp.find(')');
916 stepping = tmp.substr(startPos+1, endPos-startPos-1);
917 tmp = tmp.substr(0, startPos);
918 }
919 startPos = 0;
920
921 delimiter = tmp.find(':', startPos);
922 min = tmp.substr(startPos, delimiter-startPos);
923 max = tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1);
924}
925
926
927template<class T> bool ConfigurationKey::isInValRange(const ConfigurationKey &key, const std::string& val, const bool isInteger) {
928 bool ret = false;
929
930 T convVal;
931 T min;
932 T max;
933 T steps;
934 std::string strMin;
935 std::string strMax;
936 std::string strSteps;
937
938 std::stringstream(val) >> convVal;
939
940 ConfigurationKey::getMinMaxStepping(key, strMin, strMax, strSteps);
941 std::stringstream(strMin) >> min;
942 std::stringstream(strMax) >> max;
943 std::stringstream(strSteps) >> steps;
944
945 // TODO : only ranges checked, steps not enforced
946 if (isInteger) {
947 if (val.find('.') == std::string::npos && min <= convVal && convVal <= max) {
948 ret = true;
949 }
950 } else {
951 if (min <= convVal && convVal <= max) {
952 ret = true;
953 }
954 }
955
956 return ret;
957}
958
959const std::string ConfigurationKey::getARFCNsString() {
960 stringstream ss;
961 int i;
962 float downlink;
963 float uplink;
964
965 // 128:251 GSM850
966 downlink = 869.2;
967 uplink = 824.2;
968 for (i = 128; i <= 251; i++) {
969 ss << i << "|GSM850 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
970 downlink += 0.2;
971 uplink += 0.2;
972 }
973
974 // 1:124 PGSM900
975 downlink = 935.2;
976 uplink = 890.2;
977 for (i = 1; i <= 124; i++) {
978 ss << i << "|PGSM900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
979 downlink += 0.2;
980 uplink += 0.2;
981 }
982
983 // 512:885 DCS1800
984 downlink = 1805.2;
985 uplink = 1710.2;
986 for (i = 512; i <= 885; i++) {
987 ss << i << "|DCS1800 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
988 downlink += 0.2;
989 uplink += 0.2;
990 }
991
992 // 512:810 PCS1900
993 downlink = 1930.2;
994 uplink = 1850.2;
995 for (i = 512; i <= 810; i++) {
996 ss << i << "|PCS1900 #" << i << " : " << downlink << " MHz downlink / " << uplink << " MHz uplink,";
997 downlink += 0.2;
998 uplink += 0.2;
999 }
1000
1001 ss << endl;
1002
1003 return ss.str();
1004}
1005
1006const std::string ConfigurationKey::visibilityLevelToString(const ConfigurationKey::VisibilityLevel& visibility) {
1007 std::string ret = "UNKNOWN ERROR";
1008
1009 switch (visibility) {
1010 case ConfigurationKey::CUSTOMER:
1011 ret = "customer - can be freely changed by the customer without any detriment to their system";
1012 break;
1013 case ConfigurationKey::CUSTOMERSITE:
1014 ret = "customer site - these values are different for each BTS and should not be left default";
1015 break;
1016 case ConfigurationKey::CUSTOMERTUNE:
1017 ret = "customer tune - should only be changed to tune an installation to better suit the physical environment or MS usage pattern";
1018 break;
1019 case ConfigurationKey::CUSTOMERWARN:
1020 ret = "customer warn - a warning will be presented and confirmation required before changing this sensitive setting";
1021 break;
1022 case ConfigurationKey::DEVELOPER:
1023 ret = "developer - should only be changed by developers to debug/optimize the implementation";
1024 break;
1025 case ConfigurationKey::FACTORY:
1026 ret = "factory - set once at the factory, should never be changed";
1027 break;
1028 }
1029
1030 return ret;
1031}
1032
1033const std::string ConfigurationKey::typeToString(const ConfigurationKey::Type& type) {
1034 std::string ret = "UNKNOWN ERROR";
1035
1036 switch (type) {
1037 case BOOLEAN:
1038 ret = "boolean";
1039 break;
1040 case CHOICE_OPT:
1041 ret = "multiple choice (optional)";
1042 break;
1043 case CHOICE:
1044 ret = "multiple choice";
1045 break;
1046 case CIDR_OPT:
1047 ret = "CIDR notation (optional)";
1048 break;
1049 case CIDR:
1050 ret = "CIDR notation";
1051 break;
1052 case FILEPATH_OPT:
1053 ret = "file path (optional)";
1054 break;
1055 case FILEPATH:
1056 ret = "file path";
1057 break;
1058 case IPADDRESS_OPT:
1059 ret = "IP address (optional)";
1060 break;
1061 case IPADDRESS:
1062 ret = "IP address";
1063 break;
1064 case IPANDPORT:
1065 ret = "IP address and port";
1066 break;
1067 case MIPADDRESS_OPT:
1068 ret = "space-separated list of IP addresses (optional)";
1069 break;
1070 case MIPADDRESS:
1071 ret = "space-separated list of IP addresses";
1072 break;
1073 case PORT_OPT:
1074 ret = "IP port (optional)";
1075 break;
1076 case PORT:
1077 ret = "IP port";
1078 break;
1079 case REGEX_OPT:
1080 ret = "regular expression (optional)";
1081 break;
1082 case REGEX:
1083 ret = "regular expression";
1084 break;
1085 case STRING_OPT:
1086 ret = "string (optional)";
1087 break;
1088 case STRING:
1089 ret = "string";
1090 break;
1091 case VALRANGE:
1092 ret = "value range";
1093 break;
1094 }
1095
1096 return ret;
1097}
1098
1099void ConfigurationKey::printKey(const ConfigurationKey &key, const std::string& currentValue, ostream& os) {
1100 os << key.getName() << " ";
1101 if (!currentValue.length()) {
1102 os << "(disabled)";
1103 } else {
1104 os << currentValue;
1105 }
1106 if (currentValue.compare(key.getDefaultValue()) == 0) {
1107 os << " [default]";
1108 }
1109 os << endl;
1110}
1111
1112void ConfigurationKey::printDescription(const ConfigurationKey &key, ostream& os) {
1113 std::string tmp;
1114
1115 os << " - description: " << key.getDescription() << std::endl;
1116 if (key.getUnits().length()) {
1117 os << " - units: " << key.getUnits() << std::endl;
1118 }
1119 os << " - type: " << ConfigurationKey::typeToString(key.getType()) << std::endl;
1120 if (key.getDefaultValue().length()) {
1121 os << " - default value: " << key.getDefaultValue() << std::endl;
1122 }
1123 os << " - visibility level: " << ConfigurationKey::visibilityLevelToString(key.getVisibility()) << std::endl;
1124 os << " - static: " << key.isStatic() << std::endl;
1125
1126 tmp = key.getValidValues();
1127 if (key.getType() == ConfigurationKey::VALRANGE) {
1128 int startPos = tmp.find('(');
1129 uint delimiter = 0;
1130 if (startPos != (int)std::string::npos) {
1131 tmp = tmp.substr(0, startPos);
1132 }
1133 startPos = -1;
1134
1135 do {
1136 startPos++;
1137 delimiter = tmp.find(':', startPos);
1138 os << " - valid values: " << "from " << tmp.substr(startPos, delimiter-startPos) << " to "
1139 << tmp.substr(delimiter+1, tmp.find(',', delimiter)-delimiter-1) << std::endl;
1140
1141 } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
1142
1143 } else if (key.getType() == ConfigurationKey::CHOICE) {
1144 int startPos = -1;
1145 uint endPos = 0;
1146
1147 do {
1148 startPos++;
1149 if ((endPos = tmp.find('|', startPos)) != std::string::npos) {
1150 os << " - valid values: " << tmp.substr(startPos, endPos-startPos);
1151 os << " = " << tmp.substr(endPos+1, tmp.find(',', endPos)-endPos-1) << std::endl;
1152 } else {
1153 os << " - valid values: " << tmp.substr(startPos, tmp.find(',', startPos)-startPos) << std::endl;
1154 }
1155
1156 } while ((startPos = tmp.find(',', startPos)) != (int)std::string::npos);
1157
1158 } else if (key.getType() == ConfigurationKey::BOOLEAN) {
1159 os << " - valid values: 0 = disabled" << std::endl;
1160 os << " - valid values: 1 = enabled" << std::endl;
1161
1162 } else if (key.getType() == ConfigurationKey::STRING) {
1163 os << " - valid val regex: " << tmp << std::endl;
1164
1165 } else if (key.getValidValues().length()) {
1166 os << " - raw valid values: " << tmp << std::endl;
1167 }
1168}
1169
dburgess82c46ff2011-10-07 02:40:51 +00001170
1171// vim: ts=4 sw=4