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