license headers in libnetxms changed to LGPL
[public/netxms.git] / src / libnetxms / config.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** NetXMS Foundation Library
4 ** Copyright (C) 2003-2010 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 //
30 // Constructor for config entry
31 //
32
33 ConfigEntry::ConfigEntry(const TCHAR *name, ConfigEntry *parent, const TCHAR *file, int line, int id)
34 {
35 m_name = _tcsdup(CHECK_NULL(name));
36 m_childs = NULL;
37 m_next = NULL;
38 if (parent != NULL)
39 parent->addEntry(this);
40 m_valueCount = 0;
41 m_values = NULL;
42 m_file = _tcsdup(CHECK_NULL(file));
43 m_line = line;
44 m_id = id;
45 }
46
47
48 //
49 // Destructor for config entry
50 //
51
52 ConfigEntry::~ConfigEntry()
53 {
54 ConfigEntry *entry, *next;
55
56 for(entry = m_childs; entry != NULL; entry = next)
57 {
58 next = entry->getNext();
59 delete entry;
60 }
61 safe_free(m_name);
62 safe_free(m_file);
63
64 for(int i = 0; i < m_valueCount; i++)
65 safe_free(m_values[i]);
66 safe_free(m_values);
67 }
68
69
70 //
71 // Find entry by name
72 //
73
74 ConfigEntry *ConfigEntry::findEntry(const TCHAR *name)
75 {
76 ConfigEntry *e;
77
78 for(e = m_childs; e != NULL; e = e->getNext())
79 if (!_tcsicmp(e->getName(), name))
80 return e;
81 return NULL;
82 }
83
84
85 //
86 // Get all subentries with names matched to mask
87 //
88
89 ConfigEntryList *ConfigEntry::getSubEntries(const TCHAR *mask)
90 {
91 ConfigEntry *e, **list = NULL;
92 int count = 0, allocated = 0;
93
94 for(e = m_childs; e != NULL; e = e->getNext())
95 if (MatchString(mask, e->getName(), FALSE))
96 {
97 if (count == allocated)
98 {
99 allocated += 10;
100 list = (ConfigEntry **)realloc(list, sizeof(ConfigEntry *) * allocated);
101 }
102 list[count++] = e;
103 }
104 return new ConfigEntryList(list, count);
105 }
106
107
108 //
109 // Get all subentries with names matched to mask ordered by id
110 //
111
112 ConfigEntryList *ConfigEntry::getOrderedSubEntries(const TCHAR *mask)
113 {
114 ConfigEntryList *list = getSubEntries(mask);
115 list->sortById();
116 return list;
117 }
118
119
120 //
121 // Get value
122 //
123
124 const TCHAR *ConfigEntry::getValue(int index)
125 {
126 if ((index < 0) || (index >= m_valueCount))
127 return NULL;
128 return m_values[index];
129 }
130
131 LONG ConfigEntry::getValueInt(int index, LONG defaultValue)
132 {
133 const TCHAR *value = getValue(index);
134 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
135 }
136
137 DWORD ConfigEntry::getValueUInt(int index, DWORD defaultValue)
138 {
139 const TCHAR *value = getValue(index);
140 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
141 }
142
143 INT64 ConfigEntry::getValueInt64(int index, INT64 defaultValue)
144 {
145 const TCHAR *value = getValue(index);
146 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
147 }
148
149 QWORD ConfigEntry::getValueUInt64(int index, QWORD defaultValue)
150 {
151 const TCHAR *value = getValue(index);
152 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
153 }
154
155 bool ConfigEntry::getValueBoolean(int index, bool defaultValue)
156 {
157 const TCHAR *value = getValue(index);
158 if (value != NULL)
159 {
160 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
161 !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
162 }
163 else
164 {
165 return defaultValue;
166 }
167 }
168
169
170 //
171 // Set value
172 //
173
174 void ConfigEntry::setValue(const TCHAR *value)
175 {
176 for(int i = 0; i < m_valueCount; i++)
177 safe_free(m_values[i]);
178 m_valueCount = 1;
179 m_values = (TCHAR **)realloc(m_values, sizeof(TCHAR *));
180 m_values[0] = _tcsdup(value);
181 }
182
183
184 //
185 // Add value
186 //
187
188 void ConfigEntry::addValue(const TCHAR *value)
189 {
190 m_values = (TCHAR **)realloc(m_values, sizeof(TCHAR *) * (m_valueCount + 1));
191 m_values[m_valueCount] = _tcsdup(value);
192 m_valueCount++;
193 }
194
195
196 //
197 // Get summary length of all values as if they was concatenated with separator character
198 //
199
200 int ConfigEntry::getConcatenatedValuesLength()
201 {
202 int i, len;
203
204 if (m_valueCount < 1)
205 return 0;
206
207 for(i = 0, len = 0; i < m_valueCount; i++)
208 len += (int)_tcslen(m_values[i]);
209 return len + m_valueCount;
210 }
211
212
213 //
214 // Get value for given subentry
215 //
216
217 const TCHAR *ConfigEntry::getSubEntryValue(const TCHAR *name, int index, const TCHAR *defaultValue)
218 {
219 ConfigEntry *e = findEntry(name);
220 if (e == NULL)
221 return defaultValue;
222 const TCHAR *value = e->getValue(index);
223 return (value != NULL) ? value : defaultValue;
224 }
225
226 LONG ConfigEntry::getSubEntryValueInt(const TCHAR *name, int index, LONG defaultValue)
227 {
228 const TCHAR *value = getSubEntryValue(name, index);
229 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
230 }
231
232 DWORD ConfigEntry::getSubEntryValueUInt(const TCHAR *name, int index, DWORD defaultValue)
233 {
234 const TCHAR *value = getSubEntryValue(name, index);
235 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
236 }
237
238 INT64 ConfigEntry::getSubEntryValueInt64(const TCHAR *name, int index, INT64 defaultValue)
239 {
240 const TCHAR *value = getSubEntryValue(name, index);
241 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
242 }
243
244 QWORD ConfigEntry::getSubEntryValueUInt64(const TCHAR *name, int index, QWORD defaultValue)
245 {
246 const TCHAR *value = getSubEntryValue(name, index);
247 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
248 }
249
250 bool ConfigEntry::getSubEntryValueBoolean(const TCHAR *name, int index, bool defaultValue)
251 {
252 const TCHAR *value = getSubEntryValue(name, index);
253 if (value != NULL)
254 {
255 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
256 !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
257 }
258 else
259 {
260 return defaultValue;
261 }
262 }
263
264
265 //
266 // Print config entry
267 //
268
269 void ConfigEntry::print(FILE *file, int level)
270 {
271 _tprintf(_T("%*s%s\n"), level * 4, _T(""), m_name);
272 for(ConfigEntry *e = m_childs; e != NULL; e = e->getNext())
273 e->print(file, level + 1);
274
275 for(int i = 0, len = 0; i < m_valueCount; i++)
276 _tprintf(_T("%*svalue: %s\n"), level * 4 + 2, _T(""), m_values[i]);
277 }
278
279
280 //
281 // Sort entry list
282 //
283
284 static int CompareById(const void *p1, const void *p2)
285 {
286 ConfigEntry *e1 = *((ConfigEntry **)p1);
287 ConfigEntry *e2 = *((ConfigEntry **)p2);
288 return e1->getId() - e2->getId();
289 }
290
291 void ConfigEntryList::sortById()
292 {
293 qsort(m_list, m_size, sizeof(ConfigEntry *), CompareById);
294 }
295
296
297 //
298 // Constructor for config
299 //
300
301 Config::Config()
302 {
303 m_root = new ConfigEntry(_T("[root]"), NULL, NULL, 0, 0);
304 m_errorCount = 0;
305 }
306
307
308 //
309 // Destructor
310 //
311
312 Config::~Config()
313 {
314 delete m_root;
315 }
316
317
318 //
319 // Default error handler
320 //
321
322 void Config::onError(const TCHAR *errorMessage)
323 {
324 _ftprintf(stderr, _T("%s\n"), errorMessage);
325 }
326
327
328 //
329 // Report error
330 //
331
332 void Config::error(const TCHAR *format, ...)
333 {
334 va_list args;
335 TCHAR buffer[4096];
336
337 m_errorCount++;
338 va_start(args, format);
339 _vsntprintf(buffer, 4096, format, args);
340 va_end(args);
341 onError(buffer);
342 }
343
344
345 //
346 // Simulation of old NxLoadConfig() API
347 //
348
349 bool Config::parseTemplate(const TCHAR *section, NX_CFG_TEMPLATE *cfgTemplate)
350 {
351 TCHAR name[MAX_PATH], *curr, *eptr;
352 int i, j, pos, initialErrorCount = m_errorCount;
353 ConfigEntry *entry;
354
355 name[0] = _T('/');
356 nx_strncpy(&name[1], section, MAX_PATH - 2);
357 _tcscat(name, _T("/"));
358 pos = (int)_tcslen(name);
359
360 for(i = 0; cfgTemplate[i].iType != CT_END_OF_LIST; i++)
361 {
362 nx_strncpy(&name[pos], cfgTemplate[i].szToken, MAX_PATH - pos);
363 entry = getEntry(name);
364 if (entry != NULL)
365 {
366 const TCHAR *value = CHECK_NULL(entry->getValue(entry->getValueCount() - 1));
367 switch(cfgTemplate[i].iType)
368 {
369 case CT_LONG:
370 *((LONG *)cfgTemplate[i].pBuffer) = _tcstol(value, &eptr, 0);
371 if (*eptr != 0)
372 {
373 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
374 }
375 break;
376 case CT_WORD:
377 *((WORD *)cfgTemplate[i].pBuffer) = (WORD)_tcstoul(value, &eptr, 0);
378 if (*eptr != 0)
379 {
380 error(_T("Invalid number '%s' in configuration file %s at line %d\n"), value, entry->getFile(), entry->getLine());
381 }
382 break;
383 case CT_BOOLEAN:
384 if (!_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
385 !_tcsicmp(value, _T("on")) || !_tcsicmp(value, _T("1")))
386 {
387 *((DWORD *)cfgTemplate[i].pBuffer) |= cfgTemplate[i].dwBufferSize;
388 }
389 else
390 {
391 *((DWORD *)cfgTemplate[i].pBuffer) &= ~(cfgTemplate[i].dwBufferSize);
392 }
393 break;
394 case CT_STRING:
395 nx_strncpy((TCHAR *)cfgTemplate[i].pBuffer, value, cfgTemplate[i].dwBufferSize);
396 break;
397 case CT_STRING_LIST:
398 *((TCHAR **)cfgTemplate[i].pBuffer) = (TCHAR *)malloc(sizeof(TCHAR) * (entry->getConcatenatedValuesLength() + 1));
399 for(j = 0, curr = *((TCHAR **)cfgTemplate[i].pBuffer); j < entry->getValueCount(); j++)
400 {
401 _tcscpy(curr, entry->getValue(j));
402 curr += _tcslen(curr);
403 *curr = cfgTemplate[i].cSeparator;
404 curr++;
405 }
406 *curr = 0;
407 break;
408 case CT_IGNORE:
409 break;
410 default:
411 break;
412 }
413 }
414 }
415
416 return (m_errorCount - initialErrorCount) == 0;
417 }
418
419
420 //
421 // Get value
422 //
423
424 const TCHAR *Config::getValue(const TCHAR *path, const TCHAR *defaultValue)
425 {
426 const TCHAR *value;
427 ConfigEntry *entry = getEntry(path);
428 if (entry != NULL)
429 {
430 value = entry->getValue();
431 if (value == NULL)
432 value = defaultValue;
433 }
434 else
435 {
436 value = defaultValue;
437 }
438 return value;
439 }
440
441 LONG Config::getValueInt(const TCHAR *path, LONG defaultValue)
442 {
443 const TCHAR *value = getValue(path);
444 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
445 }
446
447 DWORD Config::getValueUInt(const TCHAR *path, DWORD defaultValue)
448 {
449 const TCHAR *value = getValue(path);
450 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
451 }
452
453 INT64 Config::getValueInt64(const TCHAR *path, INT64 defaultValue)
454 {
455 const TCHAR *value = getValue(path);
456 return (value != NULL) ? _tcstol(value, NULL, 0) : defaultValue;
457 }
458
459 QWORD Config::getValueUInt64(const TCHAR *path, QWORD defaultValue)
460 {
461 const TCHAR *value = getValue(path);
462 return (value != NULL) ? _tcstoul(value, NULL, 0) : defaultValue;
463 }
464
465 bool Config::getValueBoolean(const TCHAR *path, bool defaultValue)
466 {
467 const TCHAR *value = getValue(path);
468 if (value != NULL)
469 {
470 return !_tcsicmp(value, _T("yes")) || !_tcsicmp(value, _T("true")) ||
471 !_tcsicmp(value, _T("on")) || (_tcstol(value, NULL, 0) != 0);
472 }
473 else
474 {
475 return defaultValue;
476 }
477 }
478
479
480 //
481 // Get subentries
482 //
483
484 ConfigEntryList *Config::getSubEntries(const TCHAR *path, const TCHAR *mask)
485 {
486 ConfigEntry *entry = getEntry(path);
487 return (entry != NULL) ? entry->getSubEntries(mask) : NULL;
488 }
489
490
491 //
492 // Get subentries ordered by id
493 //
494
495 ConfigEntryList *Config::getOrderedSubEntries(const TCHAR *path, const TCHAR *mask)
496 {
497 ConfigEntry *entry = getEntry(path);
498 return (entry != NULL) ? entry->getOrderedSubEntries(mask) : NULL;
499 }
500
501
502 //
503 // Get entry
504 //
505
506 ConfigEntry *Config::getEntry(const TCHAR *path)
507 {
508 const TCHAR *curr, *end;
509 TCHAR name[256];
510 ConfigEntry *entry = m_root;
511
512 if ((path == NULL) || (*path != _T('/')))
513 return NULL;
514
515 if (!_tcscmp(path, _T("/")))
516 return m_root;
517
518 curr = path + 1;
519 while(entry != NULL)
520 {
521 end = _tcschr(curr, _T('/'));
522 if (end != NULL)
523 {
524 int len = min((int)(end - curr), 255);
525 _tcsncpy(name, curr, len);
526 name[len] = 0;
527 entry = entry->findEntry(name);
528 curr = end + 1;
529 }
530 else
531 {
532 return entry->findEntry(curr);
533 }
534 }
535 return NULL;
536 }
537
538
539 //
540 // Find comment start in INI style config line
541 // Comment starts with # character, characters within double quotes ignored
542 //
543
544 static TCHAR *FindComment(TCHAR *str)
545 {
546 TCHAR *curr;
547 bool quotes;
548
549 for(curr = str, quotes = false; *curr != 0; curr++)
550 {
551 if (*curr == _T('"'))
552 {
553 quotes = !quotes;
554 }
555 else if ((*curr == _T('#')) && !quotes)
556 {
557 return curr;
558 }
559 }
560 return NULL;
561 }
562
563
564 //
565 // Load INI-style config
566 //
567
568 bool Config::loadIniConfig(const TCHAR *file, const TCHAR *defaultIniSection)
569 {
570 FILE *cfg;
571 TCHAR buffer[4096], *ptr;
572 ConfigEntry *currentSection;
573 int sourceLine = 0;
574
575 cfg = _tfopen(file, _T("r"));
576 if (cfg == NULL)
577 {
578 error(_T("Cannot open file %s"), file);
579 return false;
580 }
581
582 currentSection = m_root->findEntry(defaultIniSection);
583 if (currentSection == NULL)
584 {
585 currentSection = new ConfigEntry(defaultIniSection, m_root, file, 0, 0);
586 }
587
588 while(!feof(cfg))
589 {
590 // Read line from file
591 buffer[0] = 0;
592 _fgetts(buffer, 4095, cfg);
593 sourceLine++;
594 ptr = _tcschr(buffer, _T('\n'));
595 if (ptr != NULL)
596 *ptr = 0;
597 ptr = FindComment(buffer);
598 if (ptr != NULL)
599 *ptr = 0;
600
601 StrStrip(buffer);
602 if (buffer[0] == 0)
603 continue;
604
605 // Check if it's a section name
606 if ((buffer[0] == _T('*')) || (buffer[0] == _T('[')))
607 {
608 if (buffer[0] == _T('['))
609 {
610 TCHAR *end = _tcschr(buffer, _T(']'));
611 if (end != NULL)
612 *end = 0;
613 }
614 currentSection = m_root->findEntry(&buffer[1]);
615 if (currentSection == NULL)
616 {
617 currentSection = new ConfigEntry(&buffer[1], m_root, file, sourceLine, 0);
618 }
619 }
620 else
621 {
622 // Divide on two parts at = sign
623 ptr = _tcschr(buffer, _T('='));
624 if (ptr == NULL)
625 {
626 error(_T("Syntax error in configuration file %s at line %d"), file, sourceLine);
627 continue;
628 }
629 *ptr = 0;
630 ptr++;
631 StrStrip(buffer);
632 StrStrip(ptr);
633
634 ConfigEntry *entry = currentSection->findEntry(buffer);
635 if (entry == NULL)
636 {
637 entry = new ConfigEntry(buffer, currentSection, file, sourceLine, 0);
638 }
639 entry->addValue(ptr);
640 }
641 }
642 fclose(cfg);
643 return true;
644 }
645
646
647 //
648 // Load config from XML file
649 //
650
651 #define MAX_STACK_DEPTH 256
652
653 typedef struct
654 {
655 const char *topLevelTag;
656 XML_Parser parser;
657 Config *config;
658 const TCHAR *file;
659 int level;
660 ConfigEntry *stack[MAX_STACK_DEPTH];
661 String charData[MAX_STACK_DEPTH];
662 bool trimValue[MAX_STACK_DEPTH];
663 } XML_PARSER_STATE;
664
665 static void StartElement(void *userData, const char *name, const char **attrs)
666 {
667 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
668
669 if (ps->level == 0)
670 {
671 if (!stricmp(name, ps->topLevelTag))
672 {
673 ps->stack[ps->level++] = ps->config->getEntry(_T("/"));
674 }
675 else
676 {
677 ps->level = -1;
678 }
679 }
680 else if (ps->level > 0)
681 {
682 if (ps->level < MAX_STACK_DEPTH)
683 {
684 TCHAR entryName[MAX_PATH];
685
686 DWORD id = XMLGetAttrDWORD(attrs, "id", 0);
687 #ifdef UNICODE
688 if (id != 0)
689 {
690 WCHAR wname[MAX_PATH];
691
692 MultiByteToWideChar(CP_UTF8, 0, name, -1, wname, MAX_PATH);
693 wname[MAX_PATH - 1] = 0;
694 _snwprintf(entryName, MAX_PATH, L"%s#%u", wname, id);
695 }
696 else
697 {
698 MultiByteToWideChar(CP_UTF8, 0, name, -1, entryName, MAX_PATH);
699 entryName[MAX_PATH - 1] = 0;
700 }
701 #else
702 if (id != 0)
703 snprintf(entryName, MAX_PATH, "%s#%u", name, id);
704 else
705 nx_strncpy(entryName, name, MAX_PATH);
706 #endif
707 ps->stack[ps->level] = ps->stack[ps->level - 1]->findEntry(entryName);
708 if (ps->stack[ps->level] == NULL)
709 ps->stack[ps->level] = new ConfigEntry(entryName, ps->stack[ps->level - 1], ps->file, XML_GetCurrentLineNumber(ps->parser), (int)id);
710 ps->charData[ps->level] = _T("");
711 ps->trimValue[ps->level] = XMLGetAttrBoolean(attrs, "trim", true);
712 ps->level++;
713 }
714 else
715 {
716 ps->level++;
717 }
718 }
719 }
720
721 static void EndElement(void *userData, const char *name)
722 {
723 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
724
725 if (ps->level > MAX_STACK_DEPTH)
726 {
727 ps->level--;
728 }
729 else if (ps->level > 0)
730 {
731 ps->level--;
732 if (ps->trimValue[ps->level])
733 ps->charData[ps->level].trim();
734 ps->stack[ps->level]->addValue(ps->charData[ps->level]);
735 }
736 }
737
738 static void CharData(void *userData, const XML_Char *s, int len)
739 {
740 XML_PARSER_STATE *ps = (XML_PARSER_STATE *)userData;
741
742 if ((ps->level > 0) && (ps->level <= MAX_STACK_DEPTH))
743 ps->charData[ps->level - 1].addMultiByteString(s, len, CP_UTF8);
744 }
745
746 bool Config::loadXmlConfigFromMemory(const char *xml, int xmlSize, const TCHAR *name, const char *topLevelTag)
747 {
748 XML_PARSER_STATE state;
749
750 XML_Parser parser = XML_ParserCreate(NULL);
751 XML_SetUserData(parser, &state);
752 XML_SetElementHandler(parser, StartElement, EndElement);
753 XML_SetCharacterDataHandler(parser, CharData);
754
755 state.topLevelTag = (topLevelTag != NULL) ? topLevelTag : "config";
756 state.config = this;
757 state.level = 0;
758 state.parser = parser;
759 state.file = (name != NULL) ? name : _T("<mem>");
760
761 bool success = (XML_Parse(parser, xml, xmlSize, TRUE) != XML_STATUS_ERROR);
762 if (!success)
763 {
764 error(_T("%s at line %d"), XML_ErrorString(XML_GetErrorCode(parser)),
765 XML_GetCurrentLineNumber(parser));
766 }
767 return success;
768 }
769
770 bool Config::loadXmlConfig(const TCHAR *file, const char *topLevelTag)
771 {
772 BYTE *xml;
773 DWORD size;
774 bool success;
775
776 xml = LoadFile(file, &size);
777 if (xml != NULL)
778 {
779 success = loadXmlConfigFromMemory((char *)xml, (int)size, file, topLevelTag);
780 }
781 else
782 {
783 success = false;
784 }
785
786 return success;
787 }
788
789
790 //
791 // Load config file with format auto detection
792 //
793
794 bool Config::loadConfig(const TCHAR *file, const TCHAR *defaultIniSection)
795 {
796 FILE *f;
797 int ch;
798
799 f = _tfopen(file, _T("r"));
800 if (f == NULL)
801 {
802 error(_T("Cannot open file %s"), file);
803 return false;
804 }
805
806 // Skip all space/newline characters
807 do
808 {
809 ch = fgetc(f);
810 } while(isspace(ch));
811
812 fclose(f);
813
814 // If first non-space character is < assume XML format, otherwise assume INI format
815 return (ch == '<') ? loadXmlConfig(file) : loadIniConfig(file, defaultIniSection);
816 }
817
818
819 //
820 // Load all files in given directory
821 //
822
823 bool Config::loadConfigDirectory(const TCHAR *path, const TCHAR *defaultIniSection)
824 {
825 DIR *dir;
826 struct dirent *file;
827 TCHAR fileName[MAX_PATH];
828 bool success;
829
830 #ifdef UNICODE
831 char mbpath[MAX_PATH];
832 WCHAR wname[MAX_PATH];
833
834 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR, path, -1, mbpath, MAX_PATH, NULL, NULL);
835 mbpath[MAX_PATH - 1] = 0;
836 dir = opendir(mbpath);
837 #else
838 dir = opendir(path);
839 #endif
840 if (dir != NULL)
841 {
842 success = true;
843 while(1)
844 {
845 file = readdir(dir);
846 if (file == NULL)
847 break;
848
849 if (!strcmp(file->d_name, ".") || !strcmp(file->d_name, ".."))
850 continue;
851
852 size_t len = _tcslen(path) + strlen(file->d_name) + 2;
853 if (len > MAX_PATH)
854 continue; // Full file name is too long
855
856 _tcscpy(fileName, path);
857 _tcscat(fileName, FS_PATH_SEPARATOR);
858 #ifdef UNICODE
859 MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, file->d_name, -1, wname, MAX_PATH);
860 wcscat(fileName, wname);
861 #else
862 strcat(fileName, file->d_name);
863 #endif
864
865 success = success && loadConfig(fileName, defaultIniSection);
866 }
867 closedir(dir);
868 }
869 else
870 {
871 success = false;
872 }
873
874 return success;
875 }
876
877
878 //
879 // Print config to output stream
880 //
881
882 void Config::print(FILE *file)
883 {
884 if (m_root != NULL)
885 m_root->print(file, 0);
886 }