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