fixed memory leaks
[public/netxms.git] / src / libnetxms / config.cpp
1 /*
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 **/
23
24 #include "libnetxms.h"
25 #include <nxconfig.h>
26 #include <expat.h>
27 #include <nxstat.h>
28
29 /**
30 * Constructor for config entry
31 */
32 ConfigEntry::ConfigEntry(const TCHAR *name, ConfigEntry *parent, const TCHAR *file, int line, int id)
33 {
34 m_name = _tcsdup(CHECK_NULL(name));
35 m_first = NULL;
36 m_last = NULL;
37 m_next = NULL;
38 if (parent != NULL)
39 parent->addEntry(this);
40 m_valueCount = 0;
41 m_values = NULL;
42 m_file = _tcsdup(CHECK_NULL(file));
43 m_line = line;
44 m_id = id;
45 }
46
47 /**
48 * Destructor for config entry
49 */
50 ConfigEntry::~ConfigEntry()
51 {
52 ConfigEntry *entry, *next;
53
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);
61
62 for(int i = 0; i < m_valueCount; i++)
63 safe_free(m_values[i]);
64 safe_free(m_values);
65 }
66
67 /**
68 * Set entry's name
69 */
70 void ConfigEntry::setName(const TCHAR *name)
71 {
72 safe_free(m_name);
73 m_name = _tcsdup(CHECK_NULL(name));
74 }
75
76 /**
77 * Find entry by name
78 */
79 ConfigEntry* ConfigEntry::findEntry(const TCHAR *name)
80 {
81 ConfigEntry *e;
82
83 for(e = m_first; e != NULL; e = e->getNext())
84 if (!_tcsicmp(e->getName(), name))
85 return e;
86 return NULL;
87 }
88
89 /**
90 * Create (or find existing) subentry
91 */
92 ConfigEntry* ConfigEntry::createEntry(const TCHAR *name)
93 {
94 ConfigEntry *e;
95
96 for(e = m_first; e != NULL; e = e->getNext())
97 if (!_tcsicmp(e->getName(), name))
98 return e;
99
100 return new ConfigEntry(name, this, _T("<memory>"), 0, 0);
101 }
102
103 /**
104 * Add sub-entry
105 */
106 void ConfigEntry::addEntry(ConfigEntry *entry)
107 {
108 entry->m_parent = this;
109 entry->m_next = NULL;
110 if (m_last != NULL)
111 m_last->m_next = entry;
112 m_last = entry;
113 if (m_first == NULL)
114 m_first = entry;
115 }
116
117 /**
118 * Unlink subentry
119 */
120 void ConfigEntry::unlinkEntry(ConfigEntry *entry)
121 {
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 }
136 if (m_last == curr)
137 m_last = prev;
138 curr->m_next = NULL;
139 break;
140 }
141 prev = curr;
142 }
143 }
144
145 /**
146 * Get all subentries with names matched to mask
147 */
148 ConfigEntryList* ConfigEntry::getSubEntries(const TCHAR *mask)
149 {
150 ConfigEntry *e, **list = NULL;
151 int count = 0, allocated = 0;
152
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);
164 }
165
166 /**
167 * Get all subentries with names matched to mask ordered by id
168 */
169 ConfigEntryList* ConfigEntry::getOrderedSubEntries(const TCHAR *mask)
170 {
171 ConfigEntryList *list = getSubEntries(mask);
172 list->sortById();
173 return list;
174 }
175
176 /*
177 * Get value
178 */
179
180 const TCHAR* ConfigEntry::getValue(int index)
181 {
182 if ((index < 0) || (index >= m_valueCount))
183 return NULL;
184 return m_values[index];
185 }
186
187 INT32 ConfigEntry::getValueInt(int index, INT32 defaultValue)
188 {
189 const TCHAR *value = getValue(index);
190 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
191 }
192
193 UINT32 ConfigEntry::getValueUInt(int index, UINT32 defaultValue)
194 {
195 const TCHAR *value = getValue(index);
196 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
197 }
198
199 INT64 ConfigEntry::getValueInt64(int index, INT64 defaultValue)
200 {
201 const TCHAR *value = getValue(index);
202 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
203 }
204
205 UINT64 ConfigEntry::getValueUInt64(int index, UINT64 defaultValue)
206 {
207 const TCHAR *value = getValue(index);
208 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
209 }
210
211 bool ConfigEntry::getValueBoolean(int index, bool defaultValue)
212 {
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 }
223 }
224
225 bool ConfigEntry::getValueUUID(int index, uuid_t uuid)
226 {
227 const TCHAR *value = getValue(index);
228 if (value != NULL)
229 {
230 return uuid_parse(value, uuid) == 0;
231 }
232 return false;
233 }
234
235 /*
236 * Set value
237 */
238
239 void ConfigEntry::setValue(const TCHAR *value)
240 {
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);
246 }
247
248 /*
249 * Add value
250 */
251
252 void ConfigEntry::addValue(const TCHAR *value)
253 {
254 m_values = (TCHAR **) realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
255 m_values[m_valueCount] = _tcsdup(value);
256 m_valueCount++;
257 }
258
259 /*
260 * Get summary length of all values as if they was concatenated with separator character
261 */
262
263 int ConfigEntry::getConcatenatedValuesLength()
264 {
265 int i, len;
266
267 if (m_valueCount < 1)
268 return 0;
269
270 for(i = 0, len = 0; i < m_valueCount; i++)
271 len += (int) _tcslen(m_values[i]);
272 return len + m_valueCount;
273 }
274
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 */
283 const TCHAR* ConfigEntry::getSubEntryValue(const TCHAR *name, int index, const TCHAR *defaultValue)
284 {
285 ConfigEntry *e = findEntry(name);
286 if (e == NULL)
287 return defaultValue;
288 const TCHAR *value = e->getValue(index);
289 return (value != NULL) ? value : defaultValue;
290 }
291
292 INT32 ConfigEntry::getSubEntryValueInt(const TCHAR *name, int index, INT32 defaultValue)
293 {
294 const TCHAR *value = getSubEntryValue(name, index);
295 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
296 }
297
298 UINT32 ConfigEntry::getSubEntryValueUInt(const TCHAR *name, int index, UINT32 defaultValue)
299 {
300 const TCHAR *value = getSubEntryValue(name, index);
301 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
302 }
303
304 INT64 ConfigEntry::getSubEntryValueInt64(const TCHAR *name, int index, INT64 defaultValue)
305 {
306 const TCHAR *value = getSubEntryValue(name, index);
307 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
308 }
309
310 UINT64 ConfigEntry::getSubEntryValueUInt64(const TCHAR *name, int index, UINT64 defaultValue)
311 {
312 const TCHAR *value = getSubEntryValue(name, index);
313 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
314 }
315
316 bool ConfigEntry::getSubEntryValueBoolean(const TCHAR *name, int index, bool defaultValue)
317 {
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 }
328 }
329
330 bool ConfigEntry::getSubEntryValueUUID(const TCHAR *name, uuid_t uuid, int index)
331 {
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 }
341 }
342
343 /**
344 * Print config entry
345 */
346 void ConfigEntry::print(FILE *file, int level)
347 {
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);
351
352 for(int i = 0, len = 0; i < m_valueCount; i++)
353 _tprintf(_T("%*svalue: %s\n"), level * 4 + 2, _T(""), m_values[i]);
354 }
355
356 /**
357 * Create XML element(s) from config entry
358 */
359 void ConfigEntry::createXml(String &xml, int level)
360 {
361 TCHAR *name = _tcsdup(m_name);
362 TCHAR *ptr = _tcschr(name, _T('#'));
363 if (ptr != NULL)
364 *ptr = 0;
365
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);
370
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 }
378
379 if (m_valueCount > 0)
380 xml.addDynamicString(EscapeStringForXML(m_values[0], -1));
381 xml.addFormattedString(_T("</%s>\n"), name);
382
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 }
392
393 free(name);
394 }
395
396 /**
397 * Comparator for ConfigEntryList::sortById()
398 */
399 static int CompareById(const void *p1, const void *p2)
400 {
401 ConfigEntry *e1 = *((ConfigEntry **) p1);
402 ConfigEntry *e2 = *((ConfigEntry **) p2);
403 return e1->getId() - e2->getId();
404 }
405
406 /**
407 * Sort entry list
408 */
409 void ConfigEntryList::sortById()
410 {
411 qsort(m_list, m_size, sizeof(ConfigEntry *), CompareById);
412 }
413
414 /**
415 * Constructor for config
416 */
417 Config::Config()
418 {
419 m_root = new ConfigEntry(_T("[root]"), NULL, NULL, 0, 0);
420 m_errorCount = 0;
421 m_mutex = MutexCreate();
422 }
423
424 /**
425 * Destructor
426 */
427 Config::~Config()
428 {
429 delete m_root;
430 MutexDestroy(m_mutex);
431 }
432
433 /**
434 * Default error handler
435 */
436 void Config::onError(const TCHAR *errorMessage)
437 {
438 _ftprintf(stderr, _T("%s\n"), errorMessage);
439 }
440
441 /**
442 * Report error
443 */
444 void Config::error(const TCHAR *format, ...)
445 {
446 va_list args;
447 TCHAR buffer[4096];
448
449 m_errorCount++;
450 va_start(args, format);
451 _vsntprintf(buffer, 4096, format, args);
452 va_end(args);
453 onError(buffer);
454 }
455
456 /**
457 * Parse configuration template (emulation of old NxLoadConfig() API)
458 */
459 bool Config::parseTemplate(const TCHAR *section, NX_CFG_TEMPLATE *cfgTemplate)
460 {
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
470 for(i = 0; cfgTemplate[i].type != CT_END_OF_LIST; i++)
471 {
472 nx_strncpy(&name[pos], cfgTemplate[i].token, MAX_PATH - pos);
473 entry = getEntry(name);
474 if (entry != NULL)
475 {
476 const TCHAR *value = CHECK_NULL(entry->getValue(entry->getValueCount() - 1));
477 switch (cfgTemplate[i].type)
478 {
479 case CT_LONG:
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);
486 if (*eptr != 0)
487 {
488 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
489 }
490 break;
491 case CT_WORD:
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);
498 if (*eptr != 0)
499 {
500 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
501 }
502 break;
503 case CT_BOOLEAN:
504 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
505 || !_tcsicmp(value, _T("1")))
506 {
507 *((UINT32 *)cfgTemplate[i].buffer) |= cfgTemplate[i].bufferSize;
508 }
509 else
510 {
511 *((UINT32 *)cfgTemplate[i].buffer) &= ~(cfgTemplate[i].bufferSize);
512 }
513 break;
514 case CT_STRING:
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);
521 break;
522 case CT_MB_STRING:
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 }
528 #ifdef UNICODE
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);
531 #else
532 nx_strncpy((TCHAR *)cfgTemplate[i].buffer, value, cfgTemplate[i].bufferSize);
533 #endif
534 break;
535 case CT_STRING_LIST:
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++)
538 {
539 _tcscpy(curr, entry->getValue(j));
540 curr += _tcslen(curr);
541 *curr = cfgTemplate[i].separator;
542 curr++;
543 }
544 *curr = 0;
545 break;
546 case CT_IGNORE:
547 break;
548 default:
549 break;
550 }
551 }
552 }
553
554 return (m_errorCount - initialErrorCount) == 0;
555 }
556
557 /**
558 * Get value
559 */
560 const TCHAR * Config::getValue(const TCHAR *path, const TCHAR *defaultValue)
561 {
562 const TCHAR *value;
563 ConfigEntry *entry = getEntry(path);
564 if (entry != NULL)
565 {
566 value = entry->getValue();
567 if (value == NULL)
568 value = defaultValue;
569 }
570 else
571 {
572 value = defaultValue;
573 }
574 return value;
575 }
576
577 INT32 Config::getValueInt(const TCHAR *path, INT32 defaultValue)
578 {
579 const TCHAR *value = getValue(path);
580 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
581 }
582
583 UINT32 Config::getValueUInt(const TCHAR *path, UINT32 defaultValue)
584 {
585 const TCHAR *value = getValue(path);
586 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
587 }
588
589 INT64 Config::getValueInt64(const TCHAR *path, INT64 defaultValue)
590 {
591 const TCHAR *value = getValue(path);
592 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
593 }
594
595 UINT64 Config::getValueUInt64(const TCHAR *path, UINT64 defaultValue)
596 {
597 const TCHAR *value = getValue(path);
598 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
599 }
600
601 bool Config::getValueBoolean(const TCHAR *path, bool defaultValue)
602 {
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 }
613 }
614
615 bool Config::getValueUUID(const TCHAR *path, uuid_t uuid)
616 {
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 }
626 }
627
628 /*
629 * Get subentries
630 */
631
632 ConfigEntryList * Config::getSubEntries(const TCHAR *path, const TCHAR *mask)
633 {
634 ConfigEntry *entry = getEntry(path);
635 return (entry != NULL) ? entry->getSubEntries(mask) : NULL;
636 }
637
638 /*
639 * Get subentries ordered by id
640 */
641
642 ConfigEntryList * Config::getOrderedSubEntries(const TCHAR *path, const TCHAR *mask)
643 {
644 ConfigEntry *entry = getEntry(path);
645 return (entry != NULL) ? entry->getOrderedSubEntries(mask) : NULL;
646 }
647
648 /*
649 * Get entry
650 */
651
652 ConfigEntry * Config::getEntry(const TCHAR *path)
653 {
654 const TCHAR *curr, *end;
655 TCHAR name[256];
656 ConfigEntry *entry = m_root;
657
658 if ((path == NULL) || (*path != _T('/')))
659 return NULL;
660
661 if (!_tcscmp(path, _T("/")))
662 return m_root;
663
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;
682 }
683
684 /*
685 * Create entry if does not exist, or return existing
686 * Will return NULL on error
687 */
688
689 ConfigEntry *Config::createEntry(const TCHAR *path)
690 {
691 const TCHAR *curr, *end;
692 TCHAR name[256];
693 ConfigEntry *entry, *parent;
694
695 if ((path == NULL) || (*path != _T('/')))
696 return NULL;
697
698 if (!_tcscmp(path, _T("/")))
699 return m_root;
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;
713 if (entry == NULL)
714 entry = new ConfigEntry(name, parent, _T("<memory>"), 0, 0);
715 parent = entry;
716 }
717 else
718 {
719 entry = parent->findEntry(curr);
720 if (entry == NULL)
721 entry = new ConfigEntry(curr, parent, _T("<memory>"), 0, 0);
722 }
723 }
724 while(end != NULL);
725 return entry;
726 }
727
728 /**
729 * Delete entry
730 */
731 void Config::deleteEntry(const TCHAR *path)
732 {
733 ConfigEntry *entry = getEntry(path);
734 if (entry == NULL)
735 return;
736
737 ConfigEntry *parent = entry->getParent();
738 if (parent == NULL) // root entry
739 return;
740
741 parent->unlinkEntry(entry);
742 delete entry;
743 }
744
745 /*
746 * Set value
747 * Returns false on error (usually caused by incorrect path)
748 */
749
750 bool Config::setValue(const TCHAR *path, const TCHAR *value)
751 {
752 ConfigEntry *entry = createEntry(path);
753 if (entry == NULL)
754 return false;
755 entry->setValue(value);
756 return true;
757 }
758
759 bool Config::setValue(const TCHAR *path, INT32 value)
760 {
761 TCHAR buffer[32];
762 _sntprintf(buffer, 32, _T("%ld"), (long) value);
763 return setValue(path, buffer);
764 }
765
766 bool Config::setValue(const TCHAR *path, UINT32 value)
767 {
768 TCHAR buffer[32];
769 _sntprintf(buffer, 32, _T("%lu"), (unsigned long) value);
770 return setValue(path, buffer);
771 }
772
773 bool Config::setValue(const TCHAR *path, INT64 value)
774 {
775 TCHAR buffer[32];
776 _sntprintf(buffer, 32, INT64_FMT, value);
777 return setValue(path, buffer);
778 }
779
780 bool Config::setValue(const TCHAR *path, UINT64 value)
781 {
782 TCHAR buffer[32];
783 _sntprintf(buffer, 32, UINT64_FMT, value);
784 return setValue(path, buffer);
785 }
786
787 bool Config::setValue(const TCHAR *path, double value)
788 {
789 TCHAR buffer[32];
790 _sntprintf(buffer, 32, _T("%f"), value);
791 return setValue(path, buffer);
792 }
793
794 bool Config::setValue(const TCHAR *path, uuid_t value)
795 {
796 TCHAR buffer[64];
797 uuid_to_string(value, buffer);
798 return setValue(path, buffer);
799 }
800
801 /**
802 * Find comment start in INI style config line
803 * Comment starts with # character, characters within double quotes ignored
804 */
805 static TCHAR* FindComment(TCHAR *str)
806 {
807 TCHAR *curr;
808 bool quotes;
809
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;
822 }
823
824 /**
825 * Load INI-style config
826 */
827 bool Config::loadIniConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
828 {
829 FILE *cfg;
830 TCHAR buffer[4096], *ptr;
831 ConfigEntry *currentSection;
832 int sourceLine = 0;
833 bool validConfig = true;
834
835 cfg = _tfopen(file, _T("r"));
836 if (cfg == NULL)
837 {
838 error(_T("Cannot open file %s"), file);
839 return false;
840 }
841
842 currentSection = m_root->findEntry(defaultIniSection);
843 if (currentSection == NULL)
844 {
845 currentSection = new ConfigEntry(defaultIniSection, m_root, file, 0, 0);
846 }
847
848 while(!feof(cfg))
849 {
850 // Read line from file
851 buffer[0] = 0;
852 if (_fgetts(buffer, 4095, cfg) == NULL)
853 break; // EOF or error
854 sourceLine++;
855 ptr = _tcschr(buffer, _T('\n'));
856 if (ptr != NULL)
857 {
858 if (ptr != buffer)
859 {
860 if (*(ptr - 1) == '\r')
861 ptr--;
862 }
863 *ptr = 0;
864 }
865 ptr = FindComment(buffer);
866 if (ptr != NULL)
867 *ptr = 0;
868
869 StrStrip(buffer);
870 if (buffer[0] == 0)
871 continue;
872
873 // Check if it's a section name
874 if ((buffer[0] == _T('*')) || (buffer[0] == _T('[')))
875 {
876 if (buffer[0] == _T('['))
877 {
878 TCHAR *end = _tcschr(buffer, _T(']'));
879 if (end != NULL)
880 *end = 0;
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 }
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);
895 validConfig = false;
896 continue;
897 }
898 *ptr = 0;
899 ptr++;
900 StrStrip(buffer);
901 StrStrip(ptr);
902
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);
909 }
910 }
911 fclose(cfg);
912 return ignoreErrors || validConfig;
913 }
914
915 /**
916 * Max stack depth for XML config
917 */
918 #define MAX_STACK_DEPTH 256
919
920 /**
921 * State information for XML parser
922 */
923 typedef struct
924 {
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];
933 } XML_PARSER_STATE;
934
935 /**
936 * Element start handler for XML parser
937 */
938 static void StartElement(void *userData, const char *name, const char **attrs)
939 {
940 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
941
942 if (ps->level == 0)
943 {
944 if (!stricmp(name, ps->topLevelTag))
945 {
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++;
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);
963 #ifdef UNICODE
964 if (id != 0)
965 {
966 WCHAR wname[MAX_PATH];
967
968 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);
969 wname[MAX_PATH - 1] = 0;
970 #ifdef _WIN32
971 _snwprintf(entryName, MAX_PATH, L"%s#%u", wname, (unsigned int)id);
972 #else
973 swprintf(entryName, MAX_PATH, L"%S#%u", wname, (unsigned int)id);
974 #endif
975 }
976 else
977 {
978 MultiByteToWideChar(CP_UTF8, 0, name, -1, entryName, MAX_PATH);
979 entryName[MAX_PATH - 1] = 0;
980 }
981 #else
982 if (id != 0)
983 snprintf(entryName, MAX_PATH, "%s#%u", name, (unsigned int) id);
984 else
985 nx_strncpy(entryName, name, MAX_PATH);
986 #endif
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 }
1000 }
1001
1002 /**
1003 * Element end handler for XML parser
1004 */
1005 static void EndElement(void *userData, const char *name)
1006 {
1007 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
1008
1009 if (ps->level > MAX_STACK_DEPTH)
1010 {
1011 ps->level--;
1012 }
1013 else if (ps->level > 0)
1014 {
1015 ps->level--;
1016 if (ps->trimValue[ps->level])
1017 ps->charData[ps->level].trim();
1018 ps->stack[ps->level]->addValue(ps->charData[ps->level]);
1019 }
1020 }
1021
1022 /**
1023 * Data handler for XML parser
1024 */
1025 static void CharData(void *userData, const XML_Char *s, int len)
1026 {
1027 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
1028
1029 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
1030 ps->charData[ps->level - 1].addMultiByteString(s, len, CP_UTF8);
1031 }
1032
1033 /**
1034 * Load config from XML in memory
1035 */
1036 bool Config::loadXmlConfigFromMemory(const char *xml, int xmlSize, const TCHAR *name, const char *topLevelTag)
1037 {
1038 XML_PARSER_STATE state;
1039
1040 XML_Parser parser = XML_ParserCreate(NULL);
1041 XML_SetUserData(parser, &state);
1042 XML_SetElementHandler(parser, StartElement, EndElement);
1043 XML_SetCharacterDataHandler(parser, CharData);
1044
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>");
1050
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;
1058 }
1059
1060 /**
1061 * Load config from XML file
1062 */
1063 bool Config::loadXmlConfig(const TCHAR *file, const char *topLevelTag)
1064 {
1065 BYTE *xml;
1066 UINT32 size;
1067 bool success;
1068
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 }
1078
1079 return success;
1080 }
1081
1082 /**
1083 * Load config file with format auto detection
1084 */
1085 bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
1086 {
1087 NX_STAT_STRUCT fileStats;
1088 int ret = CALL_STAT(file, &fileStats);
1089 if (ret != 0)
1090 {
1091 error(_T("Could not process \"%s\"!"), file);
1092 return false;
1093 }
1094
1095 if (!S_ISREG(fileStats.st_mode))
1096 {
1097 error(_T("\"%s\" is not a file!"), file);
1098 return false;
1099 }
1100
1101 FILE *f = _tfopen(file, _T("r"));
1102 if (f == NULL)
1103 {
1104 error(_T("Cannot open file %s"), file);
1105 return false;
1106 }
1107
1108 // Skip all space/newline characters
1109 int ch;
1110 do
1111 {
1112 ch = fgetc(f);
1113 }
1114 while(isspace(ch));
1115
1116 fclose(f);
1117
1118 // If first non-space character is < assume XML format, otherwise assume INI format
1119 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection, ignoreErrors);
1120 }
1121
1122 /*
1123 * Load all files in given directory
1124 */
1125
1126 bool Config::loadConfigDirectory(const TCHAR *path, const TCHAR *defaultIniSection, bool ignoreErrors)
1127 {
1128 _TDIR *dir;
1129 struct _tdirent *file;
1130 TCHAR fileName[MAX_PATH];
1131 bool success;
1132
1133 dir = _topendir(path);
1134 if (dir != NULL)
1135 {
1136 success = true;
1137 while(1)
1138 {
1139 file = _treaddir(dir);
1140 if (file == NULL)
1141 break;
1142
1143 if (!_tcscmp(file->d_name, _T(".")) || !_tcscmp(file->d_name, _T("..")))
1144 continue;
1145
1146 size_t len = _tcslen(path) + _tcslen(file->d_name) + 2;
1147 if (len > MAX_PATH)
1148 continue; // Full file name is too long
1149
1150 _tcscpy(fileName, path);
1151 _tcscat(fileName, FS_PATH_SEPARATOR);
1152 _tcscat(fileName, file->d_name);
1153
1154 if (!loadConfig(fileName, defaultIniSection, ignoreErrors))
1155 {
1156 success = false;
1157 }
1158 }
1159 _tclosedir(dir);
1160 }
1161 else
1162 {
1163 success = false;
1164 }
1165
1166 return success;
1167 }
1168
1169 /*
1170 * Print config to output stream
1171 */
1172
1173 void Config::print(FILE *file)
1174 {
1175 if (m_root != NULL)
1176 m_root->print(file, 0);
1177 }
1178
1179 /**
1180 * Create XML from config
1181 */
1182 String Config::createXml()
1183 {
1184 String xml;
1185 m_root->createXml(xml);
1186 return xml;
1187 }