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