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