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