Implemented Export and Import for Server Actions. Fixes #NX-703
[public/netxms.git] / src / server / core / import.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
4 **
5 ** This program is free software; you can redistribute it and/or modify
6 ** it under the terms of the GNU General Public License as published by
7 ** the Free Software Foundation; either version 2 of the License, or
8 ** (at your option) any later version.
9 **
10 ** This program is distributed in the hope that it will be useful,
11 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 ** GNU General Public License for more details.
14 **
15 ** You should have received a copy of the GNU General Public License
16 ** along with this program; if not, write to the Free Software
17 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 **
19 ** File: import.cpp
20 **
21 **/
22
23 #include "nxcore.h"
24
25 /**
26 * Check if given event exist either in server configuration or in configuration being imported
27 */
28 static bool IsEventExist(const TCHAR *name, Config *config)
29 {
30 EventObject *eo = FindEventObjectByName(name);
31 if (eo != NULL)
32 {
33 eo->decRefCount();
34 return true;
35 }
36
37 ConfigEntry *eventsRoot = config->getEntry(_T("/events"));
38 if (eventsRoot != NULL)
39 {
40 ObjectArray<ConfigEntry> *events = eventsRoot->getSubEntries(_T("event#*"));
41 for(int i = 0; i < events->size(); i++)
42 {
43 ConfigEntry *event = events->get(i);
44 if (!_tcsicmp(event->getSubEntryValue(_T("name"), 0, _T("<unnamed>")), name))
45 {
46 delete events;
47 return true;
48 }
49 }
50 delete events;
51 }
52
53 return false;
54 }
55
56 /**
57 * Validate DCI from template
58 */
59 static bool ValidateDci(Config *config, ConfigEntry *dci, const TCHAR *templateName, TCHAR *errorText, int errorTextLen)
60 {
61 ConfigEntry *thresholdsRoot = dci->findEntry(_T("thresholds"));
62 if (thresholdsRoot == NULL)
63 return true;
64
65 bool success = true;
66 ObjectArray<ConfigEntry> *thresholds = thresholdsRoot->getSubEntries(_T("threshold#*"));
67 for(int i = 0; i < thresholds->size(); i++)
68 {
69 ConfigEntry *threshold = thresholds->get(i);
70 if (!IsEventExist(threshold->getSubEntryValue(_T("activationEvent")), config))
71 {
72 _sntprintf(errorText, errorTextLen,
73 _T("Template \"%s\" DCI \"%s\" threshold %d attribute \"activationEvent\" refers to unknown event"),
74 templateName, dci->getSubEntryValue(_T("description"), 0, _T("<unnamed>")), i + 1);
75 success = false;
76 break;
77 }
78 if (!IsEventExist(threshold->getSubEntryValue(_T("deactivationEvent")), config))
79 {
80 _sntprintf(errorText, errorTextLen,
81 _T("Template \"%s\" DCI \"%s\" threshold %d attribute \"deactivationEvent\" refers to unknown event"),
82 templateName, dci->getSubEntryValue(_T("description"), 0, _T("<unnamed>")), i + 1);
83 success = false;
84 break;
85 }
86 }
87 delete thresholds;
88 return success;
89 }
90
91 /**
92 * Validate template
93 */
94 static bool ValidateTemplate(Config *config, ConfigEntry *root, TCHAR *errorText, int errorTextLen)
95 {
96 DbgPrintf(6, _T("ValidateConfig(): validating template \"%s\""), root->getSubEntryValue(_T("name"), 0, _T("<unnamed>")));
97
98 ConfigEntry *dcRoot = root->findEntry(_T("dataCollection"));
99 if (dcRoot == NULL)
100 return true;
101
102 bool success = true;
103 const TCHAR *name = root->getSubEntryValue(_T("name"), 0, _T("<unnamed>"));
104
105 ObjectArray<ConfigEntry> *dcis = dcRoot->getSubEntries(_T("dci#*"));
106 for(int i = 0; i < dcis->size(); i++)
107 {
108 if (!ValidateDci(config, dcis->get(i), name, errorText, errorTextLen))
109 {
110 success = false;
111 break;
112 }
113 }
114 delete dcis;
115
116 if (success)
117 {
118 ObjectArray<ConfigEntry> *dctables = dcRoot->getSubEntries(_T("dctable#*"));
119 for(int i = 0; i < dctables->size(); i++)
120 {
121 if (!ValidateDci(config, dctables->get(i), name, errorText, errorTextLen))
122 {
123 success = false;
124 break;
125 }
126 }
127 delete dctables;
128 }
129
130 return success;
131 }
132
133 /**
134 * Validate configuration before import
135 */
136 bool ValidateConfig(Config *config, UINT32 flags, TCHAR *errorText, int errorTextLen)
137 {
138 int i;
139 ObjectArray<ConfigEntry> *events = NULL, *traps = NULL, *templates = NULL;
140 ConfigEntry *eventsRoot, *trapsRoot, *templatesRoot;
141 bool success = false;
142
143 DbgPrintf(4, _T("ValidateConfig() called, flags = 0x%04X"), flags);
144
145 // Validate events
146 eventsRoot = config->getEntry(_T("/events"));
147 if (eventsRoot != NULL)
148 {
149 events = eventsRoot->getSubEntries(_T("event#*"));
150 for(i = 0; i < events->size(); i++)
151 {
152 ConfigEntry *event = events->get(i);
153 DbgPrintf(6, _T("ValidateConfig(): validating event %s"), event->getSubEntryValue(_T("name"), 0, _T("<unnamed>")));
154
155 UINT32 code = event->getSubEntryValueAsUInt(_T("code"));
156 if ((code >= FIRST_USER_EVENT_ID) || (code == 0))
157 {
158 ConfigEntry *e = event->findEntry(_T("name"));
159 if (e != NULL)
160 {
161 EventObject *eventObject = FindEventObjectByName(e->getValue());
162 if (eventObject != NULL)
163 {
164 eventObject->decRefCount();
165 if (!(flags & CFG_IMPORT_REPLACE_EVENT_BY_NAME))
166 {
167 _sntprintf(errorText, errorTextLen, _T("Event with name %s already exist"), e->getValue());
168 goto stop_processing;
169 }
170 }
171 }
172 else
173 {
174 _sntprintf(errorText, errorTextLen, _T("Mandatory attribute \"name\" missing for entry %s"), event->getName());
175 goto stop_processing;
176 }
177 }
178 else
179 {
180 EventObject *eventObject = FindEventObjectByCode(code);
181 if (eventObject != NULL)
182 {
183 if (!(flags & CFG_IMPORT_REPLACE_EVENT_BY_CODE))
184 {
185 _sntprintf(errorText, errorTextLen, _T("Event with code %d already exist (existing event name: %s; new event name: %s)"),
186 eventObject->getCode(), eventObject->getName(), event->getSubEntryValue(_T("name"), 0, _T("<unnamed>")));
187 eventObject->decRefCount();
188 goto stop_processing;
189 }
190 eventObject->decRefCount();
191 }
192 }
193 }
194 }
195
196 // Validate traps
197 trapsRoot = config->getEntry(_T("/traps"));
198 if (trapsRoot != NULL)
199 {
200 traps = trapsRoot->getSubEntries(_T("trap#*"));
201 for(i = 0; i < traps->size(); i++)
202 {
203 ConfigEntry *trap = traps->get(i);
204 DbgPrintf(6, _T("ValidateConfig(): validating trap \"%s\""), trap->getSubEntryValue(_T("description"), 0, _T("<unnamed>")));
205 if (!IsEventExist(trap->getSubEntryValue(_T("event")), config))
206 {
207 _sntprintf(errorText, errorTextLen, _T("Trap \"%s\" references unknown event"), trap->getSubEntryValue(_T("description"), 0, _T("")));
208 goto stop_processing;
209 }
210 }
211 }
212
213 // Validate templates
214 templatesRoot = config->getEntry(_T("/templates"));
215 if (templatesRoot != NULL)
216 {
217 templates = templatesRoot->getSubEntries(_T("template#*"));
218 for(i = 0; i < templates->size(); i++)
219 {
220 if (!ValidateTemplate(config, templates->get(i), errorText, errorTextLen))
221 goto stop_processing;
222 }
223 }
224
225 success = true;
226
227 stop_processing:
228 delete events;
229 delete traps;
230 delete templates;
231
232 DbgPrintf(4, _T("ValidateConfig() finished, status = %d"), success);
233 if (!success)
234 DbgPrintf(4, _T("ValidateConfig(): %s"), errorText);
235 return success;
236 }
237
238 /**
239 * Import event
240 */
241 static UINT32 ImportEvent(ConfigEntry *event)
242 {
243 const TCHAR *name = event->getSubEntryValue(_T("name"));
244 if (name == NULL)
245 return RCC_INTERNAL_ERROR;
246
247 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
248
249 UINT32 code = 0;
250 uuid guid = event->getSubEntryValueAsUUID(_T("guid"));
251 if (!guid.isNull())
252 {
253 DB_STATEMENT hStmt = DBPrepare(hdb, _T("SELECT event_code FROM event_cfg WHERE guid=?"));
254 if (hStmt == NULL)
255 {
256 DBConnectionPoolReleaseConnection(hdb);
257 return RCC_DB_FAILURE;
258 }
259 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, guid);
260 DB_RESULT hResult = DBSelectPrepared(hStmt);
261 if (hResult != NULL)
262 {
263 code = DBGetFieldULong(hResult, 0, 0);
264 DBFreeResult(hResult);
265 }
266 DBFreeStatement(hStmt);
267 if (code != 0)
268 {
269 nxlog_debug(4, _T("ImportEvent: found existing event with GUID %s (code=%d)"), (const TCHAR *)guid.toString(), code);
270 }
271 else
272 {
273 nxlog_debug(4, _T("ImportEvent: event with GUID %s not found"), (const TCHAR *)guid.toString());
274 }
275 }
276 else
277 {
278 code = event->getSubEntryValueAsUInt(_T("code"), 0, 0);
279 if (code >= FIRST_USER_EVENT_ID)
280 {
281 code = 0;
282 nxlog_debug(4, _T("ImportEvent: event without GUID and code not in system range"));
283 }
284 else
285 {
286 nxlog_debug(4, _T("ImportEvent: using provided event code %d"), code);
287 }
288 }
289
290 // Create or update event template in database
291 const TCHAR *msg = event->getSubEntryValue(_T("message"), 0, name);
292 const TCHAR *descr = event->getSubEntryValue(_T("description"));
293 TCHAR query[8192];
294 if ((code != 0) && IsDatabaseRecordExist(hdb, _T("event_cfg"), _T("event_code"), code))
295 {
296 nxlog_debug(4, _T("ImportEvent: found existing event with code %d"), code);
297 _sntprintf(query, 8192, _T("UPDATE event_cfg SET event_name=%s,severity=%d,flags=%d,message=%s,description=%s WHERE event_code=%d"),
298 (const TCHAR *)DBPrepareString(hdb, name), event->getSubEntryValueAsInt(_T("severity")),
299 event->getSubEntryValueAsInt(_T("flags")), (const TCHAR *)DBPrepareString(hdb, msg),
300 (const TCHAR *)DBPrepareString(hdb, descr), code);
301 }
302 else if (IsDatabaseRecordExist(hdb, _T("event_cfg"), _T("event_name"), name))
303 {
304 nxlog_debug(4, _T("ImportEvent: found existing event with name %s"), name);
305 _sntprintf(query, 8192, _T("UPDATE event_cfg SET severity=%d,flags=%d,message=%s,description=%s WHERE event_name=%s"),
306 event->getSubEntryValueAsInt(_T("severity")),
307 event->getSubEntryValueAsInt(_T("flags")), (const TCHAR *)DBPrepareString(hdb, msg),
308 (const TCHAR *)DBPrepareString(hdb, descr), (const TCHAR *)DBPrepareString(hdb, name));
309 }
310 else
311 {
312 if (guid.isNull())
313 guid = uuid::generate();
314 if (code == 0)
315 code = CreateUniqueId(IDG_EVENT);
316 _sntprintf(query, 8192, _T("INSERT INTO event_cfg (event_code,event_name,severity,flags,")
317 _T("message,description,guid) VALUES (%d,%s,%d,%d,%s,%s,'%s')"),
318 code, (const TCHAR *)DBPrepareString(hdb, name), event->getSubEntryValueAsInt(_T("severity")),
319 event->getSubEntryValueAsInt(_T("flags")), (const TCHAR *)DBPrepareString(hdb, msg),
320 (const TCHAR *)DBPrepareString(hdb, descr), (const TCHAR *)guid.toString());
321 nxlog_debug(4, _T("ImportEvent: added new event: code=%d, name=%s, guid=%s"), code, name, (const TCHAR *)guid.toString());
322 }
323 UINT32 rcc = DBQuery(hdb, query) ? RCC_SUCCESS : RCC_DB_FAILURE;
324
325 DBConnectionPoolReleaseConnection(hdb);
326 return rcc;
327 }
328
329 /**
330 * Import SNMP trap configuration
331 */
332 static UINT32 ImportTrap(ConfigEntry *trap) // TODO transactions needed?
333 {
334 UINT32 rcc = RCC_INTERNAL_ERROR;
335 EventTemplate *eventTemplate = FindEventTemplateByName(trap->getSubEntryValue(_T("event"), 0, _T("")));
336 if (eventTemplate == NULL)
337 return rcc;
338
339 uuid guid = trap->getSubEntryValueAsUUID(_T("guid"));
340 if (guid.isNull())
341 {
342 guid = uuid::generate();
343 nxlog_debug(4, _T("ImportTrap: GUID not found in config, generated GUID %s"), (const TCHAR *)guid.toString());
344 }
345 UINT32 id = ResolveTrapGuid(guid);
346 SNMPTrapConfiguration *trapCfg = new SNMPTrapConfiguration(trap, guid, id, eventTemplate->getCode());
347 eventTemplate->decRefCount();
348
349 if (!trapCfg->getOid().isValid())
350 {
351 delete trapCfg;
352 return rcc;
353 }
354
355 DB_HANDLE hdb = DBConnectionPoolAcquireConnection();
356 DB_STATEMENT hStmt;
357 if (id == 0)
358 {
359 hStmt = DBPrepare(hdb, _T("INSERT INTO snmp_trap_cfg (snmp_oid,event_code,description,user_tag,trap_id,guid) VALUES (?,?,?,?,?,?)"));
360 }
361 else
362 hStmt = DBPrepare(hdb, _T("UPDATE snmp_trap_cfg SET snmp_oid=?,event_code=?,description=?,user_tag=? WHERE trap_id=?"));
363
364 if (hStmt != NULL)
365 {
366 TCHAR oid[1024];
367 trapCfg->getOid().toString(oid, 1024);
368 DBBind(hStmt, 1, DB_SQLTYPE_VARCHAR, oid, DB_BIND_STATIC);
369 DBBind(hStmt, 2, DB_SQLTYPE_INTEGER, trapCfg->getEventCode());
370 DBBind(hStmt, 3, DB_SQLTYPE_VARCHAR, trapCfg->getDescription(), DB_BIND_STATIC);
371 DBBind(hStmt, 4, DB_SQLTYPE_VARCHAR, trapCfg->getUserTag(), DB_BIND_STATIC);
372 DBBind(hStmt, 5, DB_SQLTYPE_INTEGER, trapCfg->getId());
373 if (id == 0)
374 DBBind(hStmt, 6, DB_SQLTYPE_VARCHAR, trapCfg->getGuid());
375
376 if (DBBegin(hdb))
377 {
378 if(DBExecute(hStmt) && trapCfg->saveParameterMapping(hdb))
379 {
380 AddTrapCfgToList(trapCfg);
381 trapCfg->notifyOnTrapCfgChange(NX_NOTIFY_TRAPCFG_CREATED);
382 rcc = RCC_SUCCESS;
383 DBCommit(hdb);
384 }
385 else
386 {
387 DBRollback(hdb);
388 rcc = RCC_DB_FAILURE;
389 }
390 }
391 else
392 rcc = RCC_DB_FAILURE;
393 DBFreeStatement(hStmt);
394 }
395 else
396 rcc = RCC_DB_FAILURE;
397
398 if (rcc != RCC_SUCCESS)
399 delete trapCfg;
400
401 DBConnectionPoolReleaseConnection(hdb);
402
403 return rcc;
404 }
405
406 /**
407 * Find (and create as necessary) parent object for imported template
408 */
409 NetObj *FindTemplateRoot(ConfigEntry *config)
410 {
411 ConfigEntry *pathRoot = config->findEntry(_T("path"));
412 if (pathRoot == NULL)
413 return g_pTemplateRoot; // path not specified in config
414
415 NetObj *parent = g_pTemplateRoot;
416 ObjectArray<ConfigEntry> *path = pathRoot->getSubEntries(_T("element#*"));
417 for(int i = 0; i < path->size(); i++)
418 {
419 const TCHAR *name = path->get(i)->getValue();
420 NetObj *o = parent->findChildObject(name, OBJECT_TEMPLATEGROUP);
421 if (o == NULL)
422 {
423 o = new TemplateGroup(name);
424 NetObjInsert(o, true, false);
425 o->addParent(parent);
426 parent->addChild(o);
427 o->unhide();
428 o->calculateCompoundStatus(); // Force status change to NORMAL
429 }
430 parent = o;
431 }
432 delete path;
433 return parent;
434 }
435
436 /**
437 * Import configuration
438 */
439 UINT32 ImportConfig(Config *config, UINT32 flags)
440 {
441 ObjectArray<ConfigEntry> *events = NULL, *traps = NULL, *templates = NULL, *rules = NULL,
442 *scripts = NULL, *objectTools = NULL, *summaryTables = NULL, *actions = NULL;
443 ConfigEntry *eventsRoot, *trapsRoot, *templatesRoot, *rulesRoot,
444 *scriptsRoot, *objectToolsRoot, *summaryTablesRoot, *actionsRoot;
445 UINT32 rcc = RCC_SUCCESS;
446 int i;
447
448 DbgPrintf(4, _T("ImportConfig() called, flags=0x%04X"), flags);
449
450 // Import events
451 eventsRoot = config->getEntry(_T("/events"));
452 if (eventsRoot != NULL)
453 {
454 events = eventsRoot->getSubEntries(_T("event#*"));
455 DbgPrintf(5, _T("ImportConfig(): %d events to import"), events->size());
456 for(i = 0; i < events->size(); i++)
457 {
458 rcc = ImportEvent(events->get(i));
459 if (rcc != RCC_SUCCESS)
460 goto stop_processing;
461 }
462
463 if (events->size() > 0)
464 {
465 ReloadEvents();
466 NotifyClientSessions(NX_NOTIFY_RELOAD_EVENT_DB, 0);
467 }
468 DbgPrintf(5, _T("ImportConfig(): events imported"));
469 }
470
471 // Import traps
472 trapsRoot = config->getEntry(_T("/traps"));
473 if (trapsRoot != NULL)
474 {
475 traps = trapsRoot->getSubEntries(_T("trap#*"));
476 DbgPrintf(5, _T("ImportConfig(): %d SNMP traps to import"), traps->size());
477 for(i = 0; i < traps->size(); i++)
478 {
479 rcc = ImportTrap(traps->get(i));
480 if (rcc != RCC_SUCCESS)
481 goto stop_processing;
482 }
483 DbgPrintf(5, _T("ImportConfig(): SNMP traps imported"));
484 }
485
486 // Import templates
487 templatesRoot = config->getEntry(_T("/templates"));
488 if (templatesRoot != NULL)
489 {
490 templates = templatesRoot->getSubEntries(_T("template#*"));
491 for(i = 0; i < templates->size(); i++)
492 {
493 ConfigEntry *tc = templates->get(i);
494 uuid guid = tc->getSubEntryValueAsUUID(_T("guid"));
495 Template *object = (Template *)FindObjectByGUID(guid, OBJECT_TEMPLATE);
496 if (object != NULL)
497 {
498 DbgPrintf(5, _T("ImportConfig(): found existing template %s [%d] with GUID %s"), object->getName(), object->getId(), (const TCHAR *)guid.toString());
499 object->updateFromImport(tc);
500 }
501 else
502 {
503 DbgPrintf(5, _T("ImportConfig(): template with GUID %s not found"), (const TCHAR *)guid.toString());
504 NetObj *parent = FindTemplateRoot(tc);
505 object = new Template(tc);
506 NetObjInsert(object, true, true);
507 object->addParent(parent);
508 parent->addChild(object);
509 object->unhide();
510 }
511 }
512 DbgPrintf(5, _T("ImportConfig(): templates imported"));
513 }
514
515 // Import rules
516 rulesRoot = config->getEntry(_T("/rules"));
517 if (rulesRoot != NULL)
518 {
519 rules = rulesRoot->getOrderedSubEntries(_T("rule#*"));
520 if (rules->size() > 0)
521 {
522 for(i = 0; i < rules->size(); i++)
523 {
524 EPRule *rule = new EPRule(rules->get(i));
525 g_pEventPolicy->importRule(rule);
526 }
527 if(!g_pEventPolicy->saveToDB())
528 {
529 DbgPrintf(5, _T("ImportConfig(): unable to import event processing policy rules"));
530 rcc = RCC_DB_FAILURE;
531 goto stop_processing;
532 }
533 }
534 DbgPrintf(5, _T("ImportConfig(): event processing policy rules imported"));
535 }
536
537 // Import scripts
538 scriptsRoot = config->getEntry(_T("/scripts"));
539 if (scriptsRoot != NULL)
540 {
541 scripts = scriptsRoot->getSubEntries(_T("script#*"));
542 for(i = 0; i < scripts->size(); i++)
543 {
544 ImportScript(scripts->get(i));
545 }
546 DbgPrintf(5, _T("ImportConfig(): scripts imported"));
547 }
548
549 // Import object tools
550 objectToolsRoot = config->getEntry(_T("/objectTools"));
551 if (objectToolsRoot != NULL)
552 {
553 objectTools = objectToolsRoot->getSubEntries(_T("objectTool#*"));
554 for(i = 0; i < objectTools->size(); i++)
555 {
556 ImportObjectTool(objectTools->get(i));
557 }
558 DbgPrintf(5, _T("ImportConfig(): object tools imported"));
559 }
560
561 // Import summary tables
562 summaryTablesRoot = config->getEntry(_T("/dciSummaryTables"));
563 if (summaryTablesRoot != NULL)
564 {
565 summaryTables = summaryTablesRoot->getSubEntries(_T("table#*"));
566 for(i = 0; i < summaryTables->size(); i++)
567 {
568 ImportSummaryTable(summaryTables->get(i));
569 }
570 DbgPrintf(5, _T("ImportConfig(): DCI summary tables imported"));
571 }
572
573 // Import summary tables
574 actionsRoot = config->getEntry(_T("/actions"));
575 if (actionsRoot != NULL)
576 {
577 actions = actionsRoot->getSubEntries(_T("action#*"));
578 for(i = 0; i < actions->size(); i++)
579 {
580 ImportAction(actions->get(i));
581 }
582 DbgPrintf(5, _T("ImportConfig(): Actions imported"));
583 }
584
585 stop_processing:
586 delete events;
587 delete traps;
588 delete templates;
589 delete rules;
590 delete scripts;
591 delete objectTools;
592 delete summaryTables;
593 delete actions;
594
595 DbgPrintf(4, _T("ImportConfig() finished, rcc = %d"), rcc);
596 return rcc;
597 }
598
599 /**
600 * Import local configuration (configuration files stored on server)
601 */
602 void ImportLocalConfiguration()
603 {
604 TCHAR path[MAX_PATH];
605 GetNetXMSDirectory(nxDirShare, path);
606 _tcscat(path, SDIR_TEMPLATES);
607
608 int count = 0;
609 DbgPrintf(1, _T("Import configuration files from %s"), path);
610 _TDIR *dir = _topendir(path);
611 if (dir != NULL)
612 {
613 _tcscat(path, FS_PATH_SEPARATOR);
614 int insPos = (int)_tcslen(path);
615
616 struct _tdirent *f;
617 while((f = _treaddir(dir)) != NULL)
618 {
619 if (MatchString(_T("*.xml"), f->d_name, FALSE))
620 {
621 _tcscpy(&path[insPos], f->d_name);
622 Config *config = new Config(false);
623 if (config->loadXmlConfig(path, "configuration"))
624 {
625 ImportConfig(config, CFG_IMPORT_REPLACE_EVENT_BY_CODE | CFG_IMPORT_REPLACE_EVENT_BY_NAME);
626 }
627 else
628 {
629 DbgPrintf(1, _T("Error loading configuration from %s"), path);
630 }
631 delete config;
632 }
633 }
634 _tclosedir(dir);
635 }
636 DbgPrintf(1, _T("%d configuration files processed"), count);
637 }