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