]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
When a line in a config file is too long, say which config file it is.
[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())
43570492
DB
515 return _error->Error(_("%s: Line %d too long (max %lu)"),
516 FName.c_str(), CurLine, sizeof(Buffer));
5e14c452 517
08e8f724
AL
518 _strtabexpand(Buffer,sizeof(Buffer));
519 _strstrip(Buffer);
520
521 // Multi line comment
522 if (InComment == true)
523 {
524 for (const char *I = Buffer; *I != 0; I++)
525 {
526 if (*I == '*' && I[1] == '/')
527 {
528 memmove(Buffer,I+2,strlen(I+2) + 1);
529 InComment = false;
530 break;
531 }
532 }
533 if (InComment == true)
534 continue;
535 }
536
537 // Discard single line comments
bfd22fc0 538 bool InQuote = false;
08e8f724
AL
539 for (char *I = Buffer; *I != 0; I++)
540 {
bfd22fc0
AL
541 if (*I == '"')
542 InQuote = !InQuote;
543 if (InQuote == true)
544 continue;
545
08e8f724
AL
546 if (*I == '/' && I[1] == '/')
547 {
548 *I = 0;
549 break;
550 }
551 }
7834cb57 552
08e8f724 553 // Look for multi line comments
7834cb57 554 InQuote = false;
08e8f724
AL
555 for (char *I = Buffer; *I != 0; I++)
556 {
bfd22fc0
AL
557 if (*I == '"')
558 InQuote = !InQuote;
559 if (InQuote == true)
560 continue;
561
08e8f724
AL
562 if (*I == '/' && I[1] == '*')
563 {
564 InComment = true;
565 for (char *J = Buffer; *J != 0; J++)
566 {
567 if (*J == '*' && J[1] == '/')
568 {
569 memmove(I,J+2,strlen(J+2) + 1);
570 InComment = false;
571 break;
572 }
573 }
574
575 if (InComment == true)
576 {
577 *I = 0;
578 break;
579 }
580 }
581 }
582
583 // Blank
584 if (Buffer[0] == 0)
585 continue;
586
587 // We now have a valid line fragment
7834cb57 588 InQuote = false;
08e8f724
AL
589 for (char *I = Buffer; *I != 0;)
590 {
7834cb57
AL
591 if (*I == '"')
592 InQuote = !InQuote;
593
594 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 595 {
b2e465d6 596 // Put the last fragment into the buffer
08e8f724
AL
597 char *Start = Buffer;
598 char *Stop = I;
599 for (; Start != I && isspace(*Start) != 0; Start++);
600 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
601 if (LineBuffer.empty() == false && Stop - Start != 0)
602 LineBuffer += ' ';
603 LineBuffer += string(Start,Stop - Start);
604
605 // Remove the fragment
606 char TermChar = *I;
607 memmove(Buffer,I + 1,strlen(I + 1) + 1);
608 I = Buffer;
08e8f724
AL
609
610 // Syntax Error
611 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 612 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 613
b2e465d6 614 // No string on this line
08e8f724 615 if (LineBuffer.empty() == true)
b2e465d6
AL
616 {
617 if (TermChar == '}')
618 {
619 if (StackPos == 0)
620 ParentTag = string();
621 else
622 ParentTag = Stack[--StackPos];
623 }
08e8f724 624 continue;
b2e465d6
AL
625 }
626
08e8f724 627 // Parse off the tag
7f25bdff
AL
628 string Tag;
629 const char *Pos = LineBuffer.c_str();
630 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 631 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
632
633 // Parse off the word
634 string Word;
bcae6dd4 635 bool NoWord = false;
b2e465d6
AL
636 if (ParseCWord(Pos,Word) == false &&
637 ParseQuoteWord(Pos,Word) == false)
638 {
639 if (TermChar != '{')
640 {
641 Word = Tag;
642 Tag = "";
bcae6dd4 643 }
4ae405e9
AL
644 else
645 NoWord = true;
b2e465d6
AL
646 }
647 if (strlen(Pos) != 0)
648 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
649
08e8f724
AL
650 // Go down a level
651 if (TermChar == '{')
652 {
a4e87467
AL
653 if (StackPos <= 100)
654 Stack[StackPos++] = ParentTag;
b2e465d6
AL
655
656 /* Make sectional tags incorperate the section into the
657 tag string */
658 if (AsSectional == true && Word.empty() == false)
659 {
660 Tag += "::" ;
661 Tag += Word;
662 Word = "";
663 }
664
08e8f724
AL
665 if (ParentTag.empty() == true)
666 ParentTag = Tag;
667 else
668 ParentTag += string("::") + Tag;
669 Tag = string();
670 }
bfd22fc0 671
08e8f724
AL
672 // Generate the item name
673 string Item;
674 if (ParentTag.empty() == true)
675 Item = Tag;
676 else
677 {
7f25bdff 678 if (TermChar != '{' || Tag.empty() == false)
08e8f724 679 Item = ParentTag + "::" + Tag;
7f25bdff
AL
680 else
681 Item = ParentTag;
08e8f724
AL
682 }
683
b2e465d6
AL
684 // Specials
685 if (Tag.length() >= 1 && Tag[0] == '#')
686 {
687 if (ParentTag.empty() == false)
688 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
689 Tag.erase(Tag.begin());
690 if (Tag == "clear")
691 Conf.Clear(Word);
692 else if (Tag == "include")
693 {
694 if (Depth > 10)
695 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
696 if (Word.length() > 2 && Word.end()[-1] == '/')
697 {
698 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
699 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
700 }
701 else
702 {
703 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
704 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
705 }
706 }
707 else
708 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
709 }
710 else
711 {
712 // Set the item in the configuration class
bcae6dd4
AL
713 if (NoWord == false)
714 Conf.Set(Item,Word);
b2e465d6
AL
715 }
716
08e8f724 717 // Empty the buffer
171c75f1 718 LineBuffer.clear();
b2e465d6
AL
719
720 // Move up a tag, but only if there is no bit to parse
721 if (TermChar == '}')
722 {
723 if (StackPos == 0)
171c75f1 724 ParentTag.clear();
b2e465d6
AL
725 else
726 ParentTag = Stack[--StackPos];
727 }
728
08e8f724
AL
729 }
730 else
731 I++;
732 }
733
734 // Store the fragment
735 const char *Stripd = _strstrip(Buffer);
736 if (*Stripd != 0 && LineBuffer.empty() == false)
737 LineBuffer += " ";
738 LineBuffer += Stripd;
739 }
b2e465d6
AL
740
741 if (LineBuffer.empty() == false)
742 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
743 return true;
744}
745 /*}}}*/
746// ReadConfigDir - Read a directory of config files /*{{{*/
747// ---------------------------------------------------------------------
748/* */
171c75f1
MV
749bool ReadConfigDir(Configuration &Conf,const string &Dir,bool AsSectional,
750 unsigned Depth)
b2e465d6 751{
b2e465d6
AL
752 DIR *D = opendir(Dir.c_str());
753 if (D == 0)
754 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
755
756 vector<string> List;
757
758 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
759 {
721e08d6 760 if (Ent->d_name[0] == '.')
b2e465d6
AL
761 continue;
762
bb3a5465
AL
763 // Skip bad file names ala run-parts
764 const char *C = Ent->d_name;
765 for (; *C != 0; C++)
766 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 767 break;
bb3a5465 768 if (*C != 0)
b2e465d6
AL
769 continue;
770
771 // Make sure it is a file and not something else
772 string File = flCombine(Dir,Ent->d_name);
773 struct stat St;
774 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
775 continue;
776
777 List.push_back(File);
778 }
779 closedir(D);
08e8f724 780
b2e465d6
AL
781 sort(List.begin(),List.end());
782
783 // Read the files
784 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
785 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
786 return false;
08e8f724
AL
787 return true;
788}
789 /*}}}*/