* apt-pkg/contrib/configuration.cc:
[ntk/apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
5e14c452 3// $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz 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 /*{{{*/
094a497d 18#include <apt-pkg/configuration.h>
08e8f724 19#include <apt-pkg/error.h>
cdcc6d34 20#include <apt-pkg/strutl.h>
b2e465d6
AL
21#include <apt-pkg/fileutl.h>
22#include <apti18n.h>
6c139d6e 23
b2e465d6
AL
24#include <vector>
25#include <algorithm>
26#include <fstream>
851a45a8 27#include <iostream>
b2e465d6 28
6c139d6e 29#include <stdio.h>
b2e465d6
AL
30#include <dirent.h>
31#include <sys/stat.h>
32#include <unistd.h>
851a45a8
AL
33
34using namespace std;
6c139d6e 35 /*}}}*/
9c14e3d6
AL
36
37Configuration *_config = new Configuration;
6c139d6e
AL
38
39// Configuration::Configuration - Constructor /*{{{*/
40// ---------------------------------------------------------------------
41/* */
b2e465d6 42Configuration::Configuration() : ToFree(true)
6c139d6e
AL
43{
44 Root = new Item;
b2e465d6
AL
45}
46Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
47{
48};
49
50 /*}}}*/
51// Configuration::~Configuration - Destructor /*{{{*/
52// ---------------------------------------------------------------------
53/* */
54Configuration::~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 }
6c139d6e
AL
81}
82 /*}}}*/
0a8a80e5 83// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
84// ---------------------------------------------------------------------
85/* This will lookup a single item by name below another item. It is a
86 helper function for the main lookup function */
87Configuration::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;
9c14e3d6 93
7f25bdff
AL
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)
851a45a8 98 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
7f25bdff
AL
99 break;
100 }
101 else
102 for (; I != 0; Last = &I->Next, I = I->Next);
103
6c139d6e
AL
104 if (Res == 0)
105 return I;
106 if (Create == false)
107 return 0;
108
109 I = new Item;
171c75f1 110 I->Tag.assign(S,Len);
6c139d6e 111 I->Next = *Last;
9c14e3d6 112 I->Parent = Head;
6c139d6e
AL
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 */
121Configuration::Item *Configuration::Lookup(const char *Name,bool Create)
122{
0a8a80e5
AL
123 if (Name == 0)
124 return Root->Child;
125
6c139d6e
AL
126 const char *Start = Name;
127 const char *End = Start + strlen(Name);
128 const char *TagEnd = Name;
129 Item *Itm = Root;
7f25bdff 130 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
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
7f25bdff
AL
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
6c139d6e 148 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
149 return Itm;
150}
151 /*}}}*/
152// Configuration::Find - Find a value /*{{{*/
153// ---------------------------------------------------------------------
154/* */
b2e465d6 155string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 156{
b2e465d6 157 const Item *Itm = Lookup(Name);
6c139d6e 158 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
159 {
160 if (Default == 0)
171c75f1 161 return "";
9c14e3d6
AL
162 else
163 return Default;
164 }
165
6c139d6e
AL
166 return Itm->Value;
167}
168 /*}}}*/
3b5421b4 169// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
170// ---------------------------------------------------------------------
171/* Directories are stored as the base dir in the Parent node and the
3b5421b4 172 sub directory in sub nodes with the final node being the end filename
9c14e3d6 173 */
b2e465d6 174string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 175{
db2cca11
OS
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
b2e465d6 181 const Item *Itm = Lookup(Name);
9c14e3d6
AL
182 if (Itm == 0 || Itm->Value.empty() == true)
183 {
184 if (Default == 0)
171c75f1 185 return "";
9c14e3d6
AL
186 else
187 return Default;
188 }
189
b2e465d6
AL
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
db2cca11 212 return rootDir + val;
9c14e3d6
AL
213}
214 /*}}}*/
3b5421b4
AL
215// Configuration::FindDir - Find a directory name /*{{{*/
216// ---------------------------------------------------------------------
217/* This is like findfile execept the result is terminated in a / */
b2e465d6 218string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
219{
220 string Res = FindFile(Name,Default);
221 if (Res.end()[-1] != '/')
222 return Res + '/';
223 return Res;
224}
225 /*}}}*/
6c139d6e
AL
226// Configuration::FindI - Find an integer value /*{{{*/
227// ---------------------------------------------------------------------
228/* */
b2e465d6 229int Configuration::FindI(const char *Name,int Default) const
6c139d6e 230{
b2e465d6 231 const Item *Itm = Lookup(Name);
6c139d6e
AL
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
08e8f724
AL
240 return Res;
241}
242 /*}}}*/
243// Configuration::FindB - Find a boolean type /*{{{*/
244// ---------------------------------------------------------------------
245/* */
b2e465d6 246bool Configuration::FindB(const char *Name,bool Default) const
08e8f724 247{
b2e465d6 248 const Item *Itm = Lookup(Name);
08e8f724
AL
249 if (Itm == 0 || Itm->Value.empty() == true)
250 return Default;
251
3b5421b4 252 return StringToBool(Itm->Value,Default);
6c139d6e
AL
253}
254 /*}}}*/
b2e465d6
AL
255// Configuration::FindAny - Find an arbitrary type /*{{{*/
256// ---------------------------------------------------------------------
257/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
258string 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];
cc99a102 287 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
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 */
171c75f1 299void Configuration::CndSet(const char *Name,const string &Value)
b2e465d6
AL
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 /*}}}*/
6c139d6e
AL
308// Configuration::Set - Set a value /*{{{*/
309// ---------------------------------------------------------------------
310/* */
171c75f1 311void Configuration::Set(const char *Name,const string &Value)
6c139d6e
AL
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/* */
322void 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;
75ef8f14
MV
330}
331 /*}}}*/
332// Configuration::Clear - Clear an single value from a list /*{{{*/
333// ---------------------------------------------------------------------
334/* */
171c75f1 335void Configuration::Clear(const string Name, int Value)
75ef8f14
MV
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/* */
171c75f1 345void Configuration::Clear(const string Name, string Value)
75ef8f14
MV
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
6c139d6e
AL
371}
372 /*}}}*/
b2e465d6
AL
373// Configuration::Clear - Clear an entire tree /*{{{*/
374// ---------------------------------------------------------------------
375/* */
376void Configuration::Clear(string Name)
377{
378 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 379 if (Top == 0)
b2e465d6 380 return;
75ef8f14 381
171c75f1 382 Top->Value.clear();
b2e465d6
AL
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 /*}}}*/
08e8f724
AL
411// Configuration::Exists - Returns true if the Name exists /*{{{*/
412// ---------------------------------------------------------------------
413/* */
b2e465d6 414bool Configuration::Exists(const char *Name) const
08e8f724 415{
b2e465d6 416 const Item *Itm = Lookup(Name);
08e8f724
AL
417 if (Itm == 0)
418 return false;
419 return true;
420}
421 /*}}}*/
b2e465d6
AL
422// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
423// ---------------------------------------------------------------------
424/* qualified by /[fdbi] exists */
425bool Configuration::ExistsAny(const char *Name) const
426{
427 string key = Name;
428
8d998026 429 if (key.size() > 2 && key.end()[-2] == '/')
ff6bf1be 430 {
8d998026
AL
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 {
69a63027 439 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 440 }
ff6bf1be 441 }
b2e465d6
AL
442 return Exists(Name);
443}
444 /*}}}*/
93bf083d
AL
445// Configuration::Dump - Dump the config /*{{{*/
446// ---------------------------------------------------------------------
447/* Dump the entire configuration space */
ff2a211a 448void Configuration::Dump(ostream& str)
93bf083d
AL
449{
450 /* Write out all of the configuration directives by walking the
451 configuration tree */
b2e465d6 452 const Configuration::Item *Top = Tree(0);
93bf083d
AL
453 for (; Top != 0;)
454 {
ff2a211a 455 str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
93bf083d
AL
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;
b2e465d6 467 }
93bf083d
AL
468}
469 /*}}}*/
08e8f724 470
0a8a80e5
AL
471// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
472// ---------------------------------------------------------------------
b2e465d6
AL
473/* Stop sets an optional max recursion depth if this item is being viewed as
474 part of a sub tree. */
475string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 476{
b2e465d6 477 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 478 return Tag;
b2e465d6 479 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
480}
481 /*}}}*/
482
08e8f724
AL
483// ReadConfigFile - Read a configuration file /*{{{*/
484// ---------------------------------------------------------------------
485/* The configuration format is very much like the named.conf format
b2e465d6
AL
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
6df63aa6 490 tag/value. AsSectional enables Sectional parsing.*/
171c75f1 491bool ReadConfigFile(Configuration &Conf,const string &FName,bool AsSectional,
b2e465d6
AL
492 unsigned Depth)
493{
08e8f724 494 // Open the stream for reading
851a45a8 495 ifstream F(FName.c_str(),ios::in);
08e8f724 496 if (!F != 0)
b2e465d6 497 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
08e8f724 498
5e14c452 499 char Buffer[1024];
08e8f724 500 string LineBuffer;
a4e87467
AL
501 string Stack[100];
502 unsigned int StackPos = 0;
08e8f724
AL
503
504 // Parser state
505 string ParentTag;
506
507 int CurLine = 0;
508 bool InComment = false;
509 while (F.eof() == false)
510 {
511 F.getline(Buffer,sizeof(Buffer));
512 CurLine++;
5e14c452
AL
513 // This should be made to work instead, but this is better than looping
514 if (F.fail() && !F.eof())
21fd1746 515 return _error->Error(_("Line %d too long (max %u)"), CurLine, sizeof(Buffer));
5e14c452 516
08e8f724
AL
517 _strtabexpand(Buffer,sizeof(Buffer));
518 _strstrip(Buffer);
519
520 // Multi line comment
521 if (InComment == true)
522 {
523 for (const char *I = Buffer; *I != 0; I++)
524 {
525 if (*I == '*' && I[1] == '/')
526 {
527 memmove(Buffer,I+2,strlen(I+2) + 1);
528 InComment = false;
529 break;
530 }
531 }
532 if (InComment == true)
533 continue;
534 }
535
536 // Discard single line comments
bfd22fc0 537 bool InQuote = false;
08e8f724
AL
538 for (char *I = Buffer; *I != 0; I++)
539 {
bfd22fc0
AL
540 if (*I == '"')
541 InQuote = !InQuote;
542 if (InQuote == true)
543 continue;
544
08e8f724
AL
545 if (*I == '/' && I[1] == '/')
546 {
547 *I = 0;
548 break;
549 }
550 }
7834cb57 551
08e8f724 552 // Look for multi line comments
7834cb57 553 InQuote = false;
08e8f724
AL
554 for (char *I = Buffer; *I != 0; I++)
555 {
bfd22fc0
AL
556 if (*I == '"')
557 InQuote = !InQuote;
558 if (InQuote == true)
559 continue;
560
08e8f724
AL
561 if (*I == '/' && I[1] == '*')
562 {
563 InComment = true;
564 for (char *J = Buffer; *J != 0; J++)
565 {
566 if (*J == '*' && J[1] == '/')
567 {
568 memmove(I,J+2,strlen(J+2) + 1);
569 InComment = false;
570 break;
571 }
572 }
573
574 if (InComment == true)
575 {
576 *I = 0;
577 break;
578 }
579 }
580 }
581
582 // Blank
583 if (Buffer[0] == 0)
584 continue;
585
586 // We now have a valid line fragment
7834cb57 587 InQuote = false;
08e8f724
AL
588 for (char *I = Buffer; *I != 0;)
589 {
7834cb57
AL
590 if (*I == '"')
591 InQuote = !InQuote;
592
593 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 594 {
b2e465d6 595 // Put the last fragment into the buffer
08e8f724
AL
596 char *Start = Buffer;
597 char *Stop = I;
598 for (; Start != I && isspace(*Start) != 0; Start++);
599 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
600 if (LineBuffer.empty() == false && Stop - Start != 0)
601 LineBuffer += ' ';
602 LineBuffer += string(Start,Stop - Start);
603
604 // Remove the fragment
605 char TermChar = *I;
606 memmove(Buffer,I + 1,strlen(I + 1) + 1);
607 I = Buffer;
08e8f724
AL
608
609 // Syntax Error
610 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 611 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 612
b2e465d6 613 // No string on this line
08e8f724 614 if (LineBuffer.empty() == true)
b2e465d6
AL
615 {
616 if (TermChar == '}')
617 {
618 if (StackPos == 0)
619 ParentTag = string();
620 else
621 ParentTag = Stack[--StackPos];
622 }
08e8f724 623 continue;
b2e465d6
AL
624 }
625
08e8f724 626 // Parse off the tag
7f25bdff
AL
627 string Tag;
628 const char *Pos = LineBuffer.c_str();
629 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 630 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
631
632 // Parse off the word
633 string Word;
bcae6dd4 634 bool NoWord = false;
b2e465d6
AL
635 if (ParseCWord(Pos,Word) == false &&
636 ParseQuoteWord(Pos,Word) == false)
637 {
638 if (TermChar != '{')
639 {
640 Word = Tag;
641 Tag = "";
bcae6dd4 642 }
4ae405e9
AL
643 else
644 NoWord = true;
b2e465d6
AL
645 }
646 if (strlen(Pos) != 0)
647 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
648
08e8f724
AL
649 // Go down a level
650 if (TermChar == '{')
651 {
a4e87467
AL
652 if (StackPos <= 100)
653 Stack[StackPos++] = ParentTag;
b2e465d6
AL
654
655 /* Make sectional tags incorperate the section into the
656 tag string */
657 if (AsSectional == true && Word.empty() == false)
658 {
659 Tag += "::" ;
660 Tag += Word;
661 Word = "";
662 }
663
08e8f724
AL
664 if (ParentTag.empty() == true)
665 ParentTag = Tag;
666 else
667 ParentTag += string("::") + Tag;
668 Tag = string();
669 }
bfd22fc0 670
08e8f724
AL
671 // Generate the item name
672 string Item;
673 if (ParentTag.empty() == true)
674 Item = Tag;
675 else
676 {
7f25bdff 677 if (TermChar != '{' || Tag.empty() == false)
08e8f724 678 Item = ParentTag + "::" + Tag;
7f25bdff
AL
679 else
680 Item = ParentTag;
08e8f724
AL
681 }
682
b2e465d6
AL
683 // Specials
684 if (Tag.length() >= 1 && Tag[0] == '#')
685 {
686 if (ParentTag.empty() == false)
687 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
688 Tag.erase(Tag.begin());
689 if (Tag == "clear")
690 Conf.Clear(Word);
691 else if (Tag == "include")
692 {
693 if (Depth > 10)
694 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
695 if (Word.length() > 2 && Word.end()[-1] == '/')
696 {
697 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
698 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
699 }
700 else
701 {
702 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
703 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
704 }
705 }
706 else
707 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
708 }
709 else
710 {
711 // Set the item in the configuration class
bcae6dd4
AL
712 if (NoWord == false)
713 Conf.Set(Item,Word);
b2e465d6
AL
714 }
715
08e8f724 716 // Empty the buffer
171c75f1 717 LineBuffer.clear();
b2e465d6
AL
718
719 // Move up a tag, but only if there is no bit to parse
720 if (TermChar == '}')
721 {
722 if (StackPos == 0)
171c75f1 723 ParentTag.clear();
b2e465d6
AL
724 else
725 ParentTag = Stack[--StackPos];
726 }
727
08e8f724
AL
728 }
729 else
730 I++;
731 }
732
733 // Store the fragment
734 const char *Stripd = _strstrip(Buffer);
735 if (*Stripd != 0 && LineBuffer.empty() == false)
736 LineBuffer += " ";
737 LineBuffer += Stripd;
738 }
b2e465d6
AL
739
740 if (LineBuffer.empty() == false)
741 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
742 return true;
743}
744 /*}}}*/
745// ReadConfigDir - Read a directory of config files /*{{{*/
746// ---------------------------------------------------------------------
747/* */
171c75f1
MV
748bool ReadConfigDir(Configuration &Conf,const string &Dir,bool AsSectional,
749 unsigned Depth)
b2e465d6 750{
b2e465d6
AL
751 DIR *D = opendir(Dir.c_str());
752 if (D == 0)
753 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
754
755 vector<string> List;
756
757 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
758 {
721e08d6 759 if (Ent->d_name[0] == '.')
b2e465d6
AL
760 continue;
761
bb3a5465
AL
762 // Skip bad file names ala run-parts
763 const char *C = Ent->d_name;
764 for (; *C != 0; C++)
765 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 766 break;
bb3a5465 767 if (*C != 0)
b2e465d6
AL
768 continue;
769
770 // Make sure it is a file and not something else
771 string File = flCombine(Dir,Ent->d_name);
772 struct stat St;
773 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
774 continue;
775
776 List.push_back(File);
777 }
778 closedir(D);
08e8f724 779
b2e465d6
AL
780 sort(List.begin(),List.end());
781
782 // Read the files
783 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
784 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
785 return false;
08e8f724
AL
786 return true;
787}
788 /*}}}*/