blob: 5dcc27768eb03b04eab383b8334fad45fcfc1bd5 [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
dburgess82c46ff2011-10-07 02:40:51 +000035
36using namespace std;
37
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000038char gCmdName[20] = {0}; // Use a char* to avoid avoid static initialization of string, and race at startup.
dburgess82c46ff2011-10-07 02:40:51 +000039
40static const char* createConfigTable = {
41 "CREATE TABLE IF NOT EXISTS CONFIG ("
42 "KEYSTRING TEXT UNIQUE NOT NULL, "
43 "VALUESTRING TEXT, "
44 "STATIC INTEGER DEFAULT 0, "
45 "OPTIONAL INTEGER DEFAULT 0, "
46 "COMMENTS TEXT DEFAULT ''"
47 ")"
48};
49
50
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000051
52float ConfigurationRecord::floatNumber() const
dburgess82c46ff2011-10-07 02:40:51 +000053{
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000054 float val;
55 sscanf(mValue.c_str(),"%f",&val);
56 return val;
57}
58
59
kurtis.heimerl00913d72012-12-16 06:08:18 +000060ConfigurationTable::ConfigurationTable(const char* filename, const char *wCmdName)
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000061{
kurtis.heimerl00913d72012-12-16 06:08:18 +000062 gLogEarly(LOG_INFO, "opening configuration table from path %s", filename);
dburgess82c46ff2011-10-07 02:40:51 +000063 // Connect to the database.
64 int rc = sqlite3_open(filename,&mDB);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +000065 // (pat) When I used malloc here, sqlite3 sporadically crashes.
66 if (wCmdName) {
67 strncpy(gCmdName,wCmdName,18);
68 gCmdName[18] = 0;
69 strcat(gCmdName,":");
70 }
dburgess82c46ff2011-10-07 02:40:51 +000071 if (rc) {
kurtis.heimerl00913d72012-12-16 06:08:18 +000072 gLogEarly(LOG_EMERG, "cannot open configuration database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
dburgess82c46ff2011-10-07 02:40:51 +000073 sqlite3_close(mDB);
74 mDB = NULL;
75 return;
76 }
77 // Create the table, if needed.
78 if (!sqlite3_command(mDB,createConfigTable)) {
kurtis.heimerl00913d72012-12-16 06:08:18 +000079 gLogEarly(LOG_EMERG, "cannot create configuration table in database at %s, error message: %s", filename, sqlite3_errmsg(mDB));
dburgess82c46ff2011-10-07 02:40:51 +000080 }
81}
82
83
84
85bool ConfigurationTable::defines(const string& key)
86{
87 assert(mDB);
88 ScopedLock lock(mLock);
89
90 // Check the cache.
91 checkCacheAge();
92 ConfigurationMap::const_iterator where = mCache.find(key);
93 if (where!=mCache.end()) return where->second.defined();
94
95 // Check the database.
96 char *value = NULL;
97 sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"VALUESTRING",value);
98
99 // Cache the result.
100 if (value) {
101 mCache[key] = ConfigurationRecord(value);
102 free(value);
103 return true;
104 }
105
106 mCache[key] = ConfigurationRecord(false);
107 return false;
108}
109
110
111const ConfigurationRecord& ConfigurationTable::lookup(const string& key)
112{
113 assert(mDB);
114 checkCacheAge();
115 // We assume the caller holds mLock.
116 // So it is OK to return a reference into the cache.
117
118 // Check the cache.
119 // This is cheap.
120 ConfigurationMap::const_iterator where = mCache.find(key);
121 if (where!=mCache.end()) {
122 if (where->second.defined()) return where->second;
dburgess82c46ff2011-10-07 02:40:51 +0000123 throw ConfigurationTableKeyNotFound(key);
124 }
125
126 // Check the database.
127 // This is more expensive.
128 char *value = NULL;
129 sqlite3_single_lookup(mDB,"CONFIG",
130 "KEYSTRING",key.c_str(),"VALUESTRING",value);
131
132 // Nothing defined?
133 if (!value) {
134 // Cache the failure.
135 mCache[key] = ConfigurationRecord(false);
dburgess82c46ff2011-10-07 02:40:51 +0000136 throw ConfigurationTableKeyNotFound(key);
137 }
138
139 // Cache the result.
140 mCache[key] = ConfigurationRecord(value);
141 free(value);
142
143 // Leave mLock locked. The caller holds it still.
144 return mCache[key];
145}
146
147
148
149bool ConfigurationTable::isStatic(const string& key) const
150{
151 assert(mDB);
152 unsigned stat;
153 bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"STATIC",stat);
154 if (success) return (bool)stat;
155 return false;
156}
157
158bool ConfigurationTable::isRequired(const string& key) const
159{
160 assert(mDB);
161 unsigned optional;
162 bool success = sqlite3_single_lookup(mDB,"CONFIG","KEYSTRING",key.c_str(),"OPTIONAL",optional);
163 if (success) return !((bool)optional);
164 return false;
165}
166
167
168
169
170string ConfigurationTable::getStr(const string& key)
171{
172 // We need the lock because rec is a reference into the cache.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000173 try {
174 ScopedLock lock(mLock);
175 return lookup(key).value();
176 } catch (ConfigurationTableKeyNotFound) {
177 // Raise an alert and re-throw the exception.
kurtis.heimerl00913d72012-12-16 06:08:18 +0000178 gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000179 throw ConfigurationTableKeyNotFound(key);
180 }
dburgess82c46ff2011-10-07 02:40:51 +0000181}
182
183string ConfigurationTable::getStr(const string& key, const char* defaultValue)
184{
185 try {
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000186 ScopedLock lock(mLock);
187 return lookup(key).value();
dburgess82c46ff2011-10-07 02:40:51 +0000188 } catch (ConfigurationTableKeyNotFound) {
kurtis.heimerl00913d72012-12-16 06:08:18 +0000189 gLogEarly(LOG_NOTICE, "deinfing missing parameter %s with value %s", key.c_str(),defaultValue);
dburgess82c46ff2011-10-07 02:40:51 +0000190 set(key,defaultValue);
191 return string(defaultValue);
192 }
193}
194
195
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000196bool ConfigurationTable::getBool(const string& key)
197{
198 try {
199 return getNum(key) != 0;
200 } catch (ConfigurationTableKeyNotFound) {
201 return false;
202 }
203}
204
205
dburgess82c46ff2011-10-07 02:40:51 +0000206long ConfigurationTable::getNum(const string& key)
207{
208 // We need the lock because rec is a reference into the cache.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000209 try {
210 ScopedLock lock(mLock);
211 return lookup(key).number();
212 } catch (ConfigurationTableKeyNotFound) {
213 // Raise an alert and re-throw the exception.
kurtis.heimerl00913d72012-12-16 06:08:18 +0000214 gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000215 throw ConfigurationTableKeyNotFound(key);
216 }
dburgess82c46ff2011-10-07 02:40:51 +0000217}
218
219
220long ConfigurationTable::getNum(const string& key, long defaultValue)
221{
222 try {
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000223 ScopedLock lock(mLock);
224 return lookup(key).number();
dburgess82c46ff2011-10-07 02:40:51 +0000225 } catch (ConfigurationTableKeyNotFound) {
kurtis.heimerl00913d72012-12-16 06:08:18 +0000226 gLogEarly(LOG_NOTICE, "deinfing missing parameter %s with value %ld", key.c_str(),defaultValue);
dburgess82c46ff2011-10-07 02:40:51 +0000227 set(key,defaultValue);
228 return defaultValue;
229 }
230}
231
232
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000233float ConfigurationTable::getFloat(const string& key)
234{
235 // We need the lock because rec is a reference into the cache.
236 ScopedLock lock(mLock);
237 return lookup(key).floatNumber();
238}
239
240std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key)
241{
242 // Look up the string.
243 char *line=NULL;
244 try {
245 ScopedLock lock(mLock);
246 const ConfigurationRecord& rec = lookup(key);
247 line = strdup(rec.value().c_str());
248 } catch (ConfigurationTableKeyNotFound) {
249 // Raise an alert and re-throw the exception.
kurtis.heimerl00913d72012-12-16 06:08:18 +0000250 gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000251 throw ConfigurationTableKeyNotFound(key);
252 }
253
254 assert(line);
255 char *lp = line;
256
257 // Parse the string.
258 std::vector<string> retVal;
259 while (lp) {
260 while (*lp==' ') lp++;
261 if (*lp == '\0') break;
262 char *tp = strsep(&lp," ");
263 if (!tp) break;
264 retVal.push_back(tp);
265 }
266 free(line);
267 return retVal;
268}
269
270
271std::vector<string> ConfigurationTable::getVectorOfStrings(const string& key, const char* defaultValue){
272 try {
273 return getVectorOfStrings(key);
274 } catch (ConfigurationTableKeyNotFound) {
275 set(key,defaultValue);
276 return getVectorOfStrings(key);
277 }
278}
279
280
dburgess82c46ff2011-10-07 02:40:51 +0000281
282std::vector<unsigned> ConfigurationTable::getVector(const string& key)
283{
284 // Look up the string.
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000285 char *line=NULL;
286 try {
287 ScopedLock lock(mLock);
288 const ConfigurationRecord& rec = lookup(key);
289 line = strdup(rec.value().c_str());
290 } catch (ConfigurationTableKeyNotFound) {
291 // Raise an alert and re-throw the exception.
kurtis.heimerl00913d72012-12-16 06:08:18 +0000292 gLogEarly(LOG_ALERT, "configuration parameter %s has no defined value", key.c_str());
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000293 throw ConfigurationTableKeyNotFound(key);
294 }
295
296 assert(line);
297 char *lp = line;
298
dburgess82c46ff2011-10-07 02:40:51 +0000299 // Parse the string.
300 std::vector<unsigned> retVal;
dburgess82c46ff2011-10-07 02:40:51 +0000301 while (lp) {
302 // Watch for multiple or trailing spaces.
303 while (*lp==' ') lp++;
304 if (*lp=='\0') break;
305 retVal.push_back(strtol(lp,NULL,0));
306 strsep(&lp," ");
307 }
308 free(line);
309 return retVal;
310}
311
312
313bool ConfigurationTable::unset(const string& key)
314{
315 assert(mDB);
316 if (!defines(key)) return true;
317 if (isRequired(key)) return false;
318
319 ScopedLock lock(mLock);
320 // Clear the cache entry and the database.
321 ConfigurationMap::iterator where = mCache.find(key);
322 if (where!=mCache.end()) mCache.erase(where);
323 // Don't delete it; just set VALUESTRING to NULL.
324 string cmd = "UPDATE CONFIG SET VALUESTRING=NULL WHERE KEYSTRING=='"+key+"'";
325 return sqlite3_command(mDB,cmd.c_str());
326}
327
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000328bool ConfigurationTable::remove(const string& key)
329{
330 assert(mDB);
331 if (isRequired(key)) return false;
332
333 ScopedLock lock(mLock);
334 // Clear the cache entry and the database.
335 ConfigurationMap::iterator where = mCache.find(key);
336 if (where!=mCache.end()) mCache.erase(where);
337 // Really remove it.
338 string cmd = "DELETE FROM CONFIG WHERE KEYSTRING=='"+key+"'";
339 return sqlite3_command(mDB,cmd.c_str());
340}
341
342
dburgess82c46ff2011-10-07 02:40:51 +0000343
344void ConfigurationTable::find(const string& pat, ostream& os) const
345{
346 // Prepare the statement.
347 string cmd = "SELECT KEYSTRING,VALUESTRING FROM CONFIG WHERE KEYSTRING LIKE \"%" + pat + "%\"";
348 sqlite3_stmt *stmt;
349 if (sqlite3_prepare_statement(mDB,&stmt,cmd.c_str())) return;
350 // Read the result.
351 int src = sqlite3_run_query(mDB,stmt);
352 while (src==SQLITE_ROW) {
353 const char* value = (const char*)sqlite3_column_text(stmt,1);
354 os << sqlite3_column_text(stmt,0) << " ";
355 if (value) os << value << endl;
356 else os << "(null)" << endl;
357 src = sqlite3_run_query(mDB,stmt);
358 }
359 sqlite3_finalize(stmt);
360}
361
362
363bool ConfigurationTable::set(const string& key, const string& value)
364{
365 assert(mDB);
366 ScopedLock lock(mLock);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000367 string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",\"" + value + "\",1)";
dburgess82c46ff2011-10-07 02:40:51 +0000368 bool success = sqlite3_command(mDB,cmd.c_str());
369 // Cache the result.
370 if (success) mCache[key] = ConfigurationRecord(value);
371 return success;
372}
373
374bool ConfigurationTable::set(const string& key, long value)
375{
376 char buffer[30];
377 sprintf(buffer,"%ld",value);
378 return set(key,buffer);
379}
380
381
382bool ConfigurationTable::set(const string& key)
383{
384 assert(mDB);
385 ScopedLock lock(mLock);
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000386 string cmd = "INSERT OR REPLACE INTO CONFIG (KEYSTRING,VALUESTRING,OPTIONAL) VALUES (\"" + key + "\",NULL,1)";
dburgess82c46ff2011-10-07 02:40:51 +0000387 bool success = sqlite3_command(mDB,cmd.c_str());
388 if (success) mCache[key] = ConfigurationRecord(true);
389 return success;
390}
391
392
393void ConfigurationTable::checkCacheAge()
394{
395 // mLock is set by caller
396 static time_t timeOfLastPurge = 0;
397 time_t now = time(NULL);
398 // purge every 3 seconds
399 // purge period cannot be configuration parameter
400 if (now - timeOfLastPurge < 3) return;
401 timeOfLastPurge = now;
402 // this is purge() without the lock
403 ConfigurationMap::iterator mp = mCache.begin();
404 while (mp != mCache.end()) {
405 ConfigurationMap::iterator prev = mp;
406 mp++;
407 mCache.erase(prev);
408 }
409}
410
411
412void ConfigurationTable::purge()
413{
414 ScopedLock lock(mLock);
415 ConfigurationMap::iterator mp = mCache.begin();
416 while (mp != mCache.end()) {
417 ConfigurationMap::iterator prev = mp;
418 mp++;
419 mCache.erase(prev);
420 }
421}
422
423
424void ConfigurationTable::setUpdateHook(void(*func)(void *,int ,char const *,char const *,sqlite3_int64))
425{
426 assert(mDB);
427 sqlite3_update_hook(mDB,func,NULL);
428}
429
430
431
432void HashString::computeHash()
433{
434 // FIXME -- Someone needs to review this hash function.
435 const char* cstr = c_str();
436 mHash = 0;
437 for (unsigned i=0; i<size(); i++) {
438 mHash = mHash ^ (mHash >> 32);
439 mHash = mHash*127 + cstr[i];
440 }
441}
442
443
kurtis.heimerlbcf60a82012-10-26 06:25:56 +0000444void SimpleKeyValue::addItem(const char* pair_orig)
445{
446 char *pair = strdup(pair_orig);
447 char *key = pair;
448 char *mark = strchr(pair,'=');
449 if (!mark) return;
450 *mark = '\0';
451 char *value = mark+1;
452 mMap[key] = value;
453 free(pair);
454}
455
456
457
458const char* SimpleKeyValue::get(const char* key) const
459{
460 HashStringMap::const_iterator p = mMap.find(key);
461 if (p==mMap.end()) return NULL;
462 return p->second.c_str();
463}
464
465
466void SimpleKeyValue::addItems(const char* pairs_orig)
467{
468 char *pairs = strdup(pairs_orig);
469 char *thisPair;
470 while ((thisPair=strsep(&pairs," "))!=NULL) {
471 addItem(thisPair);
472 }
473 free(pairs);
474}
475
476
dburgess82c46ff2011-10-07 02:40:51 +0000477
478// vim: ts=4 sw=4