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