Bring consistency to the use of capitals in programs messages
[ntk/apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
5e14c452 3// $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz Exp $
6c139d6e
AL
4/* ######################################################################
5
6 Configuration Class
7
8 This class provides a configuration file and command line parser
9 for a tree-oriented configuration environment. All runtime configuration
10 is stored in here.
7da2b375
AL
11
12 This source is placed in the Public Domain, do with it what you will
13 It was originally written by Jason Gunthorpe <jgg@debian.org>.
6c139d6e
AL
14
15 ##################################################################### */
16 /*}}}*/
17// Include files /*{{{*/
18#ifdef __GNUG__
094a497d 19#pragma implementation "apt-pkg/configuration.h"
6c139d6e 20#endif
094a497d 21#include <apt-pkg/configuration.h>
08e8f724 22#include <apt-pkg/error.h>
cdcc6d34 23#include <apt-pkg/strutl.h>
b2e465d6
AL
24#include <apt-pkg/fileutl.h>
25#include <apti18n.h>
6c139d6e 26
b2e465d6
AL
27#include <vector>
28#include <algorithm>
29#include <fstream>
851a45a8 30#include <iostream>
b2e465d6 31
6c139d6e 32#include <stdio.h>
b2e465d6
AL
33#include <dirent.h>
34#include <sys/stat.h>
35#include <unistd.h>
851a45a8
AL
36
37using namespace std;
6c139d6e 38 /*}}}*/
9c14e3d6
AL
39
40Configuration *_config = new Configuration;
6c139d6e
AL
41
42// Configuration::Configuration - Constructor /*{{{*/
43// ---------------------------------------------------------------------
44/* */
b2e465d6 45Configuration::Configuration() : ToFree(true)
6c139d6e
AL
46{
47 Root = new Item;
b2e465d6
AL
48}
49Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
50{
51};
52
53 /*}}}*/
54// Configuration::~Configuration - Destructor /*{{{*/
55// ---------------------------------------------------------------------
56/* */
57Configuration::~Configuration()
58{
59 if (ToFree == false)
60 return;
61
62 Item *Top = Root;
63 for (; Top != 0;)
64 {
65 if (Top->Child != 0)
66 {
67 Top = Top->Child;
68 continue;
69 }
70
71 while (Top != 0 && Top->Next == 0)
72 {
73 Item *Parent = Top->Parent;
74 delete Top;
75 Top = Parent;
76 }
77 if (Top != 0)
78 {
79 Item *Next = Top->Next;
80 delete Top;
81 Top = Next;
82 }
83 }
6c139d6e
AL
84}
85 /*}}}*/
0a8a80e5 86// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
87// ---------------------------------------------------------------------
88/* This will lookup a single item by name below another item. It is a
89 helper function for the main lookup function */
90Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
91 unsigned long Len,bool Create)
92{
93 int Res = 1;
94 Item *I = Head->Child;
95 Item **Last = &Head->Child;
9c14e3d6 96
7f25bdff
AL
97 // Empty strings match nothing. They are used for lists.
98 if (Len != 0)
99 {
100 for (; I != 0; Last = &I->Next, I = I->Next)
851a45a8 101 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
7f25bdff
AL
102 break;
103 }
104 else
105 for (; I != 0; Last = &I->Next, I = I->Next);
106
6c139d6e
AL
107 if (Res == 0)
108 return I;
109 if (Create == false)
110 return 0;
111
112 I = new Item;
9c14e3d6 113 I->Tag = string(S,Len);
6c139d6e 114 I->Next = *Last;
9c14e3d6 115 I->Parent = Head;
6c139d6e
AL
116 *Last = I;
117 return I;
118}
119 /*}}}*/
120// Configuration::Lookup - Lookup a fully scoped item /*{{{*/
121// ---------------------------------------------------------------------
122/* This performs a fully scoped lookup of a given name, possibly creating
123 new items */
124Configuration::Item *Configuration::Lookup(const char *Name,bool Create)
125{
0a8a80e5
AL
126 if (Name == 0)
127 return Root->Child;
128
6c139d6e
AL
129 const char *Start = Name;
130 const char *End = Start + strlen(Name);
131 const char *TagEnd = Name;
132 Item *Itm = Root;
7f25bdff 133 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
134 {
135 if (TagEnd[0] == ':' && TagEnd[1] == ':')
136 {
137 Itm = Lookup(Itm,Start,TagEnd - Start,Create);
138 if (Itm == 0)
139 return 0;
140 TagEnd = Start = TagEnd + 2;
141 }
142 }
143
7f25bdff
AL
144 // This must be a trailing ::, we create unique items in a list
145 if (End - Start == 0)
146 {
147 if (Create == false)
148 return 0;
149 }
150
6c139d6e 151 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
152 return Itm;
153}
154 /*}}}*/
155// Configuration::Find - Find a value /*{{{*/
156// ---------------------------------------------------------------------
157/* */
b2e465d6 158string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 159{
b2e465d6 160 const Item *Itm = Lookup(Name);
6c139d6e 161 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
162 {
163 if (Default == 0)
164 return string();
165 else
166 return Default;
167 }
168
6c139d6e
AL
169 return Itm->Value;
170}
171 /*}}}*/
3b5421b4 172// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
173// ---------------------------------------------------------------------
174/* Directories are stored as the base dir in the Parent node and the
3b5421b4 175 sub directory in sub nodes with the final node being the end filename
9c14e3d6 176 */
b2e465d6 177string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 178{
b2e465d6 179 const Item *Itm = Lookup(Name);
9c14e3d6
AL
180 if (Itm == 0 || Itm->Value.empty() == true)
181 {
182 if (Default == 0)
183 return string();
184 else
185 return Default;
186 }
187
b2e465d6
AL
188 string val = Itm->Value;
189 while (Itm->Parent != 0 && Itm->Parent->Value.empty() == false)
190 {
191 // Absolute
192 if (val.length() >= 1 && val[0] == '/')
193 break;
194
195 // ~/foo or ./foo
196 if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
197 break;
198
199 // ../foo
200 if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
201 break;
202
203 if (Itm->Parent->Value.end()[-1] != '/')
204 val.insert(0, "/");
205
206 val.insert(0, Itm->Parent->Value);
207 Itm = Itm->Parent;
208 }
209
210 return val;
9c14e3d6
AL
211}
212 /*}}}*/
3b5421b4
AL
213// Configuration::FindDir - Find a directory name /*{{{*/
214// ---------------------------------------------------------------------
215/* This is like findfile execept the result is terminated in a / */
b2e465d6 216string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
217{
218 string Res = FindFile(Name,Default);
219 if (Res.end()[-1] != '/')
220 return Res + '/';
221 return Res;
222}
223 /*}}}*/
6c139d6e
AL
224// Configuration::FindI - Find an integer value /*{{{*/
225// ---------------------------------------------------------------------
226/* */
b2e465d6 227int Configuration::FindI(const char *Name,int Default) const
6c139d6e 228{
b2e465d6 229 const Item *Itm = Lookup(Name);
6c139d6e
AL
230 if (Itm == 0 || Itm->Value.empty() == true)
231 return Default;
232
233 char *End;
234 int Res = strtol(Itm->Value.c_str(),&End,0);
235 if (End == Itm->Value.c_str())
236 return Default;
237
08e8f724
AL
238 return Res;
239}
240 /*}}}*/
241// Configuration::FindB - Find a boolean type /*{{{*/
242// ---------------------------------------------------------------------
243/* */
b2e465d6 244bool Configuration::FindB(const char *Name,bool Default) const
08e8f724 245{
b2e465d6 246 const Item *Itm = Lookup(Name);
08e8f724
AL
247 if (Itm == 0 || Itm->Value.empty() == true)
248 return Default;
249
3b5421b4 250 return StringToBool(Itm->Value,Default);
6c139d6e
AL
251}
252 /*}}}*/
b2e465d6
AL
253// Configuration::FindAny - Find an arbitrary type /*{{{*/
254// ---------------------------------------------------------------------
255/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
256string Configuration::FindAny(const char *Name,const char *Default) const
257{
258 string key = Name;
259 char type = 0;
260
261 if (key.size() > 2 && key.end()[-2] == '/')
262 {
263 type = key.end()[-1];
264 key.resize(key.size() - 2);
265 }
266
267 switch (type)
268 {
269 // file
270 case 'f':
271 return FindFile(key.c_str(), Default);
272
273 // directory
274 case 'd':
275 return FindDir(key.c_str(), Default);
276
277 // bool
278 case 'b':
279 return FindB(key, Default) ? "true" : "false";
280
281 // int
282 case 'i':
283 {
284 char buf[16];
cc99a102 285 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
286 return buf;
287 }
288 }
289
290 // fallback
291 return Find(Name, Default);
292}
293 /*}}}*/
294// Configuration::CndSet - Conditinal Set a value /*{{{*/
295// ---------------------------------------------------------------------
296/* This will not overwrite */
297void Configuration::CndSet(const char *Name,string Value)
298{
299 Item *Itm = Lookup(Name,true);
300 if (Itm == 0)
301 return;
302 if (Itm->Value.empty() == true)
303 Itm->Value = Value;
304}
305 /*}}}*/
6c139d6e
AL
306// Configuration::Set - Set a value /*{{{*/
307// ---------------------------------------------------------------------
308/* */
309void Configuration::Set(const char *Name,string Value)
310{
311 Item *Itm = Lookup(Name,true);
312 if (Itm == 0)
313 return;
314 Itm->Value = Value;
315}
316 /*}}}*/
317// Configuration::Set - Set an integer value /*{{{*/
318// ---------------------------------------------------------------------
319/* */
320void Configuration::Set(const char *Name,int Value)
321{
322 Item *Itm = Lookup(Name,true);
323 if (Itm == 0)
324 return;
325 char S[300];
326 snprintf(S,sizeof(S),"%i",Value);
327 Itm->Value = S;
328}
329 /*}}}*/
b2e465d6
AL
330// Configuration::Clear - Clear an entire tree /*{{{*/
331// ---------------------------------------------------------------------
332/* */
333void Configuration::Clear(string Name)
334{
335 Item *Top = Lookup(Name.c_str(),false);
336 if (Top == 0)
337 return;
338
339 Top->Value = string();
340 Item *Stop = Top;
341 Top = Top->Child;
342 Stop->Child = 0;
343 for (; Top != 0;)
344 {
345 if (Top->Child != 0)
346 {
347 Top = Top->Child;
348 continue;
349 }
350
351 while (Top != 0 && Top->Next == 0)
352 {
353 Item *Tmp = Top;
354 Top = Top->Parent;
355 delete Tmp;
356
357 if (Top == Stop)
358 return;
359 }
360
361 Item *Tmp = Top;
362 if (Top != 0)
363 Top = Top->Next;
364 delete Tmp;
365 }
366}
367 /*}}}*/
08e8f724
AL
368// Configuration::Exists - Returns true if the Name exists /*{{{*/
369// ---------------------------------------------------------------------
370/* */
b2e465d6 371bool Configuration::Exists(const char *Name) const
08e8f724 372{
b2e465d6 373 const Item *Itm = Lookup(Name);
08e8f724
AL
374 if (Itm == 0)
375 return false;
376 return true;
377}
378 /*}}}*/
b2e465d6
AL
379// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
380// ---------------------------------------------------------------------
381/* qualified by /[fdbi] exists */
382bool Configuration::ExistsAny(const char *Name) const
383{
384 string key = Name;
385
8d998026
AL
386 if (key.size() > 2 && key.end()[-2] == '/')
387 if (key.find_first_of("fdbi",key.size()-1) < key.size())
388 {
389 key.resize(key.size() - 2);
390 if (Exists(key.c_str()))
391 return true;
392 }
393 else
394 {
69a63027 395 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 396 }
b2e465d6
AL
397
398 return Exists(Name);
399}
400 /*}}}*/
93bf083d
AL
401// Configuration::Dump - Dump the config /*{{{*/
402// ---------------------------------------------------------------------
403/* Dump the entire configuration space */
ff2a211a 404void Configuration::Dump(ostream& str)
93bf083d
AL
405{
406 /* Write out all of the configuration directives by walking the
407 configuration tree */
b2e465d6 408 const Configuration::Item *Top = Tree(0);
93bf083d
AL
409 for (; Top != 0;)
410 {
ff2a211a 411 str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
93bf083d
AL
412
413 if (Top->Child != 0)
414 {
415 Top = Top->Child;
416 continue;
417 }
418
419 while (Top != 0 && Top->Next == 0)
420 Top = Top->Parent;
421 if (Top != 0)
422 Top = Top->Next;
b2e465d6 423 }
93bf083d
AL
424}
425 /*}}}*/
08e8f724 426
0a8a80e5
AL
427// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
428// ---------------------------------------------------------------------
b2e465d6
AL
429/* Stop sets an optional max recursion depth if this item is being viewed as
430 part of a sub tree. */
431string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 432{
b2e465d6 433 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 434 return Tag;
b2e465d6 435 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
436}
437 /*}}}*/
438
08e8f724
AL
439// ReadConfigFile - Read a configuration file /*{{{*/
440// ---------------------------------------------------------------------
441/* The configuration format is very much like the named.conf format
b2e465d6
AL
442 used in bind8, in fact this routine can parse most named.conf files.
443 Sectional config files are like bind's named.conf where there are
444 sections like 'zone "foo.org" { .. };' This causes each section to be
445 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 446 tag/value. AsSectional enables Sectional parsing.*/
b2e465d6
AL
447bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
448 unsigned Depth)
449{
08e8f724 450 // Open the stream for reading
851a45a8 451 ifstream F(FName.c_str(),ios::in);
08e8f724 452 if (!F != 0)
b2e465d6 453 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
08e8f724 454
5e14c452 455 char Buffer[1024];
08e8f724 456 string LineBuffer;
a4e87467
AL
457 string Stack[100];
458 unsigned int StackPos = 0;
08e8f724
AL
459
460 // Parser state
461 string ParentTag;
462
463 int CurLine = 0;
464 bool InComment = false;
465 while (F.eof() == false)
466 {
467 F.getline(Buffer,sizeof(Buffer));
468 CurLine++;
5e14c452
AL
469 // This should be made to work instead, but this is better than looping
470 if (F.fail() && !F.eof())
471 return _error->Error(_("Line %d too long (max %d)"), CurLine, sizeof(Buffer));
472
08e8f724
AL
473 _strtabexpand(Buffer,sizeof(Buffer));
474 _strstrip(Buffer);
475
476 // Multi line comment
477 if (InComment == true)
478 {
479 for (const char *I = Buffer; *I != 0; I++)
480 {
481 if (*I == '*' && I[1] == '/')
482 {
483 memmove(Buffer,I+2,strlen(I+2) + 1);
484 InComment = false;
485 break;
486 }
487 }
488 if (InComment == true)
489 continue;
490 }
491
492 // Discard single line comments
bfd22fc0 493 bool InQuote = false;
08e8f724
AL
494 for (char *I = Buffer; *I != 0; I++)
495 {
bfd22fc0
AL
496 if (*I == '"')
497 InQuote = !InQuote;
498 if (InQuote == true)
499 continue;
500
08e8f724
AL
501 if (*I == '/' && I[1] == '/')
502 {
503 *I = 0;
504 break;
505 }
506 }
7834cb57 507
08e8f724 508 // Look for multi line comments
7834cb57 509 InQuote = false;
08e8f724
AL
510 for (char *I = Buffer; *I != 0; I++)
511 {
bfd22fc0
AL
512 if (*I == '"')
513 InQuote = !InQuote;
514 if (InQuote == true)
515 continue;
516
08e8f724
AL
517 if (*I == '/' && I[1] == '*')
518 {
519 InComment = true;
520 for (char *J = Buffer; *J != 0; J++)
521 {
522 if (*J == '*' && J[1] == '/')
523 {
524 memmove(I,J+2,strlen(J+2) + 1);
525 InComment = false;
526 break;
527 }
528 }
529
530 if (InComment == true)
531 {
532 *I = 0;
533 break;
534 }
535 }
536 }
537
538 // Blank
539 if (Buffer[0] == 0)
540 continue;
541
542 // We now have a valid line fragment
7834cb57 543 InQuote = false;
08e8f724
AL
544 for (char *I = Buffer; *I != 0;)
545 {
7834cb57
AL
546 if (*I == '"')
547 InQuote = !InQuote;
548
549 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 550 {
b2e465d6 551 // Put the last fragment into the buffer
08e8f724
AL
552 char *Start = Buffer;
553 char *Stop = I;
554 for (; Start != I && isspace(*Start) != 0; Start++);
555 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
556 if (LineBuffer.empty() == false && Stop - Start != 0)
557 LineBuffer += ' ';
558 LineBuffer += string(Start,Stop - Start);
559
560 // Remove the fragment
561 char TermChar = *I;
562 memmove(Buffer,I + 1,strlen(I + 1) + 1);
563 I = Buffer;
08e8f724
AL
564
565 // Syntax Error
566 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 567 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 568
b2e465d6 569 // No string on this line
08e8f724 570 if (LineBuffer.empty() == true)
b2e465d6
AL
571 {
572 if (TermChar == '}')
573 {
574 if (StackPos == 0)
575 ParentTag = string();
576 else
577 ParentTag = Stack[--StackPos];
578 }
08e8f724 579 continue;
b2e465d6
AL
580 }
581
08e8f724 582 // Parse off the tag
7f25bdff
AL
583 string Tag;
584 const char *Pos = LineBuffer.c_str();
585 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 586 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
587
588 // Parse off the word
589 string Word;
bcae6dd4 590 bool NoWord = false;
b2e465d6
AL
591 if (ParseCWord(Pos,Word) == false &&
592 ParseQuoteWord(Pos,Word) == false)
593 {
594 if (TermChar != '{')
595 {
596 Word = Tag;
597 Tag = "";
bcae6dd4 598 }
4ae405e9
AL
599 else
600 NoWord = true;
b2e465d6
AL
601 }
602 if (strlen(Pos) != 0)
603 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
604
08e8f724
AL
605 // Go down a level
606 if (TermChar == '{')
607 {
a4e87467
AL
608 if (StackPos <= 100)
609 Stack[StackPos++] = ParentTag;
b2e465d6
AL
610
611 /* Make sectional tags incorperate the section into the
612 tag string */
613 if (AsSectional == true && Word.empty() == false)
614 {
615 Tag += "::" ;
616 Tag += Word;
617 Word = "";
618 }
619
08e8f724
AL
620 if (ParentTag.empty() == true)
621 ParentTag = Tag;
622 else
623 ParentTag += string("::") + Tag;
624 Tag = string();
625 }
bfd22fc0 626
08e8f724
AL
627 // Generate the item name
628 string Item;
629 if (ParentTag.empty() == true)
630 Item = Tag;
631 else
632 {
7f25bdff 633 if (TermChar != '{' || Tag.empty() == false)
08e8f724 634 Item = ParentTag + "::" + Tag;
7f25bdff
AL
635 else
636 Item = ParentTag;
08e8f724
AL
637 }
638
b2e465d6
AL
639 // Specials
640 if (Tag.length() >= 1 && Tag[0] == '#')
641 {
642 if (ParentTag.empty() == false)
643 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
644 Tag.erase(Tag.begin());
645 if (Tag == "clear")
646 Conf.Clear(Word);
647 else if (Tag == "include")
648 {
649 if (Depth > 10)
650 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
651 if (Word.length() > 2 && Word.end()[-1] == '/')
652 {
653 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
654 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
655 }
656 else
657 {
658 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
659 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
660 }
661 }
662 else
663 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
664 }
665 else
666 {
667 // Set the item in the configuration class
bcae6dd4
AL
668 if (NoWord == false)
669 Conf.Set(Item,Word);
b2e465d6
AL
670 }
671
08e8f724
AL
672 // Empty the buffer
673 LineBuffer = string();
b2e465d6
AL
674
675 // Move up a tag, but only if there is no bit to parse
676 if (TermChar == '}')
677 {
678 if (StackPos == 0)
679 ParentTag = string();
680 else
681 ParentTag = Stack[--StackPos];
682 }
683
08e8f724
AL
684 }
685 else
686 I++;
687 }
688
689 // Store the fragment
690 const char *Stripd = _strstrip(Buffer);
691 if (*Stripd != 0 && LineBuffer.empty() == false)
692 LineBuffer += " ";
693 LineBuffer += Stripd;
694 }
b2e465d6
AL
695
696 if (LineBuffer.empty() == false)
697 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
698 return true;
699}
700 /*}}}*/
701// ReadConfigDir - Read a directory of config files /*{{{*/
702// ---------------------------------------------------------------------
703/* */
704bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
705 unsigned Depth)
706{
b2e465d6
AL
707 DIR *D = opendir(Dir.c_str());
708 if (D == 0)
709 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
710
711 vector<string> List;
712
713 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
714 {
721e08d6 715 if (Ent->d_name[0] == '.')
b2e465d6
AL
716 continue;
717
bb3a5465
AL
718 // Skip bad file names ala run-parts
719 const char *C = Ent->d_name;
720 for (; *C != 0; C++)
721 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 722 break;
bb3a5465 723 if (*C != 0)
b2e465d6
AL
724 continue;
725
726 // Make sure it is a file and not something else
727 string File = flCombine(Dir,Ent->d_name);
728 struct stat St;
729 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
730 continue;
731
732 List.push_back(File);
733 }
734 closedir(D);
08e8f724 735
b2e465d6
AL
736 sort(List.begin(),List.end());
737
738 // Read the files
739 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
740 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
741 return false;
08e8f724
AL
742 return true;
743}
744 /*}}}*/