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