]> 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::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] == '/') ||
606 (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 &&
607 strcmp(string(I,I+8).c_str(),"#include") != 0))
608 {
609 End = I;
610 break;
611 }
612 }
613
614 // Look for multi line comments and build up the
615 // fragment.
616 Fragment.reserve(End - Start);
617 InQuote = false;
618 for (std::string::const_iterator I = Start;
619 I != End; ++I)
620 {
621 if (*I == '"')
622 InQuote = !InQuote;
623 if (InQuote == true)
624 Fragment.push_back(*I);
625 else if (*I == '/' && I + 1 != End && I[1] == '*')
626 {
627 InComment = true;
628 for (std::string::const_iterator J = I;
629 J != End; ++J)
630 {
631 if (*J == '*' && J + 1 != End && J[1] == '/')
632 {
633 // Pretend we just finished walking over the
634 // comment, and don't add anything to the output
635 // fragment.
636 I = J + 1;
637 InComment = false;
638 break;
639 }
640 }
641
642 if (InComment == true)
643 break;
644 }
645 else
646 Fragment.push_back(*I);
647 }
648
649 // Skip blank lines.
650 if (Fragment.empty())
651 continue;
652
653 // The line has actual content; interpret what it means.
654 InQuote = false;
655 Start = Fragment.begin();
656 End = Fragment.end();
657 for (std::string::const_iterator I = Start;
658 I != End; ++I)
659 {
660 if (*I == '"')
661 InQuote = !InQuote;
662
663 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
664 {
665 // Put the last fragment into the buffer
666 std::string::const_iterator NonWhitespaceStart = Start;
667 std::string::const_iterator NonWhitespaceStop = I;
668 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; NonWhitespaceStart++)
669 ;
670 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; NonWhitespaceStop--)
671 ;
672 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
673 LineBuffer += ' ';
674 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
675
676 // Drop this from the input string, saving the character
677 // that terminated the construct we just closed. (i.e., a
678 // brace or a semicolon)
679 char TermChar = *I;
680 Start = I + 1;
681
682 // Syntax Error
683 if (TermChar == '{' && LineBuffer.empty() == true)
684 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
685
686 // No string on this line
687 if (LineBuffer.empty() == true)
688 {
689 if (TermChar == '}')
690 {
691 if (StackPos == 0)
692 ParentTag = string();
693 else
694 ParentTag = Stack[--StackPos];
695 }
696 continue;
697 }
698
699 // Parse off the tag
700 string Tag;
701 const char *Pos = LineBuffer.c_str();
702 if (ParseQuoteWord(Pos,Tag) == false)
703 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
704
705 // Parse off the word
706 string Word;
707 bool NoWord = false;
708 if (ParseCWord(Pos,Word) == false &&
709 ParseQuoteWord(Pos,Word) == false)
710 {
711 if (TermChar != '{')
712 {
713 Word = Tag;
714 Tag = "";
715 }
716 else
717 NoWord = true;
718 }
719 if (strlen(Pos) != 0)
720 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
721
722 // Go down a level
723 if (TermChar == '{')
724 {
725 if (StackPos <= 100)
726 Stack[StackPos++] = ParentTag;
727
728 /* Make sectional tags incorperate the section into the
729 tag string */
730 if (AsSectional == true && Word.empty() == false)
731 {
732 Tag += "::" ;
733 Tag += Word;
734 Word = "";
735 }
736
737 if (ParentTag.empty() == true)
738 ParentTag = Tag;
739 else
740 ParentTag += string("::") + Tag;
741 Tag = string();
742 }
743
744 // Generate the item name
745 string Item;
746 if (ParentTag.empty() == true)
747 Item = Tag;
748 else
749 {
750 if (TermChar != '{' || Tag.empty() == false)
751 Item = ParentTag + "::" + Tag;
752 else
753 Item = ParentTag;
754 }
755
756 // Specials
757 if (Tag.length() >= 1 && Tag[0] == '#')
758 {
759 if (ParentTag.empty() == false)
760 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
761 Tag.erase(Tag.begin());
762 if (Tag == "clear")
763 Conf.Clear(Word);
764 else if (Tag == "include")
765 {
766 if (Depth > 10)
767 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
768 if (Word.length() > 2 && Word.end()[-1] == '/')
769 {
770 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
771 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
772 }
773 else
774 {
775 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
776 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
777 }
778 }
779 else
780 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
781 }
782 else
783 {
784 // Set the item in the configuration class
785 if (NoWord == false)
786 Conf.Set(Item,Word);
787 }
788
789 // Empty the buffer
790 LineBuffer.clear();
791
792 // Move up a tag, but only if there is no bit to parse
793 if (TermChar == '}')
794 {
795 if (StackPos == 0)
796 ParentTag.clear();
797 else
798 ParentTag = Stack[--StackPos];
799 }
800
801 }
802 }
803
804 // Store the remaining text, if any, in the current line buffer.
805
806 // NB: could change this to use string-based operations; I'm
807 // using strstrip now to ensure backwards compatibility.
808 // -- dburrows 2008-04-01
809 {
810 char *Buffer = new char[End - Start + 1];
811 try
812 {
813 std::copy(Start, End, Buffer);
814 Buffer[End - Start] = '\0';
815
816 const char *Stripd = _strstrip(Buffer);
817 if (*Stripd != 0 && LineBuffer.empty() == false)
818 LineBuffer += " ";
819 LineBuffer += Stripd;
820 }
821 catch(...)
822 {
823 delete[] Buffer;
824 throw;
825 }
826 delete[] Buffer;
827 }
828 }
829
830 if (LineBuffer.empty() == false)
831 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
832 return true;
833 }
834 /*}}}*/
835 // ReadConfigDir - Read a directory of config files /*{{{*/
836 // ---------------------------------------------------------------------
837 /* */
838 bool ReadConfigDir(Configuration &Conf,const string &Dir,bool AsSectional,
839 unsigned Depth)
840 {
841 DIR *D = opendir(Dir.c_str());
842 if (D == 0)
843 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
844
845 vector<string> List;
846
847 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
848 {
849 if (Ent->d_name[0] == '.')
850 continue;
851
852 // Skip bad file names ala run-parts
853 const char *C = Ent->d_name;
854 for (; *C != 0; C++)
855 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
856 break;
857 if (*C != 0)
858 continue;
859
860 // Make sure it is a file and not something else
861 string File = flCombine(Dir,Ent->d_name);
862 struct stat St;
863 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
864 continue;
865
866 List.push_back(File);
867 }
868 closedir(D);
869
870 sort(List.begin(),List.end());
871
872 // Read the files
873 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
874 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
875 return false;
876 return true;
877 }
878 /*}}}*/