fixed memory leaks
[public/netxms.git] / src / libnetxms / config.cpp
CommitLineData
5039dede 1/*
207d8b21
VP
2 ** NetXMS - Network Management System
3 ** NetXMS Foundation Library
4 ** Copyright (C) 2003-2013 Raden Solutions
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU Lesser General Public License as published
8 ** by the Free Software Foundation; either version 3 of the License, or
9 ** (at your option) any later version.
10 **
11 ** This program is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ** GNU General Public License for more details.
15 **
16 ** You should have received a copy of the GNU Lesser General Public License
17 ** along with this program; if not, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 **
20 ** File: config.cpp
21 **
22 **/
5039dede
AK
23
24#include "libnetxms.h"
e6c91aac 25#include <nxconfig.h>
ff175e91 26#include <expat.h>
279cb65b 27#include <nxstat.h>
5039dede 28
40ca2297
VK
29/**
30 * Constructor for config entry
31 */
a65c1819 32ConfigEntry::ConfigEntry(const TCHAR *name, ConfigEntry *parent, const TCHAR *file, int line, int id)
e6c91aac 33{
207d8b21
VP
34 m_name = _tcsdup(CHECK_NULL(name));
35 m_first = NULL;
9387bc59 36 m_last = NULL;
207d8b21 37 m_next = NULL;
def9a42c
VP
38 if (parent != NULL)
39 parent->addEntry(this);
207d8b21
VP
40 m_valueCount = 0;
41 m_values = NULL;
42 m_file = _tcsdup(CHECK_NULL(file));
43 m_line = line;
44 m_id = id;
e6c91aac
VK
45}
46
40ca2297
VK
47/**
48 * Destructor for config entry
49 */
e6c91aac
VK
50ConfigEntry::~ConfigEntry()
51{
207d8b21 52 ConfigEntry *entry, *next;
e6c91aac 53
207d8b21
VP
54 for(entry = m_first; entry != NULL; entry = next)
55 {
56 next = entry->getNext();
57 delete entry;
58 }
59 safe_free(m_name);
60 safe_free(m_file);
e6c91aac 61
207d8b21
VP
62 for(int i = 0; i < m_valueCount; i++)
63 safe_free(m_values[i]);
64 safe_free(m_values);
e6c91aac
VK
65}
66
40ca2297
VK
67/**
68 * Set entry's name
69 */
8a0dffac
VK
70void ConfigEntry::setName(const TCHAR *name)
71{
207d8b21
VP
72 safe_free(m_name);
73 m_name = _tcsdup(CHECK_NULL(name));
8a0dffac
VK
74}
75
40ca2297
VK
76/**
77 * Find entry by name
78 */
207d8b21 79ConfigEntry* ConfigEntry::findEntry(const TCHAR *name)
e6c91aac 80{
207d8b21 81 ConfigEntry *e;
e6c91aac 82
207d8b21 83 for(e = m_first; e != NULL; e = e->getNext())
def9a42c
VP
84 if (!_tcsicmp(e->getName(), name))
85 return e;
207d8b21 86 return NULL;
e6c91aac
VK
87}
88
9387bc59
VK
89/**
90 * Create (or find existing) subentry
91 */
207d8b21 92ConfigEntry* ConfigEntry::createEntry(const TCHAR *name)
203e9d8a 93{
207d8b21 94 ConfigEntry *e;
203e9d8a 95
207d8b21 96 for(e = m_first; e != NULL; e = e->getNext())
def9a42c
VP
97 if (!_tcsicmp(e->getName(), name))
98 return e;
203e9d8a 99
207d8b21 100 return new ConfigEntry(name, this, _T("<memory>"), 0, 0);
203e9d8a
VK
101}
102
9387bc59
VK
103/**
104 * Add sub-entry
105 */
106void ConfigEntry::addEntry(ConfigEntry *entry)
207d8b21
VP
107{
108 entry->m_parent = this;
9387bc59 109 entry->m_next = NULL;
def9a42c
VP
110 if (m_last != NULL)
111 m_last->m_next = entry;
9387bc59 112 m_last = entry;
def9a42c
VP
113 if (m_first == NULL)
114 m_first = entry;
9387bc59 115}
203e9d8a 116
9387bc59
VK
117/**
118 * Unlink subentry
119 */
203e9d8a
VK
120void ConfigEntry::unlinkEntry(ConfigEntry *entry)
121{
207d8b21
VP
122 ConfigEntry *curr, *prev;
123
124 for(curr = m_first, prev = NULL; curr != NULL; curr = curr->m_next)
125 {
126 if (curr == entry)
127 {
128 if (prev != NULL)
129 {
130 prev->m_next = curr->m_next;
131 }
132 else
133 {
134 m_first = curr->m_next;
135 }
def9a42c
VP
136 if (m_last == curr)
137 m_last = prev;
9387bc59 138 curr->m_next = NULL;
207d8b21
VP
139 break;
140 }
141 prev = curr;
142 }
203e9d8a
VK
143}
144
9387bc59
VK
145/**
146 * Get all subentries with names matched to mask
147 */
207d8b21 148ConfigEntryList* ConfigEntry::getSubEntries(const TCHAR *mask)
fc61a868 149{
207d8b21
VP
150 ConfigEntry *e, **list = NULL;
151 int count = 0, allocated = 0;
fc61a868 152
207d8b21
VP
153 for(e = m_first; e != NULL; e = e->getNext())
154 if ((mask == NULL) || MatchString(mask, e->getName(), FALSE))
155 {
156 if (count == allocated)
157 {
158 allocated += 10;
159 list = (ConfigEntry **) realloc(list, sizeof(ConfigEntry *) * allocated);
160 }
161 list[count++] = e;
162 }
163 return new ConfigEntryList(list, count);
fc61a868
VK
164}
165
9387bc59
VK
166/**
167 * Get all subentries with names matched to mask ordered by id
168 */
207d8b21 169ConfigEntryList* ConfigEntry::getOrderedSubEntries(const TCHAR *mask)
a65c1819 170{
207d8b21
VP
171 ConfigEntryList *list = getSubEntries(mask);
172 list->sortById();
173 return list;
a65c1819
VK
174}
175
207d8b21
VP
176/*
177 * Get value
178 */
a65c1819 179
207d8b21 180const TCHAR* ConfigEntry::getValue(int index)
e6c91aac 181{
def9a42c
VP
182 if ((index < 0) || (index >= m_valueCount))
183 return NULL;
207d8b21 184 return m_values[index];
e6c91aac
VK
185}
186
967893bb 187INT32 ConfigEntry::getValueInt(int index, INT32 defaultValue)
a65c1819 188{
207d8b21
VP
189 const TCHAR *value = getValue(index);
190 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
a65c1819
VK
191}
192
967893bb 193UINT32 ConfigEntry::getValueUInt(int index, UINT32 defaultValue)
a65c1819 194{
207d8b21
VP
195 const TCHAR *value = getValue(index);
196 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
a65c1819
VK
197}
198
199INT64 ConfigEntry::getValueInt64(int index, INT64 defaultValue)
200{
207d8b21
VP
201 const TCHAR *value = getValue(index);
202 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
a65c1819
VK
203}
204
967893bb 205UINT64 ConfigEntry::getValueUInt64(int index, UINT64 defaultValue)
a65c1819 206{
207d8b21
VP
207 const TCHAR *value = getValue(index);
208 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
a65c1819
VK
209}
210
211bool ConfigEntry::getValueBoolean(int index, bool defaultValue)
212{
207d8b21
VP
213 const TCHAR *value = getValue(index);
214 if (value != NULL)
215 {
216 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
217 || (_tcstol(value, NULL, 0) != 0);
218 }
219 else
220 {
221 return defaultValue;
222 }
a65c1819
VK
223}
224
203e9d8a
VK
225bool ConfigEntry::getValueUUID(int index, uuid_t uuid)
226{
207d8b21
VP
227 const TCHAR *value = getValue(index);
228 if (value != NULL)
229 {
230 return uuid_parse(value, uuid) == 0;
231 }
232 return false;
203e9d8a
VK
233}
234
207d8b21
VP
235/*
236 * Set value
237 */
e6c91aac
VK
238
239void ConfigEntry::setValue(const TCHAR *value)
240{
207d8b21
VP
241 for(int i = 0; i < m_valueCount; i++)
242 safe_free(m_values[i]);
243 m_valueCount = 1;
244 m_values = (TCHAR **) realloc(m_values, sizeof(TCHAR *));
245 m_values[0] = _tcsdup(value);
e6c91aac
VK
246}
247
207d8b21
VP
248/*
249 * Add value
250 */
e6c91aac
VK
251
252void ConfigEntry::addValue(const TCHAR *value)
253{
207d8b21
VP
254 m_values = (TCHAR **) realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
255 m_values[m_valueCount] = _tcsdup(value);
256 m_valueCount++;
e6c91aac
VK
257}
258
207d8b21
VP
259/*
260 * Get summary length of all values as if they was concatenated with separator character
261 */
e6c91aac
VK
262
263int ConfigEntry::getConcatenatedValuesLength()
264{
207d8b21 265 int i, len;
e6c91aac 266
def9a42c
VP
267 if (m_valueCount < 1)
268 return 0;
e6c91aac 269
207d8b21
VP
270 for(i = 0, len = 0; i < m_valueCount; i++)
271 len += (int) _tcslen(m_values[i]);
272 return len + m_valueCount;
e6c91aac
VK
273}
274
9387bc59
VK
275/**
276 * Get value for given subentry
277 *
278 * @param name sub-entry name
279 * @param index sub-entry index (0 if omited)
280 * @param defaultValue value to be returned if requested sub-entry is missing (NULL if omited)
281 * @return sub-entry value or default value if requested sub-entry does not exist
282 */
207d8b21 283const TCHAR* ConfigEntry::getSubEntryValue(const TCHAR *name, int index, const TCHAR *defaultValue)
a65c1819 284{
207d8b21 285 ConfigEntry *e = findEntry(name);
def9a42c
VP
286 if (e == NULL)
287 return defaultValue;
207d8b21
VP
288 const TCHAR *value = e->getValue(index);
289 return (value != NULL) ? value : defaultValue;
a65c1819
VK
290}
291
967893bb 292INT32 ConfigEntry::getSubEntryValueInt(const TCHAR *name, int index, INT32 defaultValue)
a65c1819 293{
207d8b21
VP
294 const TCHAR *value = getSubEntryValue(name, index);
295 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
a65c1819
VK
296}
297
967893bb 298UINT32 ConfigEntry::getSubEntryValueUInt(const TCHAR *name, int index, UINT32 defaultValue)
a65c1819 299{
207d8b21
VP
300 const TCHAR *value = getSubEntryValue(name, index);
301 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
a65c1819
VK
302}
303
304INT64 ConfigEntry::getSubEntryValueInt64(const TCHAR *name, int index, INT64 defaultValue)
305{
207d8b21
VP
306 const TCHAR *value = getSubEntryValue(name, index);
307 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
a65c1819
VK
308}
309
967893bb 310UINT64 ConfigEntry::getSubEntryValueUInt64(const TCHAR *name, int index, UINT64 defaultValue)
a65c1819 311{
207d8b21
VP
312 const TCHAR *value = getSubEntryValue(name, index);
313 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
a65c1819
VK
314}
315
316bool ConfigEntry::getSubEntryValueBoolean(const TCHAR *name, int index, bool defaultValue)
317{
207d8b21
VP
318 const TCHAR *value = getSubEntryValue(name, index);
319 if (value != NULL)
320 {
321 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
322 || (_tcstol(value, NULL, 0) != 0);
323 }
324 else
325 {
326 return defaultValue;
327 }
a65c1819
VK
328}
329
203e9d8a
VK
330bool ConfigEntry::getSubEntryValueUUID(const TCHAR *name, uuid_t uuid, int index)
331{
207d8b21
VP
332 const TCHAR *value = getSubEntryValue(name, index);
333 if (value != NULL)
334 {
335 return uuid_parse(value, uuid) == 0;
336 }
337 else
338 {
339 return false;
340 }
203e9d8a
VK
341}
342
9387bc59
VK
343/**
344 * Print config entry
345 */
abf35d58
VK
346void ConfigEntry::print(FILE *file, int level)
347{
207d8b21
VP
348 _tprintf(_T("%*s%s\n"), level * 4, _T(""), m_name);
349 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
350 e->print(file, level + 1);
abf35d58 351
207d8b21
VP
352 for(int i = 0, len = 0; i < m_valueCount; i++)
353 _tprintf(_T("%*svalue: %s\n"), level * 4 + 2, _T(""), m_values[i]);
abf35d58
VK
354}
355
6603931b
VK
356/**
357 * Create XML element(s) from config entry
358 */
8a0dffac
VK
359void ConfigEntry::createXml(String &xml, int level)
360{
207d8b21
VP
361 TCHAR *name = _tcsdup(m_name);
362 TCHAR *ptr = _tcschr(name, _T('#'));
def9a42c
VP
363 if (ptr != NULL)
364 *ptr = 0;
8a0dffac 365
207d8b21
VP
366 if (m_id == 0)
367 xml.addFormattedString(_T("%*s<%s>"), level * 4, _T(""), name);
368 else
369 xml.addFormattedString(_T("%*s<%s id=\"%d\">"), level * 4, _T(""), name, m_id);
8a0dffac 370
207d8b21
VP
371 if (m_first != NULL)
372 {
373 xml += _T("\n");
374 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
375 e->createXml(xml, level + 1);
376 xml.addFormattedString(_T("%*s"), level * 4, _T(""));
377 }
8a0dffac 378
def9a42c
VP
379 if (m_valueCount > 0)
380 xml.addDynamicString(EscapeStringForXML(m_values[0], -1));
207d8b21 381 xml.addFormattedString(_T("</%s>\n"), name);
8a0dffac 382
207d8b21
VP
383 for(int i = 1, len = 0; i < m_valueCount; i++)
384 {
385 if (m_id == 0)
386 xml.addFormattedString(_T("%*s<%s>"), level * 4, _T(""), name);
387 else
388 xml.addFormattedString(_T("%*s<%s id=\"%d\">"), level * 4, _T(""), name, m_id);
389 xml.addDynamicString(EscapeStringForXML(m_values[i], -1));
390 xml.addFormattedString(_T("</%s>\n"), name);
391 }
8a0dffac 392
207d8b21 393 free(name);
8a0dffac
VK
394}
395
e2e66145
VK
396/**
397 * Comparator for ConfigEntryList::sortById()
207d8b21 398 */
a65c1819
VK
399static int CompareById(const void *p1, const void *p2)
400{
207d8b21
VP
401 ConfigEntry *e1 = *((ConfigEntry **) p1);
402 ConfigEntry *e2 = *((ConfigEntry **) p2);
403 return e1->getId() - e2->getId();
a65c1819
VK
404}
405
e2e66145
VK
406/**
407 * Sort entry list
408 */
a65c1819
VK
409void ConfigEntryList::sortById()
410{
207d8b21 411 qsort(m_list, m_size, sizeof(ConfigEntry *), CompareById);
a65c1819
VK
412}
413
e2e66145 414/**
207d8b21
VP
415 * Constructor for config
416 */
8a5ba964 417Config::Config()
5039dede 418{
207d8b21
VP
419 m_root = new ConfigEntry(_T("[root]"), NULL, NULL, 0, 0);
420 m_errorCount = 0;
421 m_mutex = MutexCreate();
8a5ba964 422}
5039dede 423
e2e66145 424/**
207d8b21
VP
425 * Destructor
426 */
8a5ba964
AK
427Config::~Config()
428{
207d8b21
VP
429 delete m_root;
430 MutexDestroy(m_mutex);
e6c91aac
VK
431}
432
e2e66145 433/**
207d8b21
VP
434 * Default error handler
435 */
e6c91aac
VK
436void Config::onError(const TCHAR *errorMessage)
437{
207d8b21 438 _ftprintf(stderr, _T("%s\n"), errorMessage);
e6c91aac
VK
439}
440
e2e66145 441/**
207d8b21
VP
442 * Report error
443 */
e6c91aac
VK
444void Config::error(const TCHAR *format, ...)
445{
207d8b21
VP
446 va_list args;
447 TCHAR buffer[4096];
e6c91aac 448
207d8b21
VP
449 m_errorCount++;
450 va_start(args, format);
451 _vsntprintf(buffer, 4096, format, args);
452 va_end(args);
453 onError(buffer);
e6c91aac
VK
454}
455
e2e66145
VK
456/**
457 * Parse configuration template (emulation of old NxLoadConfig() API)
207d8b21 458 */
97a92859 459bool Config::parseTemplate(const TCHAR *section, NX_CFG_TEMPLATE *cfgTemplate)
e6c91aac 460{
207d8b21
VP
461 TCHAR name[MAX_PATH], *curr, *eptr;
462 int i, j, pos, initialErrorCount = m_errorCount;
463 ConfigEntry *entry;
464
465 name[0] = _T('/');
466 nx_strncpy(&name[1], section, MAX_PATH - 2);
467 _tcscat(name, _T("/"));
468 pos = (int) _tcslen(name);
469
e2e66145 470 for(i = 0; cfgTemplate[i].type != CT_END_OF_LIST; i++)
207d8b21 471 {
e2e66145 472 nx_strncpy(&name[pos], cfgTemplate[i].token, MAX_PATH - pos);
207d8b21
VP
473 entry = getEntry(name);
474 if (entry != NULL)
475 {
476 const TCHAR *value = CHECK_NULL(entry->getValue(entry->getValueCount() - 1));
e2e66145 477 switch (cfgTemplate[i].type)
e6c91aac
VK
478 {
479 case CT_LONG:
e2e66145
VK
480 if ((cfgTemplate[i].overrideIndicator != NULL) &&
481 (*((INT32 *)cfgTemplate[i].overrideIndicator) != NXCONFIG_UNINITIALIZED_VALUE))
482 {
483 break; // this parameter was already initialized, and override from config is forbidden
484 }
485 *((INT32 *)cfgTemplate[i].buffer) = _tcstol(value, &eptr, 0);
e6c91aac
VK
486 if (*eptr != 0)
487 {
207d8b21 488 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
e6c91aac
VK
489 }
490 break;
491 case CT_WORD:
e2e66145
VK
492 if ((cfgTemplate[i].overrideIndicator != NULL) &&
493 (*((INT16 *)cfgTemplate[i].overrideIndicator) != NXCONFIG_UNINITIALIZED_VALUE))
494 {
495 break; // this parameter was already initialized, and override from config is forbidden
496 }
497 *((UINT16 *)cfgTemplate[i].buffer) = (UINT16)_tcstoul(value, &eptr, 0);
e6c91aac
VK
498 if (*eptr != 0)
499 {
207d8b21 500 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
e6c91aac
VK
501 }
502 break;
503 case CT_BOOLEAN:
207d8b21
VP
504 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
505 || !_tcsicmp(value, _T("1")))
e6c91aac 506 {
e2e66145 507 *((UINT32 *)cfgTemplate[i].buffer) |= cfgTemplate[i].bufferSize;
e6c91aac
VK
508 }
509 else
510 {
e2e66145 511 *((UINT32 *)cfgTemplate[i].buffer) &= ~(cfgTemplate[i].bufferSize);
e6c91aac
VK
512 }
513 break;
514 case CT_STRING:
e2e66145
VK
515 if ((cfgTemplate[i].overrideIndicator != NULL) &&
516 (*((TCHAR *)cfgTemplate[i].overrideIndicator) != 0))
517 {
518 break; // this parameter was already initialized, and override from config is forbidden
519 }
520 nx_strncpy((TCHAR *)cfgTemplate[i].buffer, value, cfgTemplate[i].bufferSize);
e6c91aac 521 break;
dda7c270 522 case CT_MB_STRING:
e2e66145
VK
523 if ((cfgTemplate[i].overrideIndicator != NULL) &&
524 (*((char *)cfgTemplate[i].overrideIndicator) != 0))
525 {
526 break; // this parameter was already initialized, and override from config is forbidden
527 }
dda7c270 528#ifdef UNICODE
e2e66145
VK
529 memset(cfgTemplate[i].buffer, 0, cfgTemplate[i].bufferSize);
530 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR, value, -1, (char *)cfgTemplate[i].buffer, cfgTemplate[i].bufferSize - 1, NULL, NULL);
dda7c270 531#else
2d33afc4 532 nx_strncpy((TCHAR *)cfgTemplate[i].buffer, value, cfgTemplate[i].bufferSize);
dda7c270
VK
533#endif
534 break;
e6c91aac 535 case CT_STRING_LIST:
e2e66145
VK
536 *((TCHAR **)cfgTemplate[i].buffer) = (TCHAR *)malloc(sizeof(TCHAR) * (entry->getConcatenatedValuesLength() + 1));
537 for(j = 0, curr = *((TCHAR **) cfgTemplate[i].buffer); j < entry->getValueCount(); j++)
207d8b21
VP
538 {
539 _tcscpy(curr, entry->getValue(j));
540 curr += _tcslen(curr);
e2e66145 541 *curr = cfgTemplate[i].separator;
207d8b21
VP
542 curr++;
543 }
544 *curr = 0;
e6c91aac
VK
545 break;
546 case CT_IGNORE:
547 break;
548 default:
549 break;
550 }
207d8b21
VP
551 }
552 }
e6c91aac 553
207d8b21 554 return (m_errorCount - initialErrorCount) == 0;
8a5ba964 555}
25ce4818 556
e2e66145 557/**
207d8b21
VP
558 * Get value
559 */
207d8b21 560const TCHAR * Config::getValue(const TCHAR *path, const TCHAR *defaultValue)
ff175e91 561{
207d8b21
VP
562 const TCHAR *value;
563 ConfigEntry *entry = getEntry(path);
564 if (entry != NULL)
565 {
566 value = entry->getValue();
def9a42c
VP
567 if (value == NULL)
568 value = defaultValue;
207d8b21
VP
569 }
570 else
571 {
572 value = defaultValue;
573 }
574 return value;
ec19c32d
VK
575}
576
967893bb 577INT32 Config::getValueInt(const TCHAR *path, INT32 defaultValue)
ec19c32d 578{
207d8b21
VP
579 const TCHAR *value = getValue(path);
580 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
ec19c32d
VK
581}
582
967893bb 583UINT32 Config::getValueUInt(const TCHAR *path, UINT32 defaultValue)
ec19c32d 584{
207d8b21
VP
585 const TCHAR *value = getValue(path);
586 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
ec19c32d
VK
587}
588
589INT64 Config::getValueInt64(const TCHAR *path, INT64 defaultValue)
590{
207d8b21
VP
591 const TCHAR *value = getValue(path);
592 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
ec19c32d
VK
593}
594
967893bb 595UINT64 Config::getValueUInt64(const TCHAR *path, UINT64 defaultValue)
ec19c32d 596{
207d8b21
VP
597 const TCHAR *value = getValue(path);
598 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
ff175e91
VK
599}
600
e6736e5d
VK
601bool Config::getValueBoolean(const TCHAR *path, bool defaultValue)
602{
207d8b21
VP
603 const TCHAR *value = getValue(path);
604 if (value != NULL)
605 {
606 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
607 || (_tcstol(value, NULL, 0) != 0);
608 }
609 else
610 {
611 return defaultValue;
612 }
e6736e5d
VK
613}
614
203e9d8a
VK
615bool Config::getValueUUID(const TCHAR *path, uuid_t uuid)
616{
207d8b21
VP
617 const TCHAR *value = getValue(path);
618 if (value != NULL)
619 {
620 return uuid_parse(value, uuid) == 0;
621 }
622 else
623 {
624 return false;
625 }
203e9d8a
VK
626}
627
207d8b21
VP
628/*
629 * Get subentries
630 */
ff175e91 631
207d8b21 632ConfigEntryList * Config::getSubEntries(const TCHAR *path, const TCHAR *mask)
fc61a868 633{
207d8b21
VP
634 ConfigEntry *entry = getEntry(path);
635 return (entry != NULL) ? entry->getSubEntries(mask) : NULL;
fc61a868
VK
636}
637
207d8b21
VP
638/*
639 * Get subentries ordered by id
640 */
fc61a868 641
207d8b21 642ConfigEntryList * Config::getOrderedSubEntries(const TCHAR *path, const TCHAR *mask)
a65c1819 643{
207d8b21
VP
644 ConfigEntry *entry = getEntry(path);
645 return (entry != NULL) ? entry->getOrderedSubEntries(mask) : NULL;
a65c1819
VK
646}
647
207d8b21
VP
648/*
649 * Get entry
650 */
a65c1819 651
207d8b21 652ConfigEntry * Config::getEntry(const TCHAR *path)
ff175e91 653{
207d8b21
VP
654 const TCHAR *curr, *end;
655 TCHAR name[256];
656 ConfigEntry *entry = m_root;
ff175e91 657
def9a42c
VP
658 if ((path == NULL) || (*path != _T('/')))
659 return NULL;
ff175e91 660
def9a42c
VP
661 if (!_tcscmp(path, _T("/")))
662 return m_root;
ff175e91 663
207d8b21
VP
664 curr = path + 1;
665 while(entry != NULL)
666 {
667 end = _tcschr(curr, _T('/'));
668 if (end != NULL)
669 {
670 int len = min((int )(end - curr), 255);
671 _tcsncpy(name, curr, len);
672 name[len] = 0;
673 entry = entry->findEntry(name);
674 curr = end + 1;
675 }
676 else
677 {
678 return entry->findEntry(curr);
679 }
680 }
681 return NULL;
ff175e91
VK
682}
683
207d8b21
VP
684/*
685 * Create entry if does not exist, or return existing
686 * Will return NULL on error
687 */
8a0dffac
VK
688
689ConfigEntry *Config::createEntry(const TCHAR *path)
690{
207d8b21
VP
691 const TCHAR *curr, *end;
692 TCHAR name[256];
693 ConfigEntry *entry, *parent;
694
def9a42c
VP
695 if ((path == NULL) || (*path != _T('/')))
696 return NULL;
207d8b21 697
def9a42c
VP
698 if (!_tcscmp(path, _T("/")))
699 return m_root;
207d8b21
VP
700
701 curr = path + 1;
702 parent = m_root;
703 do
704 {
705 end = _tcschr(curr, _T('/'));
706 if (end != NULL)
707 {
708 int len = min((int )(end - curr), 255);
709 _tcsncpy(name, curr, len);
710 name[len] = 0;
711 entry = parent->findEntry(name);
712 curr = end + 1;
def9a42c
VP
713 if (entry == NULL)
714 entry = new ConfigEntry(name, parent, _T("<memory>"), 0, 0);
207d8b21
VP
715 parent = entry;
716 }
717 else
718 {
719 entry = parent->findEntry(curr);
def9a42c
VP
720 if (entry == NULL)
721 entry = new ConfigEntry(curr, parent, _T("<memory>"), 0, 0);
207d8b21 722 }
def9a42c
VP
723 }
724 while(end != NULL);
207d8b21 725 return entry;
8a0dffac
VK
726}
727
40ca2297
VK
728/**
729 * Delete entry
730 */
203e9d8a
VK
731void Config::deleteEntry(const TCHAR *path)
732{
207d8b21 733 ConfigEntry *entry = getEntry(path);
def9a42c
VP
734 if (entry == NULL)
735 return;
203e9d8a 736
207d8b21
VP
737 ConfigEntry *parent = entry->getParent();
738 if (parent == NULL) // root entry
739 return;
203e9d8a 740
207d8b21
VP
741 parent->unlinkEntry(entry);
742 delete entry;
203e9d8a
VK
743}
744
207d8b21
VP
745/*
746 * Set value
747 * Returns false on error (usually caused by incorrect path)
748 */
8a0dffac
VK
749
750bool Config::setValue(const TCHAR *path, const TCHAR *value)
751{
207d8b21 752 ConfigEntry *entry = createEntry(path);
def9a42c
VP
753 if (entry == NULL)
754 return false;
207d8b21
VP
755 entry->setValue(value);
756 return true;
8a0dffac
VK
757}
758
967893bb 759bool Config::setValue(const TCHAR *path, INT32 value)
8a0dffac 760{
207d8b21
VP
761 TCHAR buffer[32];
762 _sntprintf(buffer, 32, _T("%ld"), (long) value);
763 return setValue(path, buffer);
8a0dffac
VK
764}
765
967893bb 766bool Config::setValue(const TCHAR *path, UINT32 value)
8a0dffac 767{
207d8b21
VP
768 TCHAR buffer[32];
769 _sntprintf(buffer, 32, _T("%lu"), (unsigned long) value);
770 return setValue(path, buffer);
8a0dffac
VK
771}
772
773bool Config::setValue(const TCHAR *path, INT64 value)
774{
207d8b21
VP
775 TCHAR buffer[32];
776 _sntprintf(buffer, 32, INT64_FMT, value);
777 return setValue(path, buffer);
8a0dffac
VK
778}
779
967893bb 780bool Config::setValue(const TCHAR *path, UINT64 value)
8a0dffac 781{
207d8b21
VP
782 TCHAR buffer[32];
783 _sntprintf(buffer, 32, UINT64_FMT, value);
784 return setValue(path, buffer);
8a0dffac
VK
785}
786
787bool Config::setValue(const TCHAR *path, double value)
788{
207d8b21
VP
789 TCHAR buffer[32];
790 _sntprintf(buffer, 32, _T("%f"), value);
791 return setValue(path, buffer);
8a0dffac
VK
792}
793
203e9d8a
VK
794bool Config::setValue(const TCHAR *path, uuid_t value)
795{
207d8b21
VP
796 TCHAR buffer[64];
797 uuid_to_string(value, buffer);
798 return setValue(path, buffer);
203e9d8a
VK
799}
800
40ca2297
VK
801/**
802 * Find comment start in INI style config line
803 * Comment starts with # character, characters within double quotes ignored
804 */
def9a42c 805static TCHAR* FindComment(TCHAR *str)
e72e1acd 806{
207d8b21
VP
807 TCHAR *curr;
808 bool quotes;
e72e1acd 809
207d8b21
VP
810 for(curr = str, quotes = false; *curr != 0; curr++)
811 {
812 if (*curr == _T('"'))
813 {
814 quotes = !quotes;
815 }
816 else if ((*curr == _T('#')) && !quotes)
817 {
818 return curr;
819 }
820 }
821 return NULL;
e72e1acd
VK
822}
823
40ca2297
VK
824/**
825 * Load INI-style config
826 */
31e21b08 827bool Config::loadIniConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
8a5ba964 828{
207d8b21
VP
829 FILE *cfg;
830 TCHAR buffer[4096], *ptr;
831 ConfigEntry *currentSection;
832 int sourceLine = 0;
31e21b08 833 bool validConfig = true;
e6c91aac
VK
834
835 cfg = _tfopen(file, _T("r"));
836 if (cfg == NULL)
207d8b21
VP
837 {
838 error(_T("Cannot open file %s"), file);
839 return false;
840 }
e6c91aac 841
207d8b21
VP
842 currentSection = m_root->findEntry(defaultIniSection);
843 if (currentSection == NULL)
844 {
845 currentSection = new ConfigEntry(defaultIniSection, m_root, file, 0, 0);
846 }
e6c91aac
VK
847
848 while(!feof(cfg))
849 {
850 // Read line from file
851 buffer[0] = 0;
def9a42c
VP
852 if (_fgetts(buffer, 4095, cfg) == NULL)
853 break; // EOF or error
e6c91aac
VK
854 sourceLine++;
855 ptr = _tcschr(buffer, _T('\n'));
856 if (ptr != NULL)
207d8b21
VP
857 {
858 if (ptr != buffer)
859 {
def9a42c
VP
860 if (*(ptr - 1) == '\r')
861 ptr--;
207d8b21 862 }
e6c91aac 863 *ptr = 0;
207d8b21 864 }
e72e1acd 865 ptr = FindComment(buffer);
def9a42c
VP
866 if (ptr != NULL)
867 *ptr = 0;
e6c91aac
VK
868
869 StrStrip(buffer);
def9a42c
VP
870 if (buffer[0] == 0)
871 continue;
e6c91aac
VK
872
873 // Check if it's a section name
874 if ((buffer[0] == _T('*')) || (buffer[0] == _T('[')))
875 {
207d8b21
VP
876 if (buffer[0] == _T('['))
877 {
878 TCHAR *end = _tcschr(buffer, _T(']'));
def9a42c
VP
879 if (end != NULL)
880 *end = 0;
207d8b21
VP
881 }
882 currentSection = m_root->findEntry(&buffer[1]);
883 if (currentSection == NULL)
884 {
885 currentSection = new ConfigEntry(&buffer[1], m_root, file, sourceLine, 0);
886 }
e6c91aac
VK
887 }
888 else
889 {
890 // Divide on two parts at = sign
891 ptr = _tcschr(buffer, _T('='));
892 if (ptr == NULL)
893 {
894 error(_T("Syntax error in configuration file %s at line %d"), file, sourceLine);
31e21b08 895 validConfig = false;
e6c91aac
VK
896 continue;
897 }
898 *ptr = 0;
899 ptr++;
900 StrStrip(buffer);
901 StrStrip(ptr);
902
207d8b21
VP
903 ConfigEntry *entry = currentSection->findEntry(buffer);
904 if (entry == NULL)
905 {
906 entry = new ConfigEntry(buffer, currentSection, file, sourceLine, 0);
907 }
908 entry->addValue(ptr);
e6c91aac
VK
909 }
910 }
911 fclose(cfg);
207d8b21 912 return ignoreErrors || validConfig;
e6c91aac
VK
913}
914
40ca2297
VK
915/**
916 * Max stack depth for XML config
917 */
ff175e91
VK
918#define MAX_STACK_DEPTH 256
919
40ca2297
VK
920/**
921 * State information for XML parser
922 */
ff175e91 923typedef struct
e6c91aac 924{
207d8b21
VP
925 const char *topLevelTag;
926 XML_Parser parser;
927 Config *config;
928 const TCHAR *file;
929 int level;
930 ConfigEntry *stack[MAX_STACK_DEPTH];
931 String charData[MAX_STACK_DEPTH];
932 bool trimValue[MAX_STACK_DEPTH];
ff175e91
VK
933} XML_PARSER_STATE;
934
40ca2297
VK
935/**
936 * Element start handler for XML parser
937 */
ff175e91
VK
938static void StartElement(void *userData, const char *name, const char **attrs)
939{
207d8b21
VP
940 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
941
942 if (ps->level == 0)
943 {
944 if (!stricmp(name, ps->topLevelTag))
945 {
fff23fae
VK
946 ps->stack[ps->level] = ps->config->getEntry(_T("/"));
947 ps->charData[ps->level] = _T("");
948 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
949 ps->level++;
207d8b21
VP
950 }
951 else
952 {
953 ps->level = -1;
954 }
955 }
956 else if (ps->level > 0)
957 {
958 if (ps->level < MAX_STACK_DEPTH)
959 {
960 TCHAR entryName[MAX_PATH];
961
962 UINT32 id = XMLGetAttrUINT32(attrs, "id", 0);
ff175e91 963#ifdef UNICODE
207d8b21
VP
964 if (id != 0)
965 {
966 WCHAR wname[MAX_PATH];
ff175e91 967
207d8b21
VP
968 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);
969 wname[MAX_PATH - 1] = 0;
a67fa146 970#ifdef _WIN32
207d8b21 971 _snwprintf(entryName, MAX_PATH, L"%s#%u", wname, (unsigned int)id);
a67fa146 972#else
207d8b21 973 swprintf(entryName, MAX_PATH, L"%S#%u", wname, (unsigned int)id);
a67fa146 974#endif
207d8b21
VP
975 }
976 else
977 {
978 MultiByteToWideChar(CP_UTF8, 0, name, -1, entryName, MAX_PATH);
979 entryName[MAX_PATH - 1] = 0;
980 }
ff175e91 981#else
207d8b21
VP
982 if (id != 0)
983 snprintf(entryName, MAX_PATH, "%s#%u", name, (unsigned int) id);
984 else
985 nx_strncpy(entryName, name, MAX_PATH);
ff175e91 986#endif
207d8b21
VP
987 ps->stack[ps->level] = ps->stack[ps->level - 1]->findEntry(entryName);
988 if (ps->stack[ps->level] == NULL)
989 ps->stack[ps->level] = new ConfigEntry(entryName, ps->stack[ps->level - 1], ps->file,
990 XML_GetCurrentLineNumber(ps->parser), (int) id);
991 ps->charData[ps->level] = _T("");
992 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
993 ps->level++;
994 }
995 else
996 {
997 ps->level++;
998 }
999 }
e6c91aac
VK
1000}
1001
40ca2297
VK
1002/**
1003 * Element end handler for XML parser
1004 */
ff175e91
VK
1005static void EndElement(void *userData, const char *name)
1006{
fff23fae 1007 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
e6c91aac 1008
207d8b21
VP
1009 if (ps->level > MAX_STACK_DEPTH)
1010 {
1011 ps->level--;
1012 }
1013 else if (ps->level > 0)
1014 {
1015 ps->level--;
def9a42c
VP
1016 if (ps->trimValue[ps->level])
1017 ps->charData[ps->level].trim();
207d8b21
VP
1018 ps->stack[ps->level]->addValue(ps->charData[ps->level]);
1019 }
ff175e91 1020}
e6c91aac 1021
40ca2297
VK
1022/**
1023 * Data handler for XML parser
1024 */
ff175e91 1025static void CharData(void *userData, const XML_Char *s, int len)
e6c91aac 1026{
207d8b21 1027 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
e6c91aac 1028
def9a42c
VP
1029 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
1030 ps->charData[ps->level - 1].addMultiByteString(s, len, CP_UTF8);
ff175e91 1031}
e6c91aac 1032
40ca2297
VK
1033/**
1034 * Load config from XML in memory
1035 */
a65c1819
VK
1036bool Config::loadXmlConfigFromMemory(const char *xml, int xmlSize, const TCHAR *name, const char *topLevelTag)
1037{
207d8b21 1038 XML_PARSER_STATE state;
a65c1819 1039
207d8b21
VP
1040 XML_Parser parser = XML_ParserCreate(NULL);
1041 XML_SetUserData(parser, &state);
1042 XML_SetElementHandler(parser, StartElement, EndElement);
1043 XML_SetCharacterDataHandler(parser, CharData);
a65c1819 1044
207d8b21
VP
1045 state.topLevelTag = (topLevelTag != NULL) ? topLevelTag : "config";
1046 state.config = this;
1047 state.level = 0;
1048 state.parser = parser;
1049 state.file = (name != NULL) ? name : _T("<mem>");
a65c1819 1050
207d8b21
VP
1051 bool success = (XML_Parse(parser, xml, xmlSize, TRUE) != XML_STATUS_ERROR);
1052 if (!success)
1053 {
1054 error(_T("%hs at line %d"), XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
1055 }
1056 XML_ParserFree(parser);
1057 return success;
a65c1819
VK
1058}
1059
40ca2297
VK
1060/**
1061 * Load config from XML file
1062 */
a65c1819 1063bool Config::loadXmlConfig(const TCHAR *file, const char *topLevelTag)
ff175e91 1064{
207d8b21
VP
1065 BYTE *xml;
1066 UINT32 size;
1067 bool success;
ff175e91 1068
207d8b21
VP
1069 xml = LoadFile(file, &size);
1070 if (xml != NULL)
1071 {
1072 success = loadXmlConfigFromMemory((char *) xml, (int) size, file, topLevelTag);
1073 }
1074 else
1075 {
1076 success = false;
1077 }
ff175e91 1078
207d8b21 1079 return success;
ff175e91
VK
1080}
1081
40ca2297
VK
1082/**
1083 * Load config file with format auto detection
1084 */
31e21b08 1085bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
ff175e91 1086{
279cb65b
VK
1087 NX_STAT_STRUCT fileStats;
1088 int ret = CALL_STAT(file, &fileStats);
1089 if (ret != 0)
207d8b21 1090 {
279cb65b 1091 error(_T("Could not process \"%s\"!"), file);
207d8b21
VP
1092 return false;
1093 }
4a753d31 1094
207d8b21
VP
1095 if (!S_ISREG(fileStats.st_mode))
1096 {
279cb65b 1097 error(_T("\"%s\" is not a file!"), file);
207d8b21
VP
1098 return false;
1099 }
ff175e91 1100
279cb65b 1101 FILE *f = _tfopen(file, _T("r"));
207d8b21
VP
1102 if (f == NULL)
1103 {
1104 error(_T("Cannot open file %s"), file);
1105 return false;
1106 }
ff175e91 1107
207d8b21 1108 // Skip all space/newline characters
279cb65b 1109 int ch;
207d8b21
VP
1110 do
1111 {
1112 ch = fgetc(f);
def9a42c
VP
1113 }
1114 while(isspace(ch));
ff175e91 1115
207d8b21 1116 fclose(f);
ff175e91 1117
207d8b21
VP
1118 // If first non-space character is < assume XML format, otherwise assume INI format
1119 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection, ignoreErrors);
5039dede 1120}
abf35d58 1121
207d8b21
VP
1122/*
1123 * Load all files in given directory
1124 */
abf35d58 1125
31e21b08 1126bool Config::loadConfigDirectory(const TCHAR *path, const TCHAR *defaultIniSection, bool ignoreErrors)
abf35d58 1127{
207d8b21 1128 _TDIR *dir;
5ea2db30 1129 struct _tdirent *file;
207d8b21
VP
1130 TCHAR fileName[MAX_PATH];
1131 bool success;
abf35d58 1132
5ea2db30 1133 dir = _topendir(path);
abf35d58
VK
1134 if (dir != NULL)
1135 {
207d8b21 1136 success = true;
abf35d58
VK
1137 while(1)
1138 {
5ea2db30 1139 file = _treaddir(dir);
def9a42c
VP
1140 if (file == NULL)
1141 break;
abf35d58 1142
def9a42c
VP
1143 if (!_tcscmp(file->d_name, _T(".")) || !_tcscmp(file->d_name, _T("..")))
1144 continue;
207d8b21
VP
1145
1146 size_t len = _tcslen(path) + _tcslen(file->d_name) + 2;
def9a42c
VP
1147 if (len > MAX_PATH)
1148 continue; // Full file name is too long
abf35d58
VK
1149
1150 _tcscpy(fileName, path);
1151 _tcscat(fileName, FS_PATH_SEPARATOR);
5ea2db30 1152 _tcscat(fileName, file->d_name);
abf35d58 1153
207d8b21 1154 if (!loadConfig(fileName, defaultIniSection, ignoreErrors))
31e21b08
AK
1155 {
1156 success = false;
1157 }
abf35d58 1158 }
5ea2db30 1159 _tclosedir(dir);
abf35d58 1160 }
207d8b21
VP
1161 else
1162 {
1163 success = false;
1164 }
abf35d58 1165
207d8b21 1166 return success;
abf35d58
VK
1167}
1168
207d8b21
VP
1169/*
1170 * Print config to output stream
1171 */
abf35d58
VK
1172
1173void Config::print(FILE *file)
1174{
def9a42c
VP
1175 if (m_root != NULL)
1176 m_root->print(file, 0);
abf35d58 1177}
8a0dffac 1178
6603931b
VK
1179/**
1180 * Create XML from config
1181 */
8a0dffac
VK
1182String Config::createXml()
1183{
207d8b21
VP
1184 String xml;
1185 m_root->createXml(xml);
1186 return xml;
8a0dffac 1187}