Remove a debugging message that I left in by accident (if it ever triggers the progra...
[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());
3f807f6c 498
08e8f724 499 string LineBuffer;
a4e87467
AL
500 string Stack[100];
501 unsigned int StackPos = 0;
08e8f724
AL
502
503 // Parser state
504 string ParentTag;
505
506 int CurLine = 0;
507 bool InComment = false;
508 while (F.eof() == false)
509 {
3f807f6c
DB
510 // The raw input line.
511 std::string Input;
512 // The input line with comments stripped.
513 std::string Fragment;
8a27f5da
DB
514
515 // Grab the next line of F and place it in Input.
3f807f6c
DB
516 do
517 {
518 char *Buffer = new char[1024];
519
520 F.clear();
521 F.getline(Buffer,sizeof(Buffer) / 2);
522
523 Input += Buffer;
524 }
525 while (F.fail() && !F.eof());
526
8a27f5da
DB
527 // Expand tabs in the input line and remove leading and trailing
528 // whitespace.
3f807f6c 529 {
3f807f6c
DB
530 const int BufferSize = Input.size() * 8 + 1;
531 char *Buffer = new char[BufferSize];
532 try
533 {
534 memcpy(Buffer, Input.c_str(), Input.size() + 1);
535
536 _strtabexpand(Buffer, BufferSize);
537 _strstrip(Buffer);
538 Input = Buffer;
539 }
540 catch(...)
541 {
542 delete[] Buffer;
543 throw;
544 }
545 delete[] Buffer;
546 }
08e8f724 547 CurLine++;
5e14c452 548
8a27f5da
DB
549 // Now strip comments; if the whole line is contained in a
550 // comment, skip this line.
551
3f807f6c
DB
552 // The first meaningful character in the current fragment; will
553 // be adjusted below as we remove bytes from the front.
554 std::string::const_iterator Start = Input.begin();
555 // The last meaningful character in the current fragment.
556 std::string::const_iterator End = Input.end();
08e8f724
AL
557
558 // Multi line comment
559 if (InComment == true)
560 {
3f807f6c
DB
561 for (std::string::const_iterator I = Start;
562 I != End; ++I)
08e8f724 563 {
3f807f6c 564 if (*I == '*' && I + 1 != End && I[1] == '/')
08e8f724 565 {
3f807f6c 566 Start = I + 2;
08e8f724
AL
567 InComment = false;
568 break;
569 }
570 }
571 if (InComment == true)
572 continue;
573 }
574
575 // Discard single line comments
bfd22fc0 576 bool InQuote = false;
3f807f6c
DB
577 for (std::string::const_iterator I = Start;
578 I != End; ++I)
08e8f724 579 {
bfd22fc0
AL
580 if (*I == '"')
581 InQuote = !InQuote;
582 if (InQuote == true)
583 continue;
584
3f807f6c 585 if (*I == '/' && I + 1 != End && I[1] == '/')
08e8f724 586 {
3f807f6c 587 End = I;
08e8f724
AL
588 break;
589 }
590 }
7834cb57 591
3f807f6c
DB
592 // Look for multi line comments and build up the
593 // fragment.
594 Fragment.reserve(End - Start);
7834cb57 595 InQuote = false;
3f807f6c
DB
596 for (std::string::const_iterator I = Start;
597 I != End; ++I)
08e8f724 598 {
bfd22fc0
AL
599 if (*I == '"')
600 InQuote = !InQuote;
601 if (InQuote == true)
3f807f6c
DB
602 Fragment.push_back(*I);
603 else if (*I == '/' && I + 1 != End && I[1] == '*')
08e8f724
AL
604 {
605 InComment = true;
3f807f6c
DB
606 for (std::string::const_iterator J = I;
607 J != End; ++J)
08e8f724 608 {
3f807f6c 609 if (*J == '*' && J + 1 != End && J[1] == '/')
08e8f724 610 {
3f807f6c
DB
611 // Pretend we just finished walking over the
612 // comment, and don't add anything to the output
613 // fragment.
614 I = J + 1;
08e8f724
AL
615 InComment = false;
616 break;
617 }
618 }
619
620 if (InComment == true)
3f807f6c 621 break;
08e8f724 622 }
3f807f6c
DB
623 else
624 Fragment.push_back(*I);
08e8f724 625 }
3f807f6c
DB
626
627 // Skip blank lines.
628 if (Fragment.empty())
08e8f724
AL
629 continue;
630
8a27f5da 631 // The line has actual content; interpret what it means.
7834cb57 632 InQuote = false;
3f807f6c
DB
633 Start = Fragment.begin();
634 End = Fragment.end();
635 for (std::string::const_iterator I = Start;
636 I != End; ++I)
08e8f724 637 {
7834cb57
AL
638 if (*I == '"')
639 InQuote = !InQuote;
640
641 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 642 {
b2e465d6 643 // Put the last fragment into the buffer
3f807f6c
DB
644 std::string::const_iterator FirstNonWhitespace = Start;
645 std::string::const_iterator LastNonWhitespace = I;
646 for (; FirstNonWhitespace != I && isspace(*FirstNonWhitespace) != 0; FirstNonWhitespace++)
647 ;
648 for (; LastNonWhitespace != FirstNonWhitespace && isspace(LastNonWhitespace[-1]) != 0; LastNonWhitespace--)
649 ;
650 if (LineBuffer.empty() == false && LastNonWhitespace - FirstNonWhitespace != 0)
08e8f724 651 LineBuffer += ' ';
3f807f6c
DB
652 LineBuffer += string(FirstNonWhitespace, LastNonWhitespace);
653
654 // Drop this from the input string, saving the character
655 // that terminated the construct we just closed. (i.e., a
656 // brace or a semicolon)
08e8f724 657 char TermChar = *I;
3f807f6c 658 Start = I + 1;
08e8f724
AL
659
660 // Syntax Error
661 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 662 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 663
b2e465d6 664 // No string on this line
08e8f724 665 if (LineBuffer.empty() == true)
b2e465d6
AL
666 {
667 if (TermChar == '}')
668 {
669 if (StackPos == 0)
670 ParentTag = string();
671 else
672 ParentTag = Stack[--StackPos];
673 }
08e8f724 674 continue;
b2e465d6
AL
675 }
676
08e8f724 677 // Parse off the tag
7f25bdff
AL
678 string Tag;
679 const char *Pos = LineBuffer.c_str();
680 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 681 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
682
683 // Parse off the word
684 string Word;
bcae6dd4 685 bool NoWord = false;
b2e465d6
AL
686 if (ParseCWord(Pos,Word) == false &&
687 ParseQuoteWord(Pos,Word) == false)
688 {
689 if (TermChar != '{')
690 {
691 Word = Tag;
692 Tag = "";
bcae6dd4 693 }
4ae405e9
AL
694 else
695 NoWord = true;
b2e465d6
AL
696 }
697 if (strlen(Pos) != 0)
698 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
699
08e8f724
AL
700 // Go down a level
701 if (TermChar == '{')
702 {
a4e87467
AL
703 if (StackPos <= 100)
704 Stack[StackPos++] = ParentTag;
b2e465d6
AL
705
706 /* Make sectional tags incorperate the section into the
707 tag string */
708 if (AsSectional == true && Word.empty() == false)
709 {
710 Tag += "::" ;
711 Tag += Word;
712 Word = "";
713 }
714
08e8f724
AL
715 if (ParentTag.empty() == true)
716 ParentTag = Tag;
717 else
718 ParentTag += string("::") + Tag;
719 Tag = string();
720 }
bfd22fc0 721
08e8f724
AL
722 // Generate the item name
723 string Item;
724 if (ParentTag.empty() == true)
725 Item = Tag;
726 else
727 {
7f25bdff 728 if (TermChar != '{' || Tag.empty() == false)
08e8f724 729 Item = ParentTag + "::" + Tag;
7f25bdff
AL
730 else
731 Item = ParentTag;
08e8f724
AL
732 }
733
b2e465d6
AL
734 // Specials
735 if (Tag.length() >= 1 && Tag[0] == '#')
736 {
737 if (ParentTag.empty() == false)
738 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
739 Tag.erase(Tag.begin());
740 if (Tag == "clear")
741 Conf.Clear(Word);
742 else if (Tag == "include")
743 {
744 if (Depth > 10)
745 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
746 if (Word.length() > 2 && Word.end()[-1] == '/')
747 {
748 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
749 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
750 }
751 else
752 {
753 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
754 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
755 }
756 }
757 else
758 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
759 }
760 else
761 {
762 // Set the item in the configuration class
bcae6dd4
AL
763 if (NoWord == false)
764 Conf.Set(Item,Word);
b2e465d6
AL
765 }
766
08e8f724 767 // Empty the buffer
171c75f1 768 LineBuffer.clear();
b2e465d6
AL
769
770 // Move up a tag, but only if there is no bit to parse
771 if (TermChar == '}')
772 {
773 if (StackPos == 0)
171c75f1 774 ParentTag.clear();
b2e465d6
AL
775 else
776 ParentTag = Stack[--StackPos];
777 }
778
08e8f724 779 }
08e8f724
AL
780 }
781
3f807f6c
DB
782 // Store the remaining text, if any, in the current line buffer.
783
784 // NB: could change this to use string-based operations; I'm
785 // using strstrip now to ensure backwards compatibility.
786 // -- dburrows 2008-04-01
787 {
788 char *Buffer = new char[End - Start + 1];
789 try
790 {
791 std::copy(Start, End, Buffer);
792 Buffer[End - Start] = '\0';
793
794 const char *Stripd = _strstrip(Buffer);
795 if (*Stripd != 0 && LineBuffer.empty() == false)
796 LineBuffer += " ";
797 LineBuffer += Stripd;
798 }
799 catch(...)
800 {
801 delete[] Buffer;
802 throw;
803 }
804 delete[] Buffer;
805 }
08e8f724 806 }
b2e465d6
AL
807
808 if (LineBuffer.empty() == false)
809 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
810 return true;
811}
812 /*}}}*/
813// ReadConfigDir - Read a directory of config files /*{{{*/
814// ---------------------------------------------------------------------
815/* */
171c75f1
MV
816bool ReadConfigDir(Configuration &Conf,const string &Dir,bool AsSectional,
817 unsigned Depth)
b2e465d6 818{
b2e465d6
AL
819 DIR *D = opendir(Dir.c_str());
820 if (D == 0)
821 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
822
823 vector<string> List;
824
825 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
826 {
721e08d6 827 if (Ent->d_name[0] == '.')
b2e465d6
AL
828 continue;
829
bb3a5465
AL
830 // Skip bad file names ala run-parts
831 const char *C = Ent->d_name;
832 for (; *C != 0; C++)
833 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 834 break;
bb3a5465 835 if (*C != 0)
b2e465d6
AL
836 continue;
837
838 // Make sure it is a file and not something else
839 string File = flCombine(Dir,Ent->d_name);
840 struct stat St;
841 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
842 continue;
843
844 List.push_back(File);
845 }
846 closedir(D);
08e8f724 847
b2e465d6
AL
848 sort(List.begin(),List.end());
849
850 // Read the files
851 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
852 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
853 return false;
08e8f724
AL
854 return true;
855}
856 /*}}}*/