54e1642b3f1c8c9647853fed12fe96e278131eb7
[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 }
948 else
949 {
950 ps->level = -1;
951 }
952 }
953 else if (ps->level > 0)
954 {
955 if (ps->level < MAX_STACK_DEPTH)
956 {
957 TCHAR entryName[MAX_PATH];
958
959 UINT32 id = XMLGetAttrUINT32(attrs, "id", 0);
960 #ifdef UNICODE
961 if (id != 0)
962 {
963 WCHAR wname[MAX_PATH];
964
965 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);
966 wname[MAX_PATH - 1] = 0;
967 #ifdef _WIN32
968 _snwprintf(entryName, MAX_PATH, L"%s#%u", wname, (unsigned int)id);
969 #else
970 swprintf(entryName, MAX_PATH, L"%S#%u", wname, (unsigned int)id);
971 #endif
972 }
973 else
974 {
975 MultiByteToWideChar(CP_UTF8, 0, name, -1, entryName, MAX_PATH);
976 entryName[MAX_PATH - 1] = 0;
977 }
978 #else
979 if (id != 0)
980 snprintf(entryName, MAX_PATH, "%s#%u", name, (unsigned int) id);
981 else
982 nx_strncpy(entryName, name, MAX_PATH);
983 #endif
984 ps->stack[ps->level] = ps->stack[ps->level - 1]->findEntry(entryName);
985 if (ps->stack[ps->level] == NULL)
986 ps->stack[ps->level] = new ConfigEntry(entryName, ps->stack[ps->level - 1], ps->file,
987 XML_GetCurrentLineNumber(ps->parser), (int) id);
988 ps->charData[ps->level] = _T("");
989 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
990 ps->level++;
991 }
992 else
993 {
994 ps->level++;
995 }
996 }
997 }
998
999 /**
1000 * Element end handler for XML parser
1001 */
1002 static void EndElement(void *userData, const char *name)
1003 {
1004 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
1005
1006 if (ps->level > MAX_STACK_DEPTH)
1007 {
1008 ps->level--;
1009 }
1010 else if (ps->level > 0)
1011 {
1012 ps->level--;
1013 if (ps->trimValue[ps->level])
1014 ps->charData[ps->level].trim();
1015 ps->stack[ps->level]->addValue(ps->charData[ps->level]);
1016 }
1017 }
1018
1019 /**
1020 * Data handler for XML parser
1021 */
1022 static void CharData(void *userData, const XML_Char *s, int len)
1023 {
1024 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
1025
1026 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
1027 ps->charData[ps->level - 1].addMultiByteString(s, len, CP_UTF8);
1028 }
1029
1030 /**
1031 * Load config from XML in memory
1032 */
1033 bool Config::loadXmlConfigFromMemory(const char *xml, int xmlSize, const TCHAR *name, const char *topLevelTag)
1034 {
1035 XML_PARSER_STATE state;
1036
1037 XML_Parser parser = XML_ParserCreate(NULL);
1038 XML_SetUserData(parser, &state);
1039 XML_SetElementHandler(parser, StartElement, EndElement);
1040 XML_SetCharacterDataHandler(parser, CharData);
1041
1042 state.topLevelTag = (topLevelTag != NULL) ? topLevelTag : "config";
1043 state.config = this;
1044 state.level = 0;
1045 state.parser = parser;
1046 state.file = (name != NULL) ? name : _T("<mem>");
1047
1048 bool success = (XML_Parse(parser, xml, xmlSize, TRUE) != XML_STATUS_ERROR);
1049 if (!success)
1050 {
1051 error(_T("%hs at line %d"), XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
1052 }
1053 XML_ParserFree(parser);
1054 return success;
1055 }
1056
1057 /**
1058 * Load config from XML file
1059 */
1060 bool Config::loadXmlConfig(const TCHAR *file, const char *topLevelTag)
1061 {
1062 BYTE *xml;
1063 UINT32 size;
1064 bool success;
1065
1066 xml = LoadFile(file, &size);
1067 if (xml != NULL)
1068 {
1069 success = loadXmlConfigFromMemory((char *) xml, (int) size, file, topLevelTag);
1070 }
1071 else
1072 {
1073 success = false;
1074 }
1075
1076 return success;
1077 }
1078
1079 /**
1080 * Load config file with format auto detection
1081 */
1082 bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
1083 {
1084 NX_STAT_STRUCT fileStats;
1085 int ret = CALL_STAT(file, &fileStats);
1086 if (ret != 0)
1087 {
1088 error(_T("Could not process \"%s\"!"), file);
1089 return false;
1090 }
1091
1092 if (!S_ISREG(fileStats.st_mode))
1093 {
1094 error(_T("\"%s\" is not a file!"), file);
1095 return false;
1096 }
1097
1098 FILE *f = _tfopen(file, _T("r"));
1099 if (f == NULL)
1100 {
1101 error(_T("Cannot open file %s"), file);
1102 return false;
1103 }
1104
1105 // Skip all space/newline characters
1106 int ch;
1107 do
1108 {
1109 ch = fgetc(f);
1110 }
1111 while(isspace(ch));
1112
1113 fclose(f);
1114
1115 // If first non-space character is < assume XML format, otherwise assume INI format
1116 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection, ignoreErrors);
1117 }
1118
1119 /*
1120 * Load all files in given directory
1121 */
1122
1123 bool Config::loadConfigDirectory(const TCHAR *path, const TCHAR *defaultIniSection, bool ignoreErrors)
1124 {
1125 _TDIR *dir;
1126 struct _tdirent *file;
1127 TCHAR fileName[MAX_PATH];
1128 bool success;
1129
1130 dir = _topendir(path);
1131 if (dir != NULL)
1132 {
1133 success = true;
1134 while(1)
1135 {
1136 file = _treaddir(dir);
1137 if (file == NULL)
1138 break;
1139
1140 if (!_tcscmp(file->d_name, _T(".")) || !_tcscmp(file->d_name, _T("..")))
1141 continue;
1142
1143 size_t len = _tcslen(path) + _tcslen(file->d_name) + 2;
1144 if (len > MAX_PATH)
1145 continue; // Full file name is too long
1146
1147 _tcscpy(fileName, path);
1148 _tcscat(fileName, FS_PATH_SEPARATOR);
1149 _tcscat(fileName, file->d_name);
1150
1151 if (!loadConfig(fileName, defaultIniSection, ignoreErrors))
1152 {
1153 success = false;
1154 }
1155 }
1156 _tclosedir(dir);
1157 }
1158 else
1159 {
1160 success = false;
1161 }
1162
1163 return success;
1164 }
1165
1166 /*
1167 * Print config to output stream
1168 */
1169
1170 void Config::print(FILE *file)
1171 {
1172 if (m_root != NULL)
1173 m_root->print(file, 0);
1174 }
1175
1176 /**
1177 * Create XML from config
1178 */
1179 String Config::createXml()
1180 {
1181 String xml;
1182 m_root->createXml(xml);
1183 return xml;
1184 }