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