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