]> git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
b83ece3e47935d0f1213ea872e8a3bc6c092fcde
[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::FindVector - Find a vector of values /*{{{*/
227 // ---------------------------------------------------------------------
228 /* Returns a vector of config values under the given item */
229 vector<string> Configuration::FindVector(const char *Name) const
230 {
231 vector<string> Vec;
232 const Item *Top = Lookup(Name);
233 if (Top == NULL)
234 return Vec;
235
236 Item *I = Top->Child;
237 while(I != NULL)
238 {
239 Vec.push_back(I->Value);
240 I = I->Next;
241 }
242 return Vec;
243 }
244 /*}}}*/
245 // Configuration::FindI - Find an integer value /*{{{*/
246 // ---------------------------------------------------------------------
247 /* */
248 int Configuration::FindI(const char *Name,int Default) const
249 {
250 const Item *Itm = Lookup(Name);
251 if (Itm == 0 || Itm->Value.empty() == true)
252 return Default;
253
254 char *End;
255 int Res = strtol(Itm->Value.c_str(),&End,0);
256 if (End == Itm->Value.c_str())
257 return Default;
258
259 return Res;
260 }
261 /*}}}*/
262 // Configuration::FindB - Find a boolean type /*{{{*/
263 // ---------------------------------------------------------------------
264 /* */
265 bool Configuration::FindB(const char *Name,bool Default) const
266 {
267 const Item *Itm = Lookup(Name);
268 if (Itm == 0 || Itm->Value.empty() == true)
269 return Default;
270
271 return StringToBool(Itm->Value,Default);
272 }
273 /*}}}*/
274 // Configuration::FindAny - Find an arbitrary type /*{{{*/
275 // ---------------------------------------------------------------------
276 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
277 string Configuration::FindAny(const char *Name,const char *Default) const
278 {
279 string key = Name;
280 char type = 0;
281
282 if (key.size() > 2 && key.end()[-2] == '/')
283 {
284 type = key.end()[-1];
285 key.resize(key.size() - 2);
286 }
287
288 switch (type)
289 {
290 // file
291 case 'f':
292 return FindFile(key.c_str(), Default);
293
294 // directory
295 case 'd':
296 return FindDir(key.c_str(), Default);
297
298 // bool
299 case 'b':
300 return FindB(key, Default) ? "true" : "false";
301
302 // int
303 case 'i':
304 {
305 char buf[16];
306 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
307 return buf;
308 }
309 }
310
311 // fallback
312 return Find(Name, Default);
313 }
314 /*}}}*/
315 // Configuration::CndSet - Conditinal Set a value /*{{{*/
316 // ---------------------------------------------------------------------
317 /* This will not overwrite */
318 void Configuration::CndSet(const char *Name,const string &Value)
319 {
320 Item *Itm = Lookup(Name,true);
321 if (Itm == 0)
322 return;
323 if (Itm->Value.empty() == true)
324 Itm->Value = Value;
325 }
326 /*}}}*/
327 // Configuration::Set - Set a value /*{{{*/
328 // ---------------------------------------------------------------------
329 /* */
330 void Configuration::Set(const char *Name,const string &Value)
331 {
332 Item *Itm = Lookup(Name,true);
333 if (Itm == 0)
334 return;
335 Itm->Value = Value;
336 }
337 /*}}}*/
338 // Configuration::Set - Set an integer value /*{{{*/
339 // ---------------------------------------------------------------------
340 /* */
341 void Configuration::Set(const char *Name,int Value)
342 {
343 Item *Itm = Lookup(Name,true);
344 if (Itm == 0)
345 return;
346 char S[300];
347 snprintf(S,sizeof(S),"%i",Value);
348 Itm->Value = S;
349 }
350 /*}}}*/
351 // Configuration::Clear - Clear an single value from a list /*{{{*/
352 // ---------------------------------------------------------------------
353 /* */
354 void Configuration::Clear(const string Name, int Value)
355 {
356 char S[300];
357 snprintf(S,sizeof(S),"%i",Value);
358 Clear(Name, S);
359 }
360 /*}}}*/
361 // Configuration::Clear - Clear an single value from a list /*{{{*/
362 // ---------------------------------------------------------------------
363 /* */
364 void Configuration::Clear(const string Name, string Value)
365 {
366 Item *Top = Lookup(Name.c_str(),false);
367 if (Top == 0 || Top->Child == 0)
368 return;
369
370 Item *Tmp, *Prev, *I;
371 Prev = I = Top->Child;
372
373 while(I != NULL)
374 {
375 if(I->Value == Value)
376 {
377 Tmp = I;
378 // was first element, point parent to new first element
379 if(Top->Child == Tmp)
380 Top->Child = I->Next;
381 I = I->Next;
382 Prev->Next = I;
383 delete Tmp;
384 } else {
385 Prev = I;
386 I = I->Next;
387 }
388 }
389
390 }
391 /*}}}*/
392 // Configuration::Clear - Clear an entire tree /*{{{*/
393 // ---------------------------------------------------------------------
394 /* */
395 void Configuration::Clear(string Name)
396 {
397 Item *Top = Lookup(Name.c_str(),false);
398 if (Top == 0)
399 return;
400
401 Top->Value.clear();
402 Item *Stop = Top;
403 Top = Top->Child;
404 Stop->Child = 0;
405 for (; Top != 0;)
406 {
407 if (Top->Child != 0)
408 {
409 Top = Top->Child;
410 continue;
411 }
412
413 while (Top != 0 && Top->Next == 0)
414 {
415 Item *Tmp = Top;
416 Top = Top->Parent;
417 delete Tmp;
418
419 if (Top == Stop)
420 return;
421 }
422
423 Item *Tmp = Top;
424 if (Top != 0)
425 Top = Top->Next;
426 delete Tmp;
427 }
428 }
429 /*}}}*/
430 // Configuration::Exists - Returns true if the Name exists /*{{{*/
431 // ---------------------------------------------------------------------
432 /* */
433 bool Configuration::Exists(const char *Name) const
434 {
435 const Item *Itm = Lookup(Name);
436 if (Itm == 0)
437 return false;
438 return true;
439 }
440 /*}}}*/
441 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
442 // ---------------------------------------------------------------------
443 /* qualified by /[fdbi] exists */
444 bool Configuration::ExistsAny(const char *Name) const
445 {
446 string key = Name;
447
448 if (key.size() > 2 && key.end()[-2] == '/')
449 {
450 if (key.find_first_of("fdbi",key.size()-1) < key.size())
451 {
452 key.resize(key.size() - 2);
453 if (Exists(key.c_str()))
454 return true;
455 }
456 else
457 {
458 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
459 }
460 }
461 return Exists(Name);
462 }
463 /*}}}*/
464 // Configuration::Dump - Dump the config /*{{{*/
465 // ---------------------------------------------------------------------
466 /* Dump the entire configuration space */
467 void Configuration::Dump(ostream& str)
468 {
469 /* Write out all of the configuration directives by walking the
470 configuration tree */
471 const Configuration::Item *Top = Tree(0);
472 for (; Top != 0;)
473 {
474 str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
475
476 if (Top->Child != 0)
477 {
478 Top = Top->Child;
479 continue;
480 }
481
482 while (Top != 0 && Top->Next == 0)
483 Top = Top->Parent;
484 if (Top != 0)
485 Top = Top->Next;
486 }
487 }
488 /*}}}*/
489
490 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
491 // ---------------------------------------------------------------------
492 /* Stop sets an optional max recursion depth if this item is being viewed as
493 part of a sub tree. */
494 string Configuration::Item::FullTag(const Item *Stop) const
495 {
496 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
497 return Tag;
498 return Parent->FullTag(Stop) + "::" + Tag;
499 }
500 /*}}}*/
501
502 // ReadConfigFile - Read a configuration file /*{{{*/
503 // ---------------------------------------------------------------------
504 /* The configuration format is very much like the named.conf format
505 used in bind8, in fact this routine can parse most named.conf files.
506 Sectional config files are like bind's named.conf where there are
507 sections like 'zone "foo.org" { .. };' This causes each section to be
508 added in with a tag like "zone::foo.org" instead of being split
509 tag/value. AsSectional enables Sectional parsing.*/
510 bool ReadConfigFile(Configuration &Conf,const string &FName,bool AsSectional,
511 unsigned Depth)
512 {
513 // Open the stream for reading
514 ifstream F(FName.c_str(),ios::in);
515 if (!F != 0)
516 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
517
518 string LineBuffer;
519 string Stack[100];
520 unsigned int StackPos = 0;
521
522 // Parser state
523 string ParentTag;
524
525 int CurLine = 0;
526 bool InComment = false;
527 while (F.eof() == false)
528 {
529 // The raw input line.
530 std::string Input;
531 // The input line with comments stripped.
532 std::string Fragment;
533
534 // Grab the next line of F and place it in Input.
535 do
536 {
537 char *Buffer = new char[1024];
538
539 F.clear();
540 F.getline(Buffer,sizeof(Buffer) / 2);
541
542 Input += Buffer;
543 delete[] Buffer;
544 }
545 while (F.fail() && !F.eof());
546
547 // Expand tabs in the input line and remove leading and trailing
548 // whitespace.
549 {
550 const int BufferSize = Input.size() * 8 + 1;
551 char *Buffer = new char[BufferSize];
552 try
553 {
554 memcpy(Buffer, Input.c_str(), Input.size() + 1);
555
556 _strtabexpand(Buffer, BufferSize);
557 _strstrip(Buffer);
558 Input = Buffer;
559 }
560 catch(...)
561 {
562 delete[] Buffer;
563 throw;
564 }
565 delete[] Buffer;
566 }
567 CurLine++;
568
569 // Now strip comments; if the whole line is contained in a
570 // comment, skip this line.
571
572 // The first meaningful character in the current fragment; will
573 // be adjusted below as we remove bytes from the front.
574 std::string::const_iterator Start = Input.begin();
575 // The last meaningful character in the current fragment.
576 std::string::const_iterator End = Input.end();
577
578 // Multi line comment
579 if (InComment == true)
580 {
581 for (std::string::const_iterator I = Start;
582 I != End; ++I)
583 {
584 if (*I == '*' && I + 1 != End && I[1] == '/')
585 {
586 Start = I + 2;
587 InComment = false;
588 break;
589 }
590 }
591 if (InComment == true)
592 continue;
593 }
594
595 // Discard single line comments
596 bool 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 continue;
604
605 if ((*I == '/' && I + 1 != End && I[1] == '/') || *I == '#')
606 {
607 End = I;
608 break;
609 }
610 }
611
612 // Look for multi line comments and build up the
613 // fragment.
614 Fragment.reserve(End - Start);
615 InQuote = false;
616 for (std::string::const_iterator I = Start;
617 I != End; ++I)
618 {
619 if (*I == '"')
620 InQuote = !InQuote;
621 if (InQuote == true)
622 Fragment.push_back(*I);
623 else if (*I == '/' && I + 1 != End && I[1] == '*')
624 {
625 InComment = true;
626 for (std::string::const_iterator J = I;
627 J != End; ++J)
628 {
629 if (*J == '*' && J + 1 != End && J[1] == '/')
630 {
631 // Pretend we just finished walking over the
632 // comment, and don't add anything to the output
633 // fragment.
634 I = J + 1;
635 InComment = false;
636 break;
637 }
638 }
639
640 if (InComment == true)
641 break;
642 }
643 else
644 Fragment.push_back(*I);
645 }
646
647 // Skip blank lines.
648 if (Fragment.empty())
649 continue;
650
651 // The line has actual content; interpret what it means.
652 InQuote = false;
653 Start = Fragment.begin();
654 End = Fragment.end();
655 for (std::string::const_iterator I = Start;
656 I != End; ++I)
657 {
658 if (*I == '"')
659 InQuote = !InQuote;
660
661 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
662 {
663 // Put the last fragment into the buffer
664 std::string::const_iterator NonWhitespaceStart = Start;
665 std::string::const_iterator NonWhitespaceStop = I;
666 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; NonWhitespaceStart++)
667 ;
668 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; NonWhitespaceStop--)
669 ;
670 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
671 LineBuffer += ' ';
672 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
673
674 // Drop this from the input string, saving the character
675 // that terminated the construct we just closed. (i.e., a
676 // brace or a semicolon)
677 char TermChar = *I;
678 Start = I + 1;
679
680 // Syntax Error
681 if (TermChar == '{' && LineBuffer.empty() == true)
682 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
683
684 // No string on this line
685 if (LineBuffer.empty() == true)
686 {
687 if (TermChar == '}')
688 {
689 if (StackPos == 0)
690 ParentTag = string();
691 else
692 ParentTag = Stack[--StackPos];
693 }
694 continue;
695 }
696
697 // Parse off the tag
698 string Tag;
699 const char *Pos = LineBuffer.c_str();
700 if (ParseQuoteWord(Pos,Tag) == false)
701 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
702
703 // Parse off the word
704 string Word;
705 bool NoWord = false;
706 if (ParseCWord(Pos,Word) == false &&
707 ParseQuoteWord(Pos,Word) == false)
708 {
709 if (TermChar != '{')
710 {
711 Word = Tag;
712 Tag = "";
713 }
714 else
715 NoWord = true;
716 }
717 if (strlen(Pos) != 0)
718 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
719
720 // Go down a level
721 if (TermChar == '{')
722 {
723 if (StackPos <= 100)
724 Stack[StackPos++] = ParentTag;
725
726 /* Make sectional tags incorperate the section into the
727 tag string */
728 if (AsSectional == true && Word.empty() == false)
729 {
730 Tag += "::" ;
731 Tag += Word;
732 Word = "";
733 }
734
735 if (ParentTag.empty() == true)
736 ParentTag = Tag;
737 else
738 ParentTag += string("::") + Tag;
739 Tag = string();
740 }
741
742 // Generate the item name
743 string Item;
744 if (ParentTag.empty() == true)
745 Item = Tag;
746 else
747 {
748 if (TermChar != '{' || Tag.empty() == false)
749 Item = ParentTag + "::" + Tag;
750 else
751 Item = ParentTag;
752 }
753
754 // Specials
755 if (Tag.length() >= 1 && Tag[0] == '#')
756 {
757 if (ParentTag.empty() == false)
758 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
759 Tag.erase(Tag.begin());
760 if (Tag == "clear")
761 Conf.Clear(Word);
762 else if (Tag == "include")
763 {
764 if (Depth > 10)
765 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
766 if (Word.length() > 2 && Word.end()[-1] == '/')
767 {
768 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
769 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
770 }
771 else
772 {
773 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
774 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
775 }
776 }
777 else
778 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
779 }
780 else
781 {
782 // Set the item in the configuration class
783 if (NoWord == false)
784 Conf.Set(Item,Word);
785 }
786
787 // Empty the buffer
788 LineBuffer.clear();
789
790 // Move up a tag, but only if there is no bit to parse
791 if (TermChar == '}')
792 {
793 if (StackPos == 0)
794 ParentTag.clear();
795 else
796 ParentTag = Stack[--StackPos];
797 }
798
799 }
800 }
801
802 // Store the remaining text, if any, in the current line buffer.
803
804 // NB: could change this to use string-based operations; I'm
805 // using strstrip now to ensure backwards compatibility.
806 // -- dburrows 2008-04-01
807 {
808 char *Buffer = new char[End - Start + 1];
809 try
810 {
811 std::copy(Start, End, Buffer);
812 Buffer[End - Start] = '\0';
813
814 const char *Stripd = _strstrip(Buffer);
815 if (*Stripd != 0 && LineBuffer.empty() == false)
816 LineBuffer += " ";
817 LineBuffer += Stripd;
818 }
819 catch(...)
820 {
821 delete[] Buffer;
822 throw;
823 }
824 delete[] Buffer;
825 }
826 }
827
828 if (LineBuffer.empty() == false)
829 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
830 return true;
831 }
832 /*}}}*/
833 // ReadConfigDir - Read a directory of config files /*{{{*/
834 // ---------------------------------------------------------------------
835 /* */
836 bool ReadConfigDir(Configuration &Conf,const string &Dir,bool AsSectional,
837 unsigned Depth)
838 {
839 DIR *D = opendir(Dir.c_str());
840 if (D == 0)
841 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
842
843 vector<string> List;
844
845 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
846 {
847 if (Ent->d_name[0] == '.')
848 continue;
849
850 // Skip bad file names ala run-parts
851 const char *C = Ent->d_name;
852 for (; *C != 0; C++)
853 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
854 break;
855 if (*C != 0)
856 continue;
857
858 // Make sure it is a file and not something else
859 string File = flCombine(Dir,Ent->d_name);
860 struct stat St;
861 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
862 continue;
863
864 List.push_back(File);
865 }
866 closedir(D);
867
868 sort(List.begin(),List.end());
869
870 // Read the files
871 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
872 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
873 return false;
874 return true;
875 }
876 /*}}}*/