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