92619fcb359abbb9eb0d5b7d55c3f55ef10d5ff9
[public/netxms.git] / src / libnetxms / config.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** NetXMS Foundation Library
4 ** Copyright (C) 2003-2017 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 #if defined(_WIN32)
30 #define isatty _isatty
31 #elif !HAVE_ISATTY
32 #define isatty(x) (true)
33 #endif
34
35 /**
36 * Expand value
37 */
38 static TCHAR *ExpandValue(const TCHAR *src, bool xmlFormat, bool expandEnv)
39 {
40 if (xmlFormat && !expandEnv)
41 return _tcsdup(src);
42
43 size_t allocated = _tcslen(src) + 1;
44 TCHAR *buffer = (TCHAR *)malloc(allocated * sizeof(TCHAR));
45
46 const TCHAR *in = src;
47 TCHAR *out = buffer;
48 bool squotes = false;
49 bool dquotes = false;
50 if ((*in == _T('"')) && !xmlFormat)
51 {
52 dquotes = true;
53 in++;
54 }
55 else if ((*in == _T('\'')) && !xmlFormat)
56 {
57 squotes = true;
58 in++;
59 }
60
61 for(; *in != 0; in++)
62 {
63 if (squotes && (*in == _T('\'')))
64 {
65 // Single quoting characters are ignored in quoted string
66 if (*(in + 1) == _T('\''))
67 {
68 in++;
69 *out++ = _T('\'');
70 }
71 }
72 else if (dquotes && (*in == _T('"')))
73 {
74 // Double quoting characters are ignored in quoted string
75 if (*(in + 1) == _T('"'))
76 {
77 in++;
78 *out++ = _T('"');
79 }
80 }
81 else if (!squotes && expandEnv && (*in == _T('$')))
82 {
83 if (*(in + 1) == _T('{')) // environment string expansion
84 {
85 const TCHAR *end = _tcschr(in, _T('}'));
86 if (end != NULL)
87 {
88 in += 2;
89
90 TCHAR name[256];
91 size_t nameLen = end - in;
92 if (nameLen >= 256)
93 nameLen = 255;
94 memcpy(name, in, nameLen * sizeof(TCHAR));
95 name[nameLen] = 0;
96 const TCHAR *env = _tgetenv(name);
97 if ((env != NULL) && (*env != 0))
98 {
99 size_t len = _tcslen(env);
100 allocated += len;
101 size_t pos = out - buffer;
102 buffer = (TCHAR *)realloc(buffer, allocated * sizeof(TCHAR));
103 out = &buffer[pos];
104 memcpy(out, env, len * sizeof(TCHAR));
105 out += len;
106 }
107 in = end;
108 }
109 else
110 {
111 // unexpected end of line, ignore anything after ${
112 break;
113 }
114 }
115 else
116 {
117 *out++ = *in;
118 }
119 }
120 else
121 {
122 *out++ = *in;
123 }
124 }
125 *out = 0;
126 return buffer;
127 }
128
129 /**
130 * Constructor for config entry
131 */
132 ConfigEntry::ConfigEntry(const TCHAR *name, ConfigEntry *parent, const Config *owner, const TCHAR *file, int line, int id)
133 {
134 m_name = _tcsdup(CHECK_NULL(name));
135 m_first = NULL;
136 m_last = NULL;
137 m_next = NULL;
138 m_parent = NULL;
139 if (parent != NULL)
140 parent->addEntry(this);
141 m_valueCount = 0;
142 m_values = NULL;
143 m_file = _tcsdup(CHECK_NULL(file));
144 m_line = line;
145 m_id = id;
146 m_owner = owner;
147 }
148
149 /**
150 * Destructor for config entry
151 */
152 ConfigEntry::~ConfigEntry()
153 {
154 ConfigEntry *entry, *next;
155
156 for(entry = m_first; entry != NULL; entry = next)
157 {
158 next = entry->getNext();
159 delete entry;
160 }
161 free(m_name);
162 free(m_file);
163
164 for(int i = 0; i < m_valueCount; i++)
165 safe_free(m_values[i]);
166 free(m_values);
167 }
168
169 /**
170 * Set entry's name
171 */
172 void ConfigEntry::setName(const TCHAR *name)
173 {
174 safe_free(m_name);
175 m_name = _tcsdup(CHECK_NULL(name));
176 }
177
178 /**
179 * Find entry by name
180 */
181 ConfigEntry* ConfigEntry::findEntry(const TCHAR *name) const
182 {
183 const TCHAR *realName;
184 if (name[0] == _T('%'))
185 {
186 const TCHAR *alias = m_owner->getAlias(&name[1]);
187 if (alias == NULL)
188 return NULL;
189 realName = alias;
190 }
191 else
192 {
193 realName = name;
194 }
195 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
196 if (!_tcsicmp(e->getName(), realName))
197 return e;
198 return NULL;
199 }
200
201 /**
202 * Create (or find existing) subentry
203 */
204 ConfigEntry* ConfigEntry::createEntry(const TCHAR *name)
205 {
206 const TCHAR *realName;
207 if (name[0] == _T('%'))
208 {
209 const TCHAR *alias = m_owner->getAlias(&name[1]);
210 realName = (alias != NULL) ? alias : &name[1];
211 }
212 else
213 {
214 realName = name;
215 }
216
217 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
218 if (!_tcsicmp(e->getName(), realName))
219 return e;
220
221 return new ConfigEntry(realName, this, m_owner, _T("<memory>"), 0, 0);
222 }
223
224 /**
225 * Add sub-entry
226 */
227 void ConfigEntry::addEntry(ConfigEntry *entry)
228 {
229 entry->m_parent = this;
230 entry->m_next = NULL;
231 if (m_last != NULL)
232 m_last->m_next = entry;
233 m_last = entry;
234 if (m_first == NULL)
235 m_first = entry;
236 }
237
238 /**
239 * Unlink subentry
240 */
241 void ConfigEntry::unlinkEntry(ConfigEntry *entry)
242 {
243 ConfigEntry *curr, *prev;
244
245 for(curr = m_first, prev = NULL; curr != NULL; curr = curr->m_next)
246 {
247 if (curr == entry)
248 {
249 if (prev != NULL)
250 {
251 prev->m_next = curr->m_next;
252 }
253 else
254 {
255 m_first = curr->m_next;
256 }
257 if (m_last == curr)
258 m_last = prev;
259 curr->m_next = NULL;
260 break;
261 }
262 prev = curr;
263 }
264 }
265
266 /**
267 * Get all subentries with names matched to mask.
268 * Returned list ordered by ID
269 */
270 ObjectArray<ConfigEntry> *ConfigEntry::getSubEntries(const TCHAR *mask) const
271 {
272 ObjectArray<ConfigEntry> *list = new ObjectArray<ConfigEntry>(16, 16, false);
273 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
274 if ((mask == NULL) || MatchString(mask, e->getName(), FALSE))
275 {
276 list->add(e);
277 }
278 return list;
279 }
280
281 /**
282 * Comparator for ConfigEntryList::sortById()
283 */
284 static int CompareById(const ConfigEntry **e1, const ConfigEntry **e2)
285 {
286 return (*e1)->getId() - (*e2)->getId();
287 }
288
289 /**
290 * Get all subentries with names matched to mask ordered by id
291 */
292 ObjectArray<ConfigEntry> *ConfigEntry::getOrderedSubEntries(const TCHAR *mask) const
293 {
294 ObjectArray<ConfigEntry> *list = getSubEntries(mask);
295 list->sort(CompareById);
296 return list;
297 }
298
299 /**
300 * Get entry value
301 */
302 const TCHAR* ConfigEntry::getValue(int index)
303 {
304 if ((index < 0) || (index >= m_valueCount))
305 return NULL;
306 return m_values[index];
307 }
308
309 /**
310 * Get entry value as integer
311 */
312 INT32 ConfigEntry::getValueAsInt(int index, INT32 defaultValue)
313 {
314 const TCHAR *value = getValue(index);
315 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
316 }
317
318 /**
319 * Get entry value as unsigned integer
320 */
321 UINT32 ConfigEntry::getValueAsUInt(int index, UINT32 defaultValue)
322 {
323 const TCHAR *value = getValue(index);
324 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
325 }
326
327 /**
328 * Get entry value as 64 bit integer
329 */
330 INT64 ConfigEntry::getValueAsInt64(int index, INT64 defaultValue)
331 {
332 const TCHAR *value = getValue(index);
333 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
334 }
335
336 /**
337 * Get entry value as 64 bit unsigned integer
338 */
339 UINT64 ConfigEntry::getValueAsUInt64(int index, UINT64 defaultValue)
340 {
341 const TCHAR *value = getValue(index);
342 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
343 }
344
345 /**
346 * Get entry value as boolean
347 * (consider non-zero numerical value or strings "yes", "true", "on" as true)
348 */
349 bool ConfigEntry::getValueAsBoolean(int index, bool defaultValue)
350 {
351 const TCHAR *value = getValue(index);
352 if (value != NULL)
353 {
354 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
355 || (_tcstol(value, NULL, 0) != 0);
356 }
357 else
358 {
359 return defaultValue;
360 }
361 }
362
363 /**
364 * Get entry value as GUID
365 */
366 uuid ConfigEntry::getValueAsUUID(int index)
367 {
368 const TCHAR *value = getValue(index);
369 if (value != NULL)
370 {
371 return uuid::parse(value);
372 }
373 return uuid::NULL_UUID;
374 }
375
376 /**
377 * Set value (replace all existing values)
378 */
379 void ConfigEntry::setValue(const TCHAR *value)
380 {
381 for(int i = 0; i < m_valueCount; i++)
382 safe_free(m_values[i]);
383 m_valueCount = 1;
384 m_values = (TCHAR **) realloc(m_values, sizeof(TCHAR *));
385 m_values[0] = _tcsdup(value);
386 }
387
388 /**
389 * Add value
390 */
391 void ConfigEntry::addValue(const TCHAR *value)
392 {
393 m_values = (TCHAR **) realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
394 m_values[m_valueCount] = _tcsdup(value);
395 m_valueCount++;
396 }
397
398 /**
399 * Add value (pre-allocated string)
400 */
401 void ConfigEntry::addValuePreallocated(TCHAR *value)
402 {
403 m_values = (TCHAR **) realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
404 m_values[m_valueCount] = value;
405 m_valueCount++;
406 }
407
408 /**
409 * Get summary length of all values as if they was concatenated with separator character
410 */
411 int ConfigEntry::getConcatenatedValuesLength()
412 {
413 int i, len;
414
415 if (m_valueCount < 1)
416 return 0;
417
418 for(i = 0, len = 0; i < m_valueCount; i++)
419 len += (int) _tcslen(m_values[i]);
420 return len + m_valueCount;
421 }
422
423 /**
424 * Get value for given subentry
425 *
426 * @param name sub-entry name
427 * @param index sub-entry index (0 if omited)
428 * @param defaultValue value to be returned if requested sub-entry is missing (NULL if omited)
429 * @return sub-entry value or default value if requested sub-entry does not exist
430 */
431 const TCHAR* ConfigEntry::getSubEntryValue(const TCHAR *name, int index, const TCHAR *defaultValue) const
432 {
433 ConfigEntry *e = findEntry(name);
434 if (e == NULL)
435 return defaultValue;
436 const TCHAR *value = e->getValue(index);
437 return (value != NULL) ? value : defaultValue;
438 }
439
440 INT32 ConfigEntry::getSubEntryValueAsInt(const TCHAR *name, int index, INT32 defaultValue) const
441 {
442 const TCHAR *value = getSubEntryValue(name, index);
443 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
444 }
445
446 UINT32 ConfigEntry::getSubEntryValueAsUInt(const TCHAR *name, int index, UINT32 defaultValue) const
447 {
448 const TCHAR *value = getSubEntryValue(name, index);
449 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
450 }
451
452 INT64 ConfigEntry::getSubEntryValueAsInt64(const TCHAR *name, int index, INT64 defaultValue) const
453 {
454 const TCHAR *value = getSubEntryValue(name, index);
455 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
456 }
457
458 UINT64 ConfigEntry::getSubEntryValueAsUInt64(const TCHAR *name, int index, UINT64 defaultValue) const
459 {
460 const TCHAR *value = getSubEntryValue(name, index);
461 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
462 }
463
464 /**
465 * Get sub-entry value as boolean
466 * (consider non-zero numerical value or strings "yes", "true", "on" as true)
467 */
468 bool ConfigEntry::getSubEntryValueAsBoolean(const TCHAR *name, int index, bool defaultValue) const
469 {
470 const TCHAR *value = getSubEntryValue(name, index);
471 if (value != NULL)
472 {
473 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
474 }
475 else
476 {
477 return defaultValue;
478 }
479 }
480
481 /**
482 * Get sub-entry value as UUID
483 */
484 uuid ConfigEntry::getSubEntryValueAsUUID(const TCHAR *name, int index) const
485 {
486 const TCHAR *value = getSubEntryValue(name, index);
487 if (value != NULL)
488 {
489 return uuid::parse(value);
490 }
491 else
492 {
493 return uuid::NULL_UUID;
494 }
495 }
496
497 /**
498 * Get attribute as integer
499 */
500 INT32 ConfigEntry::getAttributeAsInt(const TCHAR *name, INT32 defaultValue) const
501 {
502 const TCHAR *value = getAttribute(name);
503 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
504 }
505
506 /**
507 * Get attribute as unsigned integer
508 */
509 UINT32 ConfigEntry::getAttributeAsUInt(const TCHAR *name, UINT32 defaultValue) const
510 {
511 const TCHAR *value = getAttribute(name);
512 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
513 }
514
515 /**
516 * Get attribute as 64 bit integer
517 */
518 INT64 ConfigEntry::getAttributeAsInt64(const TCHAR *name, INT64 defaultValue) const
519 {
520 const TCHAR *value = getAttribute(name);
521 return (value != NULL) ? _tcstoll(value, NULL, 0) : defaultValue;
522 }
523
524 /**
525 * Get attribute as unsigned 64 bit integer
526 */
527 UINT64 ConfigEntry::getAttributeAsUInt64(const TCHAR *name, UINT64 defaultValue) const
528 {
529 const TCHAR *value = getAttribute(name);
530 return (value != NULL) ? _tcstoull(value, NULL, 0) : defaultValue;
531 }
532
533 /**
534 * Get attribute as boolean
535 * (consider non-zero numerical value or strings "yes", "true", "on" as true)
536 */
537 bool ConfigEntry::getAttributeAsBoolean(const TCHAR *name, bool defaultValue) const
538 {
539 const TCHAR *value = getAttribute(name);
540 if (value != NULL)
541 {
542 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
543 }
544 else
545 {
546 return defaultValue;
547 }
548 }
549
550 /**
551 * Set attribute
552 */
553 void ConfigEntry::setAttribute(const TCHAR *name, INT32 value)
554 {
555 TCHAR buffer[64];
556 _sntprintf(buffer, 64, _T("%d"), (int)value);
557 setAttribute(name, buffer);
558 }
559
560 /**
561 * Set attribute
562 */
563 void ConfigEntry::setAttribute(const TCHAR *name, UINT32 value)
564 {
565 TCHAR buffer[64];
566 _sntprintf(buffer, 64, _T("%u"), (unsigned int)value);
567 setAttribute(name, buffer);
568 }
569
570 /**
571 * Set attribute
572 */
573 void ConfigEntry::setAttribute(const TCHAR *name, INT64 value)
574 {
575 TCHAR buffer[64];
576 _sntprintf(buffer, 64, INT64_FMT, value);
577 setAttribute(name, buffer);
578 }
579
580 /**
581 * Set attribute
582 */
583 void ConfigEntry::setAttribute(const TCHAR *name, UINT64 value)
584 {
585 TCHAR buffer[64];
586 _sntprintf(buffer, 64, UINT64_FMT, value);
587 setAttribute(name, buffer);
588 }
589
590 /**
591 * Set attribute
592 */
593 void ConfigEntry::setAttribute(const TCHAR *name, bool value)
594 {
595 setAttribute(name, value ? _T("true") : _T("false"));
596 }
597
598 /**
599 * Print config entry
600 */
601 void ConfigEntry::print(FILE *file, int level, TCHAR *prefix)
602 {
603 if (isatty(fileno(file)))
604 WriteToTerminalEx(_T("%s\x1b[32;1m%s\x1b[0m\n"), prefix, m_name);
605 else
606 _tprintf(_T("%s%s\n"), prefix, m_name);
607
608 if (level > 0)
609 {
610 prefix[(level - 1) * 4 + 1] = (m_next == NULL) ? _T(' ') : _T('|');
611 prefix[(level - 1) * 4 + 2] = _T(' ');
612 }
613
614 // Do not print empty values for non-leaf nodes
615 if ((m_first == NULL) || ((m_valueCount > 0) && (m_values[0][0] != 0)))
616 {
617 for(int i = 0; i < m_valueCount; i++)
618 {
619 if (isatty(fileno(file)))
620 WriteToTerminalEx(_T("%s value: \x1b[1m%s\x1b[0m\n"), prefix, m_values[i]);
621 else
622 _tprintf(_T("%s value: %s\n"), prefix, m_values[i]);
623 }
624 }
625
626 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
627 {
628 _tcscat(prefix, _T(" +- "));
629 e->print(file, level + 1, prefix);
630 prefix[level * 4] = 0;
631 }
632 }
633
634 /**
635 * Add attribute
636 */
637 static EnumerationCallbackResult AddAttribute(const TCHAR *key, const void *value, void *userData)
638 {
639 if (_tcscmp(key, _T("id")))
640 ((String *)userData)->appendFormattedString(_T(" %s=\"%s\""), key, (const TCHAR *)value);
641 return _CONTINUE;
642 }
643
644 /**
645 * Create XML element(s) from config entry
646 */
647 void ConfigEntry::createXml(String &xml, int level)
648 {
649 TCHAR *name = _tcsdup(m_name);
650 TCHAR *ptr = _tcschr(name, _T('#'));
651 if (ptr != NULL)
652 *ptr = 0;
653
654 if (m_id == 0)
655 xml.appendFormattedString(_T("%*s<%s"), level * 4, _T(""), name);
656 else
657 xml.appendFormattedString(_T("%*s<%s id=\"%d\""), level * 4, _T(""), name, m_id);
658 m_attributes.forEach(AddAttribute, &xml);
659 xml += _T(">");
660
661 if (m_first != NULL)
662 {
663 xml += _T("\n");
664 for(ConfigEntry *e = m_first; e != NULL; e = e->getNext())
665 e->createXml(xml, level + 1);
666 xml.appendFormattedString(_T("%*s"), level * 4, _T(""));
667 }
668
669 if (m_valueCount > 0)
670 xml.appendPreallocated(EscapeStringForXML(m_values[0], -1));
671 xml.appendFormattedString(_T("</%s>\n"), name);
672
673 for(int i = 1; i < m_valueCount; i++)
674 {
675 if (m_id == 0)
676 xml.appendFormattedString(_T("%*s<%s>"), level * 4, _T(""), name);
677 else
678 xml.appendFormattedString(_T("%*s<%s id=\"%d\">"), level * 4, _T(""), name, m_id);
679 xml.appendPreallocated(EscapeStringForXML(m_values[i], -1));
680 xml.appendFormattedString(_T("</%s>\n"), name);
681 }
682
683 free(name);
684 }
685
686 /**
687 * Constructor for config
688 */
689 Config::Config(bool allowMacroExpansion)
690 {
691 m_root = new ConfigEntry(_T("[root]"), NULL, this, NULL, 0, 0);
692 m_errorCount = 0;
693 m_mutex = MutexCreate();
694 m_allowMacroExpansion = allowMacroExpansion;
695 }
696
697 /**
698 * Destructor
699 */
700 Config::~Config()
701 {
702 delete m_root;
703 MutexDestroy(m_mutex);
704 }
705
706 /**
707 * Default error handler
708 */
709 void Config::onError(const TCHAR *errorMessage)
710 {
711 nxlog_debug(1, _T("Configuration loading error: %s"), errorMessage);
712 }
713
714 /**
715 * Report error
716 */
717 void Config::error(const TCHAR *format, ...)
718 {
719 va_list args;
720 TCHAR buffer[4096];
721
722 m_errorCount++;
723 va_start(args, format);
724 _vsntprintf(buffer, 4096, format, args);
725 va_end(args);
726 onError(buffer);
727 }
728
729 /**
730 * Parse size specification (with K, M, G, or T suffixes)
731 */
732 static UINT64 ParseSize(const TCHAR *s, UINT64 multiplier)
733 {
734 TCHAR *eptr;
735 UINT64 value = _tcstoull(s, &eptr, 0);
736 while(*eptr == ' ')
737 eptr++;
738 if ((*eptr == 'K') || (*eptr == 'k'))
739 return value * multiplier;
740 if ((*eptr == 'M') || (*eptr == 'm'))
741 return value * multiplier * multiplier;
742 if ((*eptr == 'G') || (*eptr == 'g'))
743 return value * multiplier * multiplier * multiplier;
744 if ((*eptr == 'T') || (*eptr == 't'))
745 return value * multiplier * multiplier * multiplier * multiplier;
746 return value;
747 }
748
749 /**
750 * Parse configuration template (emulation of old NxLoadConfig() API)
751 */
752 bool Config::parseTemplate(const TCHAR *section, NX_CFG_TEMPLATE *cfgTemplate)
753 {
754 TCHAR name[MAX_PATH], *curr, *eptr;
755 int i, j, pos, initialErrorCount = m_errorCount;
756 ConfigEntry *entry;
757
758 name[0] = _T('/');
759 nx_strncpy(&name[1], section, MAX_PATH - 2);
760 _tcscat(name, _T("/"));
761 pos = (int)_tcslen(name);
762
763 for(i = 0; cfgTemplate[i].type != CT_END_OF_LIST; i++)
764 {
765 nx_strncpy(&name[pos], cfgTemplate[i].token, MAX_PATH - pos);
766 entry = getEntry(name);
767 if (entry != NULL)
768 {
769 const TCHAR *value = CHECK_NULL(entry->getValue(entry->getValueCount() - 1));
770 switch(cfgTemplate[i].type)
771 {
772 case CT_LONG:
773 if ((cfgTemplate[i].overrideIndicator != NULL) &&
774 (*((INT32 *)cfgTemplate[i].overrideIndicator) != NXCONFIG_UNINITIALIZED_VALUE))
775 {
776 break; // this parameter was already initialized, and override from config is forbidden
777 }
778 *((INT32 *)cfgTemplate[i].buffer) = _tcstol(value, &eptr, 0);
779 if (*eptr != 0)
780 {
781 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
782 }
783 break;
784 case CT_WORD:
785 if ((cfgTemplate[i].overrideIndicator != NULL) &&
786 (*((INT16 *)cfgTemplate[i].overrideIndicator) != NXCONFIG_UNINITIALIZED_VALUE))
787 {
788 break; // this parameter was already initialized, and override from config is forbidden
789 }
790 *((UINT16 *)cfgTemplate[i].buffer) = (UINT16)_tcstoul(value, &eptr, 0);
791 if (*eptr != 0)
792 {
793 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
794 }
795 break;
796 case CT_BOOLEAN:
797 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
798 || !_tcsicmp(value, _T("1")))
799 {
800 *((UINT32 *)cfgTemplate[i].buffer) |= (UINT32)cfgTemplate[i].bufferSize;
801 }
802 else
803 {
804 *((UINT32 *)cfgTemplate[i].buffer) &= ~((UINT32)cfgTemplate[i].bufferSize);
805 }
806 break;
807 case CT_BOOLEAN64:
808 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
809 || !_tcsicmp(value, _T("1")))
810 {
811 *((UINT64 *)cfgTemplate[i].buffer) |= cfgTemplate[i].bufferSize;
812 }
813 else
814 {
815 *((UINT64 *)cfgTemplate[i].buffer) &= ~(cfgTemplate[i].bufferSize);
816 }
817 break;
818 case CT_STRING:
819 if ((cfgTemplate[i].overrideIndicator != NULL) &&
820 (*((TCHAR *)cfgTemplate[i].overrideIndicator) != 0))
821 {
822 break; // this parameter was already initialized, and override from config is forbidden
823 }
824 nx_strncpy((TCHAR *)cfgTemplate[i].buffer, value, (size_t)cfgTemplate[i].bufferSize);
825 break;
826 case CT_MB_STRING:
827 if ((cfgTemplate[i].overrideIndicator != NULL) &&
828 (*((char *)cfgTemplate[i].overrideIndicator) != 0))
829 {
830 break; // this parameter was already initialized, and override from config is forbidden
831 }
832 #ifdef UNICODE
833 memset(cfgTemplate[i].buffer, 0, (size_t)cfgTemplate[i].bufferSize);
834 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR, value, -1, (char *)cfgTemplate[i].buffer, (int)cfgTemplate[i].bufferSize - 1, NULL, NULL);
835 #else
836 nx_strncpy((TCHAR *)cfgTemplate[i].buffer, value, (size_t)cfgTemplate[i].bufferSize);
837 #endif
838 break;
839 case CT_STRING_LIST:
840 *((TCHAR **)cfgTemplate[i].buffer) = (TCHAR *)malloc(sizeof(TCHAR) * (entry->getConcatenatedValuesLength() + 1));
841 for(j = 0, curr = *((TCHAR **) cfgTemplate[i].buffer); j < entry->getValueCount(); j++)
842 {
843 _tcscpy(curr, entry->getValue(j));
844 curr += _tcslen(curr);
845 *curr = cfgTemplate[i].separator;
846 curr++;
847 }
848 *curr = 0;
849 break;
850 case CT_SIZE_BYTES:
851 if ((cfgTemplate[i].overrideIndicator != NULL) &&
852 (*((INT32 *)cfgTemplate[i].overrideIndicator) != NXCONFIG_UNINITIALIZED_VALUE))
853 {
854 break; // this parameter was already initialized, and override from config is forbidden
855 }
856 *((UINT64 *)cfgTemplate[i].buffer) = ParseSize(value, 1024);
857 break;
858 case CT_SIZE_UNITS:
859 if ((cfgTemplate[i].overrideIndicator != NULL) &&
860 (*((INT32 *)cfgTemplate[i].overrideIndicator) != NXCONFIG_UNINITIALIZED_VALUE))
861 {
862 break; // this parameter was already initialized, and override from config is forbidden
863 }
864 *((UINT64 *)cfgTemplate[i].buffer) = ParseSize(value, 1000);
865 break;
866 case CT_IGNORE:
867 break;
868 default:
869 break;
870 }
871 }
872 }
873
874 return (m_errorCount - initialErrorCount) == 0;
875 }
876
877 /**
878 * Get value
879 */
880 const TCHAR *Config::getValue(const TCHAR *path, const TCHAR *defaultValue)
881 {
882 const TCHAR *value;
883 ConfigEntry *entry = getEntry(path);
884 if (entry != NULL)
885 {
886 value = entry->getValue();
887 if (value == NULL)
888 value = defaultValue;
889 }
890 else
891 {
892 value = defaultValue;
893 }
894 return value;
895 }
896
897 /**
898 * Get value as 32 bit integer
899 */
900 INT32 Config::getValueAsInt(const TCHAR *path, INT32 defaultValue)
901 {
902 const TCHAR *value = getValue(path);
903 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
904 }
905
906 /**
907 * Get value as unsigned 32 bit integer
908 */
909 UINT32 Config::getValueAsUInt(const TCHAR *path, UINT32 defaultValue)
910 {
911 const TCHAR *value = getValue(path);
912 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
913 }
914
915 /**
916 * Get value as 64 bit integer
917 */
918 INT64 Config::getValueAsInt64(const TCHAR *path, INT64 defaultValue)
919 {
920 const TCHAR *value = getValue(path);
921 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
922 }
923
924 /**
925 * Get value as unsigned 64 bit integer
926 */
927 UINT64 Config::getValueAsUInt64(const TCHAR *path, UINT64 defaultValue)
928 {
929 const TCHAR *value = getValue(path);
930 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
931 }
932
933 /**
934 * Get value as boolean
935 */
936 bool Config::getValueAsBoolean(const TCHAR *path, bool defaultValue)
937 {
938 const TCHAR *value = getValue(path);
939 if (value != NULL)
940 {
941 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) || !_tcsicmp(value, _T("on"))
942 || (_tcstol(value, NULL, 0) != 0);
943 }
944 else
945 {
946 return defaultValue;
947 }
948 }
949
950 /**
951 * Get value at given path as UUID
952 */
953 uuid Config::getValueAsUUID(const TCHAR *path)
954 {
955 const TCHAR *value = getValue(path);
956 if (value != NULL)
957 {
958 return uuid::parse(value);
959 }
960 else
961 {
962 return uuid::NULL_UUID;
963 }
964 }
965
966 /**
967 * Get subentries
968 */
969 ObjectArray<ConfigEntry> *Config::getSubEntries(const TCHAR *path, const TCHAR *mask)
970 {
971 ConfigEntry *entry = getEntry(path);
972 return (entry != NULL) ? entry->getSubEntries(mask) : NULL;
973 }
974
975 /**
976 * Get subentries ordered by id
977 */
978 ObjectArray<ConfigEntry> *Config::getOrderedSubEntries(const TCHAR *path, const TCHAR *mask)
979 {
980 ConfigEntry *entry = getEntry(path);
981 return (entry != NULL) ? entry->getOrderedSubEntries(mask) : NULL;
982 }
983
984 /**
985 * Get entry
986 */
987 ConfigEntry *Config::getEntry(const TCHAR *path)
988 {
989 if ((path == NULL) || (*path != _T('/')))
990 return NULL;
991
992 if (!_tcscmp(path, _T("/")))
993 return m_root;
994
995 TCHAR name[256];
996 const TCHAR *curr, *end;
997 ConfigEntry *entry = m_root;
998
999 curr = path + 1;
1000 while(entry != NULL)
1001 {
1002 end = _tcschr(curr, _T('/'));
1003 if (end != NULL)
1004 {
1005 int len = min((int )(end - curr), 255);
1006 _tcsncpy(name, curr, len);
1007 name[len] = 0;
1008 entry = entry->findEntry(name);
1009 curr = end + 1;
1010 }
1011 else
1012 {
1013 return entry->findEntry(curr);
1014 }
1015 }
1016 return NULL;
1017 }
1018
1019 /**
1020 * Create entry if does not exist, or return existing
1021 * Will return NULL on error
1022 */
1023 ConfigEntry *Config::createEntry(const TCHAR *path)
1024 {
1025 const TCHAR *curr, *end;
1026 TCHAR name[256];
1027 ConfigEntry *entry, *parent;
1028
1029 if ((path == NULL) || (*path != _T('/')))
1030 return NULL;
1031
1032 if (!_tcscmp(path, _T("/")))
1033 return m_root;
1034
1035 curr = path + 1;
1036 parent = m_root;
1037 do
1038 {
1039 end = _tcschr(curr, _T('/'));
1040 if (end != NULL)
1041 {
1042 int len = min((int )(end - curr), 255);
1043 _tcsncpy(name, curr, len);
1044 name[len] = 0;
1045 entry = parent->findEntry(name);
1046 curr = end + 1;
1047 if (entry == NULL)
1048 entry = new ConfigEntry(name, parent, this, _T("<memory>"), 0, 0);
1049 parent = entry;
1050 }
1051 else
1052 {
1053 entry = parent->findEntry(curr);
1054 if (entry == NULL)
1055 entry = new ConfigEntry(curr, parent, this, _T("<memory>"), 0, 0);
1056 }
1057 }
1058 while(end != NULL);
1059 return entry;
1060 }
1061
1062 /**
1063 * Delete entry
1064 */
1065 void Config::deleteEntry(const TCHAR *path)
1066 {
1067 ConfigEntry *entry = getEntry(path);
1068 if (entry == NULL)
1069 return;
1070
1071 ConfigEntry *parent = entry->getParent();
1072 if (parent == NULL) // root entry
1073 return;
1074
1075 parent->unlinkEntry(entry);
1076 delete entry;
1077 }
1078
1079 /**
1080 * Set value
1081 * Returns false on error (usually caused by incorrect path)
1082 */
1083 bool Config::setValue(const TCHAR *path, const TCHAR *value)
1084 {
1085 ConfigEntry *entry = createEntry(path);
1086 if (entry == NULL)
1087 return false;
1088 entry->setValue(value);
1089 return true;
1090 }
1091
1092 /**
1093 * Set value
1094 * Returns false on error (usually caused by incorrect path)
1095 */
1096 bool Config::setValue(const TCHAR *path, INT32 value)
1097 {
1098 TCHAR buffer[32];
1099 _sntprintf(buffer, 32, _T("%ld"), (long) value);
1100 return setValue(path, buffer);
1101 }
1102
1103 /**
1104 * Set value
1105 * Returns false on error (usually caused by incorrect path)
1106 */
1107 bool Config::setValue(const TCHAR *path, UINT32 value)
1108 {
1109 TCHAR buffer[32];
1110 _sntprintf(buffer, 32, _T("%lu"), (unsigned long) value);
1111 return setValue(path, buffer);
1112 }
1113
1114 /**
1115 * Set value
1116 * Returns false on error (usually caused by incorrect path)
1117 */
1118 bool Config::setValue(const TCHAR *path, INT64 value)
1119 {
1120 TCHAR buffer[32];
1121 _sntprintf(buffer, 32, INT64_FMT, value);
1122 return setValue(path, buffer);
1123 }
1124
1125 /**
1126 * Set value
1127 * Returns false on error (usually caused by incorrect path)
1128 */
1129 bool Config::setValue(const TCHAR *path, UINT64 value)
1130 {
1131 TCHAR buffer[32];
1132 _sntprintf(buffer, 32, UINT64_FMT, value);
1133 return setValue(path, buffer);
1134 }
1135
1136 /**
1137 * Set value
1138 * Returns false on error (usually caused by incorrect path)
1139 */
1140 bool Config::setValue(const TCHAR *path, double value)
1141 {
1142 TCHAR buffer[32];
1143 _sntprintf(buffer, 32, _T("%f"), value);
1144 return setValue(path, buffer);
1145 }
1146
1147 /**
1148 * Set value
1149 * Returns false on error (usually caused by incorrect path)
1150 */
1151 bool Config::setValue(const TCHAR *path, const uuid& value)
1152 {
1153 TCHAR buffer[64];
1154 value.toString(buffer);
1155 return setValue(path, buffer);
1156 }
1157
1158 /**
1159 * Find comment start in INI style config line
1160 * Comment starts with # character, characters within double quotes ignored
1161 */
1162 static TCHAR *FindComment(TCHAR *str)
1163 {
1164 TCHAR *curr;
1165 bool quotes;
1166
1167 for(curr = str, quotes = false; *curr != 0; curr++)
1168 {
1169 if (*curr == _T('"'))
1170 {
1171 quotes = !quotes;
1172 }
1173 else if ((*curr == _T('#')) && !quotes)
1174 {
1175 return curr;
1176 }
1177 }
1178 return NULL;
1179 }
1180
1181 /**
1182 * Load INI-style config
1183 */
1184 bool Config::loadIniConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
1185 {
1186 FILE *cfg;
1187 TCHAR buffer[4096], *ptr;
1188 ConfigEntry *currentSection;
1189 int sourceLine = 0;
1190 bool validConfig = true;
1191
1192 cfg = _tfopen(file, _T("r"));
1193 if (cfg == NULL)
1194 {
1195 error(_T("Cannot open file %s"), file);
1196 return false;
1197 }
1198
1199 currentSection = m_root->findEntry(defaultIniSection);
1200 if (currentSection == NULL)
1201 {
1202 currentSection = new ConfigEntry(defaultIniSection, m_root, this, file, 0, 0);
1203 }
1204
1205 while(!feof(cfg))
1206 {
1207 // Read line from file
1208 buffer[0] = 0;
1209 if (_fgetts(buffer, 4095, cfg) == NULL)
1210 break; // EOF or error
1211 sourceLine++;
1212 ptr = _tcschr(buffer, _T('\n'));
1213 if (ptr != NULL)
1214 {
1215 if (ptr != buffer)
1216 {
1217 if (*(ptr - 1) == '\r')
1218 ptr--;
1219 }
1220 *ptr = 0;
1221 }
1222 ptr = FindComment(buffer);
1223 if (ptr != NULL)
1224 *ptr = 0;
1225
1226 StrStrip(buffer);
1227 if (buffer[0] == 0)
1228 continue;
1229
1230 // Check if it's a section name
1231 if ((buffer[0] == _T('*')) || (buffer[0] == _T('[')))
1232 {
1233 if (buffer[0] == _T('['))
1234 {
1235 TCHAR *end = _tcschr(buffer, _T(']'));
1236 if (end != NULL)
1237 *end = 0;
1238 }
1239
1240 ConfigEntry *parent = m_root;
1241 TCHAR *curr = &buffer[1];
1242 TCHAR *s;
1243 do
1244 {
1245 s = _tcschr(curr, _T('/'));
1246 if (s != NULL)
1247 *s = 0;
1248 currentSection = parent->findEntry(curr);
1249 if (currentSection == NULL)
1250 {
1251 currentSection = new ConfigEntry(curr, parent, this, file, sourceLine, 0);
1252 }
1253 curr = s + 1;
1254 parent = currentSection;
1255 } while(s != NULL);
1256 }
1257 else
1258 {
1259 // Divide on two parts at = sign
1260 ptr = _tcschr(buffer, _T('='));
1261 if (ptr == NULL)
1262 {
1263 error(_T("Syntax error in configuration file %s at line %d"), file, sourceLine);
1264 validConfig = false;
1265 continue;
1266 }
1267 *ptr = 0;
1268 ptr++;
1269 StrStrip(buffer);
1270 StrStrip(ptr);
1271
1272 ConfigEntry *entry = currentSection->findEntry(buffer);
1273 if (entry == NULL)
1274 {
1275 entry = new ConfigEntry(buffer, currentSection, this, file, sourceLine, 0);
1276 }
1277 entry->addValuePreallocated(ExpandValue(ptr, false, m_allowMacroExpansion));
1278 }
1279 }
1280 fclose(cfg);
1281 return ignoreErrors || validConfig;
1282 }
1283
1284 /**
1285 * Max stack depth for XML config
1286 */
1287 #define MAX_STACK_DEPTH 256
1288
1289 /**
1290 * State information for XML parser
1291 */
1292 typedef struct
1293 {
1294 const char *topLevelTag;
1295 XML_Parser parser;
1296 Config *config;
1297 const TCHAR *file;
1298 int level;
1299 ConfigEntry *stack[MAX_STACK_DEPTH];
1300 String charData[MAX_STACK_DEPTH];
1301 bool trimValue[MAX_STACK_DEPTH];
1302 bool merge;
1303 bool expandEnv;
1304 } XML_PARSER_STATE;
1305
1306 /**
1307 * Element start handler for XML parser
1308 */
1309 static void StartElement(void *userData, const char *name, const char **attrs)
1310 {
1311 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
1312
1313 if (ps->level == 0)
1314 {
1315 if (!stricmp(name, ps->topLevelTag))
1316 {
1317 ps->stack[ps->level] = ps->config->getEntry(_T("/"));
1318 ps->charData[ps->level] = _T("");
1319 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
1320 ps->level++;
1321 }
1322 else
1323 {
1324 ps->level = -1;
1325 }
1326 }
1327 else if (ps->level > 0)
1328 {
1329 if (ps->level < MAX_STACK_DEPTH)
1330 {
1331 TCHAR entryName[MAX_PATH];
1332
1333 UINT32 id = XMLGetAttrUINT32(attrs, "id", 0);
1334 #ifdef UNICODE
1335 if (id != 0)
1336 {
1337 WCHAR wname[MAX_PATH];
1338
1339 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);
1340 wname[MAX_PATH - 1] = 0;
1341 #ifdef _WIN32
1342 _snwprintf(entryName, MAX_PATH, L"%s#%u", wname, (unsigned int)id);
1343 #else
1344 swprintf(entryName, MAX_PATH, L"%S#%u", wname, (unsigned int)id);
1345 #endif
1346 }
1347 else
1348 {
1349 MultiByteToWideChar(CP_UTF8, 0, name, -1, entryName, MAX_PATH);
1350 entryName[MAX_PATH - 1] = 0;
1351 }
1352 #else
1353 if (id != 0)
1354 snprintf(entryName, MAX_PATH, "%s#%u", name, (unsigned int) id);
1355 else
1356 nx_strncpy(entryName, name, MAX_PATH);
1357 #endif
1358 bool merge = XMLGetAttrBoolean(attrs, "merge", ps->merge);
1359 ps->stack[ps->level] = merge ? ps->stack[ps->level - 1]->findEntry(entryName) : NULL;
1360 if (ps->stack[ps->level] == NULL)
1361 {
1362 ConfigEntry *e = new ConfigEntry(entryName, ps->stack[ps->level - 1], ps->config, ps->file, XML_GetCurrentLineNumber(ps->parser), (int)id);
1363 ps->stack[ps->level] = e;
1364 // add all attributes to the entry
1365 for(int i = 0; attrs[i] != NULL; i += 2)
1366 {
1367 #ifdef UNICODE
1368 e->setAttributePreallocated(WideStringFromMBString(attrs[i]), WideStringFromMBString(attrs[i + 1]));
1369 #else
1370 e->setAttribute(attrs[i], attrs[i + 1]);
1371 #endif
1372 }
1373 }
1374 ps->charData[ps->level] = _T("");
1375 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
1376 ps->level++;
1377 }
1378 else
1379 {
1380 ps->level++;
1381 }
1382 }
1383 }
1384
1385 /**
1386 * Element end handler for XML parser
1387 */
1388 static void EndElement(void *userData, const char *name)
1389 {
1390 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
1391
1392 if (ps->level > MAX_STACK_DEPTH)
1393 {
1394 ps->level--;
1395 }
1396 else if (ps->level > 0)
1397 {
1398 ps->level--;
1399 if (ps->trimValue[ps->level])
1400 ps->charData[ps->level].trim();
1401 ps->stack[ps->level]->addValuePreallocated(ExpandValue(ps->charData[ps->level], true, ps->expandEnv));
1402 }
1403 }
1404
1405 /**
1406 * Data handler for XML parser
1407 */
1408 static void CharData(void *userData, const XML_Char *s, int len)
1409 {
1410 XML_PARSER_STATE *ps = (XML_PARSER_STATE *) userData;
1411
1412 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
1413 ps->charData[ps->level - 1].appendMBString(s, len, CP_UTF8);
1414 }
1415
1416 /**
1417 * Load config from XML in memory
1418 */
1419 bool Config::loadXmlConfigFromMemory(const char *xml, int xmlSize, const TCHAR *name, const char *topLevelTag, bool merge)
1420 {
1421 XML_PARSER_STATE state;
1422
1423 XML_Parser parser = XML_ParserCreate(NULL);
1424 XML_SetUserData(parser, &state);
1425 XML_SetElementHandler(parser, StartElement, EndElement);
1426 XML_SetCharacterDataHandler(parser, CharData);
1427
1428 state.topLevelTag = (topLevelTag != NULL) ? topLevelTag : "config";
1429 state.config = this;
1430 state.level = 0;
1431 state.parser = parser;
1432 state.file = (name != NULL) ? name : _T("<mem>");
1433 state.merge = merge;
1434 state.expandEnv = m_allowMacroExpansion;
1435
1436 bool success = (XML_Parse(parser, xml, xmlSize, TRUE) != XML_STATUS_ERROR);
1437 if (!success)
1438 {
1439 error(_T("%hs at line %d"), XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser));
1440 }
1441 XML_ParserFree(parser);
1442 return success;
1443 }
1444
1445 /**
1446 * Load config from XML file
1447 */
1448 bool Config::loadXmlConfig(const TCHAR *file, const char *topLevelTag)
1449 {
1450 BYTE *xml;
1451 UINT32 size;
1452 bool success;
1453
1454 xml = LoadFile(file, &size);
1455 if (xml != NULL)
1456 {
1457 success = loadXmlConfigFromMemory((char *)xml, (int)size, file, topLevelTag);
1458 free(xml);
1459 }
1460 else
1461 {
1462 success = false;
1463 }
1464 return success;
1465 }
1466
1467 /**
1468 * Load config file with format auto detection
1469 */
1470 bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection, bool ignoreErrors)
1471 {
1472 NX_STAT_STRUCT fileStats;
1473 int ret = CALL_STAT(file, &fileStats);
1474 if (ret != 0)
1475 {
1476 error(_T("Could not process \"%s\"!"), file);
1477 return false;
1478 }
1479
1480 if (!S_ISREG(fileStats.st_mode))
1481 {
1482 error(_T("\"%s\" is not a file!"), file);
1483 return false;
1484 }
1485
1486 FILE *f = _tfopen(file, _T("r"));
1487 if (f == NULL)
1488 {
1489 error(_T("Cannot open file %s"), file);
1490 return false;
1491 }
1492
1493 // Skip all space/newline characters
1494 int ch;
1495 do
1496 {
1497 ch = fgetc(f);
1498 }
1499 while(isspace(ch));
1500
1501 fclose(f);
1502
1503 // If first non-space character is < assume XML format, otherwise assume INI format
1504 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection, ignoreErrors);
1505 }
1506
1507 /**
1508 * Load all files in given directory
1509 */
1510 bool Config::loadConfigDirectory(const TCHAR *path, const TCHAR *defaultIniSection, bool ignoreErrors)
1511 {
1512 _TDIR *dir;
1513 struct _tdirent *file;
1514 TCHAR fileName[MAX_PATH];
1515 bool success;
1516
1517 dir = _topendir(path);
1518 if (dir != NULL)
1519 {
1520 success = true;
1521 while(true)
1522 {
1523 file = _treaddir(dir);
1524 if (file == NULL)
1525 break;
1526
1527 if (!_tcscmp(file->d_name, _T(".")) || !_tcscmp(file->d_name, _T("..")))
1528 continue;
1529
1530 size_t len = _tcslen(path) + _tcslen(file->d_name) + 2;
1531 if (len > MAX_PATH)
1532 continue; // Full file name is too long
1533
1534 _tcscpy(fileName, path);
1535 _tcscat(fileName, FS_PATH_SEPARATOR);
1536 _tcscat(fileName, file->d_name);
1537
1538 if (!loadConfig(fileName, defaultIniSection, ignoreErrors))
1539 {
1540 success = false;
1541 }
1542 }
1543 _tclosedir(dir);
1544 }
1545 else
1546 {
1547 success = false;
1548 }
1549
1550 return success;
1551 }
1552
1553 /*
1554 * Print config to output stream
1555 */
1556
1557 void Config::print(FILE *file)
1558 {
1559 TCHAR prefix[256] = _T("");
1560 if (m_root != NULL)
1561 m_root->print(file, 0, prefix);
1562 }
1563
1564 /**
1565 * Create XML from config
1566 */
1567 String Config::createXml()
1568 {
1569 String xml;
1570 m_root->createXml(xml);
1571 return xml;
1572 }