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