* sparc64 alignment fix
[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 /*{{{*/
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;
75ef8f14
MV
328}
329 /*}}}*/
330// Configuration::Clear - Clear an single value from a list /*{{{*/
331// ---------------------------------------------------------------------
332/* */
333void Configuration::Clear(string Name, int Value)
334{
335 char S[300];
336 snprintf(S,sizeof(S),"%i",Value);
337 Clear(Name, S);
338}
339 /*}}}*/
340// Configuration::Clear - Clear an single value from a list /*{{{*/
341// ---------------------------------------------------------------------
342/* */
343void Configuration::Clear(string Name, string Value)
344{
345 Item *Top = Lookup(Name.c_str(),false);
346 if (Top == 0 || Top->Child == 0)
347 return;
348
349 Item *Tmp, *Prev, *I;
350 Prev = I = Top->Child;
351
352 while(I != NULL)
353 {
354 if(I->Value == Value)
355 {
356 Tmp = I;
357 // was first element, point parent to new first element
358 if(Top->Child == Tmp)
359 Top->Child = I->Next;
360 I = I->Next;
361 Prev->Next = I;
362 delete Tmp;
363 } else {
364 Prev = I;
365 I = I->Next;
366 }
367 }
368
6c139d6e
AL
369}
370 /*}}}*/
b2e465d6
AL
371// Configuration::Clear - Clear an entire tree /*{{{*/
372// ---------------------------------------------------------------------
373/* */
374void Configuration::Clear(string Name)
375{
376 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 377 if (Top == 0)
b2e465d6 378 return;
75ef8f14 379
b2e465d6
AL
380 Top->Value = string();
381 Item *Stop = Top;
382 Top = Top->Child;
383 Stop->Child = 0;
384 for (; Top != 0;)
385 {
386 if (Top->Child != 0)
387 {
388 Top = Top->Child;
389 continue;
390 }
391
392 while (Top != 0 && Top->Next == 0)
393 {
394 Item *Tmp = Top;
395 Top = Top->Parent;
396 delete Tmp;
397
398 if (Top == Stop)
399 return;
400 }
401
402 Item *Tmp = Top;
403 if (Top != 0)
404 Top = Top->Next;
405 delete Tmp;
406 }
407}
408 /*}}}*/
08e8f724
AL
409// Configuration::Exists - Returns true if the Name exists /*{{{*/
410// ---------------------------------------------------------------------
411/* */
b2e465d6 412bool Configuration::Exists(const char *Name) const
08e8f724 413{
b2e465d6 414 const Item *Itm = Lookup(Name);
08e8f724
AL
415 if (Itm == 0)
416 return false;
417 return true;
418}
419 /*}}}*/
b2e465d6
AL
420// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
421// ---------------------------------------------------------------------
422/* qualified by /[fdbi] exists */
423bool Configuration::ExistsAny(const char *Name) const
424{
425 string key = Name;
426
8d998026
AL
427 if (key.size() > 2 && key.end()[-2] == '/')
428 if (key.find_first_of("fdbi",key.size()-1) < key.size())
429 {
430 key.resize(key.size() - 2);
431 if (Exists(key.c_str()))
432 return true;
433 }
434 else
435 {
69a63027 436 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 437 }
b2e465d6
AL
438
439 return Exists(Name);
440}
441 /*}}}*/
93bf083d
AL
442// Configuration::Dump - Dump the config /*{{{*/
443// ---------------------------------------------------------------------
444/* Dump the entire configuration space */
ff2a211a 445void Configuration::Dump(ostream& str)
93bf083d
AL
446{
447 /* Write out all of the configuration directives by walking the
448 configuration tree */
b2e465d6 449 const Configuration::Item *Top = Tree(0);
93bf083d
AL
450 for (; Top != 0;)
451 {
ff2a211a 452 str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
93bf083d
AL
453
454 if (Top->Child != 0)
455 {
456 Top = Top->Child;
457 continue;
458 }
459
460 while (Top != 0 && Top->Next == 0)
461 Top = Top->Parent;
462 if (Top != 0)
463 Top = Top->Next;
b2e465d6 464 }
93bf083d
AL
465}
466 /*}}}*/
08e8f724 467
0a8a80e5
AL
468// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
469// ---------------------------------------------------------------------
b2e465d6
AL
470/* Stop sets an optional max recursion depth if this item is being viewed as
471 part of a sub tree. */
472string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 473{
b2e465d6 474 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 475 return Tag;
b2e465d6 476 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
477}
478 /*}}}*/
479
08e8f724
AL
480// ReadConfigFile - Read a configuration file /*{{{*/
481// ---------------------------------------------------------------------
482/* The configuration format is very much like the named.conf format
b2e465d6
AL
483 used in bind8, in fact this routine can parse most named.conf files.
484 Sectional config files are like bind's named.conf where there are
485 sections like 'zone "foo.org" { .. };' This causes each section to be
486 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 487 tag/value. AsSectional enables Sectional parsing.*/
b2e465d6
AL
488bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
489 unsigned Depth)
490{
08e8f724 491 // Open the stream for reading
851a45a8 492 ifstream F(FName.c_str(),ios::in);
08e8f724 493 if (!F != 0)
b2e465d6 494 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
08e8f724 495
5e14c452 496 char Buffer[1024];
08e8f724 497 string LineBuffer;
a4e87467
AL
498 string Stack[100];
499 unsigned int StackPos = 0;
08e8f724
AL
500
501 // Parser state
502 string ParentTag;
503
504 int CurLine = 0;
505 bool InComment = false;
506 while (F.eof() == false)
507 {
508 F.getline(Buffer,sizeof(Buffer));
509 CurLine++;
5e14c452
AL
510 // This should be made to work instead, but this is better than looping
511 if (F.fail() && !F.eof())
512 return _error->Error(_("Line %d too long (max %d)"), CurLine, sizeof(Buffer));
513
08e8f724
AL
514 _strtabexpand(Buffer,sizeof(Buffer));
515 _strstrip(Buffer);
516
517 // Multi line comment
518 if (InComment == true)
519 {
520 for (const char *I = Buffer; *I != 0; I++)
521 {
522 if (*I == '*' && I[1] == '/')
523 {
524 memmove(Buffer,I+2,strlen(I+2) + 1);
525 InComment = false;
526 break;
527 }
528 }
529 if (InComment == true)
530 continue;
531 }
532
533 // Discard single line comments
bfd22fc0 534 bool InQuote = false;
08e8f724
AL
535 for (char *I = Buffer; *I != 0; I++)
536 {
bfd22fc0
AL
537 if (*I == '"')
538 InQuote = !InQuote;
539 if (InQuote == true)
540 continue;
541
08e8f724
AL
542 if (*I == '/' && I[1] == '/')
543 {
544 *I = 0;
545 break;
546 }
547 }
7834cb57 548
08e8f724 549 // Look for multi line comments
7834cb57 550 InQuote = false;
08e8f724
AL
551 for (char *I = Buffer; *I != 0; I++)
552 {
bfd22fc0
AL
553 if (*I == '"')
554 InQuote = !InQuote;
555 if (InQuote == true)
556 continue;
557
08e8f724
AL
558 if (*I == '/' && I[1] == '*')
559 {
560 InComment = true;
561 for (char *J = Buffer; *J != 0; J++)
562 {
563 if (*J == '*' && J[1] == '/')
564 {
565 memmove(I,J+2,strlen(J+2) + 1);
566 InComment = false;
567 break;
568 }
569 }
570
571 if (InComment == true)
572 {
573 *I = 0;
574 break;
575 }
576 }
577 }
578
579 // Blank
580 if (Buffer[0] == 0)
581 continue;
582
583 // We now have a valid line fragment
7834cb57 584 InQuote = false;
08e8f724
AL
585 for (char *I = Buffer; *I != 0;)
586 {
7834cb57
AL
587 if (*I == '"')
588 InQuote = !InQuote;
589
590 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 591 {
b2e465d6 592 // Put the last fragment into the buffer
08e8f724
AL
593 char *Start = Buffer;
594 char *Stop = I;
595 for (; Start != I && isspace(*Start) != 0; Start++);
596 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
597 if (LineBuffer.empty() == false && Stop - Start != 0)
598 LineBuffer += ' ';
599 LineBuffer += string(Start,Stop - Start);
600
601 // Remove the fragment
602 char TermChar = *I;
603 memmove(Buffer,I + 1,strlen(I + 1) + 1);
604 I = Buffer;
08e8f724
AL
605
606 // Syntax Error
607 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 608 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 609
b2e465d6 610 // No string on this line
08e8f724 611 if (LineBuffer.empty() == true)
b2e465d6
AL
612 {
613 if (TermChar == '}')
614 {
615 if (StackPos == 0)
616 ParentTag = string();
617 else
618 ParentTag = Stack[--StackPos];
619 }
08e8f724 620 continue;
b2e465d6
AL
621 }
622
08e8f724 623 // Parse off the tag
7f25bdff
AL
624 string Tag;
625 const char *Pos = LineBuffer.c_str();
626 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 627 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
628
629 // Parse off the word
630 string Word;
bcae6dd4 631 bool NoWord = false;
b2e465d6
AL
632 if (ParseCWord(Pos,Word) == false &&
633 ParseQuoteWord(Pos,Word) == false)
634 {
635 if (TermChar != '{')
636 {
637 Word = Tag;
638 Tag = "";
bcae6dd4 639 }
4ae405e9
AL
640 else
641 NoWord = true;
b2e465d6
AL
642 }
643 if (strlen(Pos) != 0)
644 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
645
08e8f724
AL
646 // Go down a level
647 if (TermChar == '{')
648 {
a4e87467
AL
649 if (StackPos <= 100)
650 Stack[StackPos++] = ParentTag;
b2e465d6
AL
651
652 /* Make sectional tags incorperate the section into the
653 tag string */
654 if (AsSectional == true && Word.empty() == false)
655 {
656 Tag += "::" ;
657 Tag += Word;
658 Word = "";
659 }
660
08e8f724
AL
661 if (ParentTag.empty() == true)
662 ParentTag = Tag;
663 else
664 ParentTag += string("::") + Tag;
665 Tag = string();
666 }
bfd22fc0 667
08e8f724
AL
668 // Generate the item name
669 string Item;
670 if (ParentTag.empty() == true)
671 Item = Tag;
672 else
673 {
7f25bdff 674 if (TermChar != '{' || Tag.empty() == false)
08e8f724 675 Item = ParentTag + "::" + Tag;
7f25bdff
AL
676 else
677 Item = ParentTag;
08e8f724
AL
678 }
679
b2e465d6
AL
680 // Specials
681 if (Tag.length() >= 1 && Tag[0] == '#')
682 {
683 if (ParentTag.empty() == false)
684 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
685 Tag.erase(Tag.begin());
686 if (Tag == "clear")
687 Conf.Clear(Word);
688 else if (Tag == "include")
689 {
690 if (Depth > 10)
691 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
692 if (Word.length() > 2 && Word.end()[-1] == '/')
693 {
694 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
695 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
696 }
697 else
698 {
699 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
700 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
701 }
702 }
703 else
704 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
705 }
706 else
707 {
708 // Set the item in the configuration class
bcae6dd4
AL
709 if (NoWord == false)
710 Conf.Set(Item,Word);
b2e465d6
AL
711 }
712
08e8f724
AL
713 // Empty the buffer
714 LineBuffer = string();
b2e465d6
AL
715
716 // Move up a tag, but only if there is no bit to parse
717 if (TermChar == '}')
718 {
719 if (StackPos == 0)
720 ParentTag = string();
721 else
722 ParentTag = Stack[--StackPos];
723 }
724
08e8f724
AL
725 }
726 else
727 I++;
728 }
729
730 // Store the fragment
731 const char *Stripd = _strstrip(Buffer);
732 if (*Stripd != 0 && LineBuffer.empty() == false)
733 LineBuffer += " ";
734 LineBuffer += Stripd;
735 }
b2e465d6
AL
736
737 if (LineBuffer.empty() == false)
738 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
739 return true;
740}
741 /*}}}*/
742// ReadConfigDir - Read a directory of config files /*{{{*/
743// ---------------------------------------------------------------------
744/* */
745bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
746 unsigned Depth)
747{
b2e465d6
AL
748 DIR *D = opendir(Dir.c_str());
749 if (D == 0)
750 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
751
752 vector<string> List;
753
754 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
755 {
721e08d6 756 if (Ent->d_name[0] == '.')
b2e465d6
AL
757 continue;
758
bb3a5465
AL
759 // Skip bad file names ala run-parts
760 const char *C = Ent->d_name;
761 for (; *C != 0; C++)
762 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 763 break;
bb3a5465 764 if (*C != 0)
b2e465d6
AL
765 continue;
766
767 // Make sure it is a file and not something else
768 string File = flCombine(Dir,Ent->d_name);
769 struct stat St;
770 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
771 continue;
772
773 List.push_back(File);
774 }
775 closedir(D);
08e8f724 776
b2e465d6
AL
777 sort(List.begin(),List.end());
778
779 // Read the files
780 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
781 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
782 return false;
08e8f724
AL
783 return true;
784}
785 /*}}}*/