/dev/null is a special absolute path as it has no subdirectories
[ntk/apt.git] / apt-pkg / contrib / configuration.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz Exp $
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.
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>.
14
15 ##################################################################### */
16 /*}}}*/
17 // Include files /*{{{*/
18 #include <config.h>
19
20 #include <apt-pkg/configuration.h>
21 #include <apt-pkg/error.h>
22 #include <apt-pkg/strutl.h>
23 #include <apt-pkg/fileutl.h>
24
25 #include <vector>
26 #include <fstream>
27 #include <iostream>
28
29 #include <apti18n.h>
30
31 using namespace std;
32 /*}}}*/
33
34 Configuration *_config = new Configuration;
35
36 // Configuration::Configuration - Constructor /*{{{*/
37 // ---------------------------------------------------------------------
38 /* */
39 Configuration::Configuration() : ToFree(true)
40 {
41 Root = new Item;
42 }
43 Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
44 {
45 };
46
47 /*}}}*/
48 // Configuration::~Configuration - Destructor /*{{{*/
49 // ---------------------------------------------------------------------
50 /* */
51 Configuration::~Configuration()
52 {
53 if (ToFree == false)
54 return;
55
56 Item *Top = Root;
57 for (; Top != 0;)
58 {
59 if (Top->Child != 0)
60 {
61 Top = Top->Child;
62 continue;
63 }
64
65 while (Top != 0 && Top->Next == 0)
66 {
67 Item *Parent = Top->Parent;
68 delete Top;
69 Top = Parent;
70 }
71 if (Top != 0)
72 {
73 Item *Next = Top->Next;
74 delete Top;
75 Top = Next;
76 }
77 }
78 }
79 /*}}}*/
80 // Configuration::Lookup - Lookup a single item /*{{{*/
81 // ---------------------------------------------------------------------
82 /* This will lookup a single item by name below another item. It is a
83 helper function for the main lookup function */
84 Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
85 unsigned long const &Len,bool const &Create)
86 {
87 int Res = 1;
88 Item *I = Head->Child;
89 Item **Last = &Head->Child;
90
91 // Empty strings match nothing. They are used for lists.
92 if (Len != 0)
93 {
94 for (; I != 0; Last = &I->Next, I = I->Next)
95 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
96 break;
97 }
98 else
99 for (; I != 0; Last = &I->Next, I = I->Next);
100
101 if (Res == 0)
102 return I;
103 if (Create == false)
104 return 0;
105
106 I = new Item;
107 I->Tag.assign(S,Len);
108 I->Next = *Last;
109 I->Parent = Head;
110 *Last = I;
111 return I;
112 }
113 /*}}}*/
114 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
115 // ---------------------------------------------------------------------
116 /* This performs a fully scoped lookup of a given name, possibly creating
117 new items */
118 Configuration::Item *Configuration::Lookup(const char *Name,bool const &Create)
119 {
120 if (Name == 0)
121 return Root->Child;
122
123 const char *Start = Name;
124 const char *End = Start + strlen(Name);
125 const char *TagEnd = Name;
126 Item *Itm = Root;
127 for (; End - TagEnd >= 2; TagEnd++)
128 {
129 if (TagEnd[0] == ':' && TagEnd[1] == ':')
130 {
131 Itm = Lookup(Itm,Start,TagEnd - Start,Create);
132 if (Itm == 0)
133 return 0;
134 TagEnd = Start = TagEnd + 2;
135 }
136 }
137
138 // This must be a trailing ::, we create unique items in a list
139 if (End - Start == 0)
140 {
141 if (Create == false)
142 return 0;
143 }
144
145 Itm = Lookup(Itm,Start,End - Start,Create);
146 return Itm;
147 }
148 /*}}}*/
149 // Configuration::Find - Find a value /*{{{*/
150 // ---------------------------------------------------------------------
151 /* */
152 string Configuration::Find(const char *Name,const char *Default) const
153 {
154 const Item *Itm = Lookup(Name);
155 if (Itm == 0 || Itm->Value.empty() == true)
156 {
157 if (Default == 0)
158 return "";
159 else
160 return Default;
161 }
162
163 return Itm->Value;
164 }
165 /*}}}*/
166 // Configuration::FindFile - Find a Filename /*{{{*/
167 // ---------------------------------------------------------------------
168 /* Directories are stored as the base dir in the Parent node and the
169 sub directory in sub nodes with the final node being the end filename
170 */
171 string Configuration::FindFile(const char *Name,const char *Default) const
172 {
173 const Item *RootItem = Lookup("RootDir");
174 std::string result = (RootItem == 0) ? "" : RootItem->Value;
175 if(result.empty() == false && result[result.size() - 1] != '/')
176 result.push_back('/');
177
178 const Item *Itm = Lookup(Name);
179 if (Itm == 0 || Itm->Value.empty() == true)
180 {
181 if (Default != 0)
182 result.append(Default);
183 }
184 else
185 {
186 string val = Itm->Value;
187 while (Itm->Parent != 0)
188 {
189 if (Itm->Parent->Value.empty() == true)
190 {
191 Itm = Itm->Parent;
192 continue;
193 }
194
195 // Absolute
196 if (val.length() >= 1 && val[0] == '/')
197 {
198 if (val.compare(0, 9, "/dev/null") == 0)
199 val.erase(9);
200 break;
201 }
202
203 // ~/foo or ./foo
204 if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
205 break;
206
207 // ../foo
208 if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
209 break;
210
211 if (Itm->Parent->Value.end()[-1] != '/')
212 val.insert(0, "/");
213
214 val.insert(0, Itm->Parent->Value);
215 Itm = Itm->Parent;
216 }
217 result.append(val);
218 }
219
220 // do some normalisation by removing // and /./ from the path
221 size_t found = string::npos;
222 while ((found = result.find("/./")) != string::npos)
223 result.replace(found, 3, "/");
224 while ((found = result.find("//")) != string::npos)
225 result.replace(found, 2, "/");
226
227 return result;
228 }
229 /*}}}*/
230 // Configuration::FindDir - Find a directory name /*{{{*/
231 // ---------------------------------------------------------------------
232 /* This is like findfile execept the result is terminated in a / */
233 string Configuration::FindDir(const char *Name,const char *Default) const
234 {
235 string Res = FindFile(Name,Default);
236 if (Res.end()[-1] != '/')
237 {
238 size_t const found = Res.rfind("/dev/null");
239 if (found != string::npos && found == Res.size() - 9)
240 return Res; // /dev/null returning
241 return Res + '/';
242 }
243 return Res;
244 }
245 /*}}}*/
246 // Configuration::FindVector - Find a vector of values /*{{{*/
247 // ---------------------------------------------------------------------
248 /* Returns a vector of config values under the given item */
249 vector<string> Configuration::FindVector(const char *Name) const
250 {
251 vector<string> Vec;
252 const Item *Top = Lookup(Name);
253 if (Top == NULL)
254 return Vec;
255
256 Item *I = Top->Child;
257 while(I != NULL)
258 {
259 Vec.push_back(I->Value);
260 I = I->Next;
261 }
262 return Vec;
263 }
264 /*}}}*/
265 // Configuration::FindI - Find an integer value /*{{{*/
266 // ---------------------------------------------------------------------
267 /* */
268 int Configuration::FindI(const char *Name,int const &Default) const
269 {
270 const Item *Itm = Lookup(Name);
271 if (Itm == 0 || Itm->Value.empty() == true)
272 return Default;
273
274 char *End;
275 int Res = strtol(Itm->Value.c_str(),&End,0);
276 if (End == Itm->Value.c_str())
277 return Default;
278
279 return Res;
280 }
281 /*}}}*/
282 // Configuration::FindB - Find a boolean type /*{{{*/
283 // ---------------------------------------------------------------------
284 /* */
285 bool Configuration::FindB(const char *Name,bool const &Default) const
286 {
287 const Item *Itm = Lookup(Name);
288 if (Itm == 0 || Itm->Value.empty() == true)
289 return Default;
290
291 return StringToBool(Itm->Value,Default);
292 }
293 /*}}}*/
294 // Configuration::FindAny - Find an arbitrary type /*{{{*/
295 // ---------------------------------------------------------------------
296 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
297 string Configuration::FindAny(const char *Name,const char *Default) const
298 {
299 string key = Name;
300 char type = 0;
301
302 if (key.size() > 2 && key.end()[-2] == '/')
303 {
304 type = key.end()[-1];
305 key.resize(key.size() - 2);
306 }
307
308 switch (type)
309 {
310 // file
311 case 'f':
312 return FindFile(key.c_str(), Default);
313
314 // directory
315 case 'd':
316 return FindDir(key.c_str(), Default);
317
318 // bool
319 case 'b':
320 return FindB(key, Default) ? "true" : "false";
321
322 // int
323 case 'i':
324 {
325 char buf[16];
326 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
327 return buf;
328 }
329 }
330
331 // fallback
332 return Find(Name, Default);
333 }
334 /*}}}*/
335 // Configuration::CndSet - Conditinal Set a value /*{{{*/
336 // ---------------------------------------------------------------------
337 /* This will not overwrite */
338 void Configuration::CndSet(const char *Name,const string &Value)
339 {
340 Item *Itm = Lookup(Name,true);
341 if (Itm == 0)
342 return;
343 if (Itm->Value.empty() == true)
344 Itm->Value = Value;
345 }
346 /*}}}*/
347 // Configuration::Set - Set an integer value /*{{{*/
348 // ---------------------------------------------------------------------
349 /* */
350 void Configuration::CndSet(const char *Name,int const Value)
351 {
352 Item *Itm = Lookup(Name,true);
353 if (Itm == 0 || Itm->Value.empty() == false)
354 return;
355 char S[300];
356 snprintf(S,sizeof(S),"%i",Value);
357 Itm->Value = S;
358 }
359 /*}}}*/
360 // Configuration::Set - Set a value /*{{{*/
361 // ---------------------------------------------------------------------
362 /* */
363 void Configuration::Set(const char *Name,const string &Value)
364 {
365 Item *Itm = Lookup(Name,true);
366 if (Itm == 0)
367 return;
368 Itm->Value = Value;
369 }
370 /*}}}*/
371 // Configuration::Set - Set an integer value /*{{{*/
372 // ---------------------------------------------------------------------
373 /* */
374 void Configuration::Set(const char *Name,int const &Value)
375 {
376 Item *Itm = Lookup(Name,true);
377 if (Itm == 0)
378 return;
379 char S[300];
380 snprintf(S,sizeof(S),"%i",Value);
381 Itm->Value = S;
382 }
383 /*}}}*/
384 // Configuration::Clear - Clear an single value from a list /*{{{*/
385 // ---------------------------------------------------------------------
386 /* */
387 void Configuration::Clear(string const &Name, int const &Value)
388 {
389 char S[300];
390 snprintf(S,sizeof(S),"%i",Value);
391 Clear(Name, S);
392 }
393 /*}}}*/
394 // Configuration::Clear - Clear an single value from a list /*{{{*/
395 // ---------------------------------------------------------------------
396 /* */
397 void Configuration::Clear(string const &Name, string const &Value)
398 {
399 Item *Top = Lookup(Name.c_str(),false);
400 if (Top == 0 || Top->Child == 0)
401 return;
402
403 Item *Tmp, *Prev, *I;
404 Prev = I = Top->Child;
405
406 while(I != NULL)
407 {
408 if(I->Value == Value)
409 {
410 Tmp = I;
411 // was first element, point parent to new first element
412 if(Top->Child == Tmp)
413 Top->Child = I->Next;
414 I = I->Next;
415 Prev->Next = I;
416 delete Tmp;
417 } else {
418 Prev = I;
419 I = I->Next;
420 }
421 }
422
423 }
424 /*}}}*/
425 // Configuration::Clear - Clear an entire tree /*{{{*/
426 // ---------------------------------------------------------------------
427 /* */
428 void Configuration::Clear(string const &Name)
429 {
430 Item *Top = Lookup(Name.c_str(),false);
431 if (Top == 0)
432 return;
433
434 Top->Value.clear();
435 Item *Stop = Top;
436 Top = Top->Child;
437 Stop->Child = 0;
438 for (; Top != 0;)
439 {
440 if (Top->Child != 0)
441 {
442 Top = Top->Child;
443 continue;
444 }
445
446 while (Top != 0 && Top->Next == 0)
447 {
448 Item *Tmp = Top;
449 Top = Top->Parent;
450 delete Tmp;
451
452 if (Top == Stop)
453 return;
454 }
455
456 Item *Tmp = Top;
457 if (Top != 0)
458 Top = Top->Next;
459 delete Tmp;
460 }
461 }
462 /*}}}*/
463 // Configuration::Exists - Returns true if the Name exists /*{{{*/
464 // ---------------------------------------------------------------------
465 /* */
466 bool Configuration::Exists(const char *Name) const
467 {
468 const Item *Itm = Lookup(Name);
469 if (Itm == 0)
470 return false;
471 return true;
472 }
473 /*}}}*/
474 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
475 // ---------------------------------------------------------------------
476 /* qualified by /[fdbi] exists */
477 bool Configuration::ExistsAny(const char *Name) const
478 {
479 string key = Name;
480
481 if (key.size() > 2 && key.end()[-2] == '/')
482 {
483 if (key.find_first_of("fdbi",key.size()-1) < key.size())
484 {
485 key.resize(key.size() - 2);
486 if (Exists(key.c_str()))
487 return true;
488 }
489 else
490 {
491 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
492 }
493 }
494 return Exists(Name);
495 }
496 /*}}}*/
497 // Configuration::Dump - Dump the config /*{{{*/
498 // ---------------------------------------------------------------------
499 /* Dump the entire configuration space */
500 void Configuration::Dump(ostream& str)
501 {
502 Dump(str, NULL, "%f \"%v\";\n", true);
503 }
504 void Configuration::Dump(ostream& str, char const * const root,
505 char const * const formatstr, bool const emptyValue)
506 {
507 const Configuration::Item* Top = Tree(root);
508 if (Top == 0)
509 return;
510 const Configuration::Item* const Root = (root == NULL) ? NULL : Top;
511 std::vector<std::string> const format = VectorizeString(formatstr, '%');
512
513 /* Write out all of the configuration directives by walking the
514 configuration tree */
515 do {
516 if (emptyValue == true || Top->Value.empty() == emptyValue)
517 {
518 std::vector<std::string>::const_iterator f = format.begin();
519 str << *f;
520 for (++f; f != format.end(); ++f)
521 {
522 if (f->empty() == true)
523 {
524 ++f;
525 str << '%' << *f;
526 continue;
527 }
528 char const type = (*f)[0];
529 if (type == 'f')
530 str << Top->FullTag();
531 else if (type == 't')
532 str << Top->Tag;
533 else if (type == 'v')
534 str << Top->Value;
535 else if (type == 'F')
536 str << QuoteString(Top->FullTag(), "=\"\n");
537 else if (type == 'T')
538 str << QuoteString(Top->Tag, "=\"\n");
539 else if (type == 'V')
540 str << QuoteString(Top->Value, "=\"\n");
541 else if (type == 'n')
542 str << "\n";
543 else if (type == 'N')
544 str << "\t";
545 else
546 str << '%' << type;
547 str << f->c_str() + 1;
548 }
549 }
550
551 if (Top->Child != 0)
552 {
553 Top = Top->Child;
554 continue;
555 }
556
557 while (Top != 0 && Top->Next == 0)
558 Top = Top->Parent;
559 if (Top != 0)
560 Top = Top->Next;
561
562 if (Root != NULL)
563 {
564 const Configuration::Item* I = Top;
565 while(I != 0)
566 {
567 if (I == Root)
568 break;
569 else
570 I = I->Parent;
571 }
572 if (I == 0)
573 break;
574 }
575 } while (Top != 0);
576 }
577 /*}}}*/
578
579 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
580 // ---------------------------------------------------------------------
581 /* Stop sets an optional max recursion depth if this item is being viewed as
582 part of a sub tree. */
583 string Configuration::Item::FullTag(const Item *Stop) const
584 {
585 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
586 return Tag;
587 return Parent->FullTag(Stop) + "::" + Tag;
588 }
589 /*}}}*/
590
591 // ReadConfigFile - Read a configuration file /*{{{*/
592 // ---------------------------------------------------------------------
593 /* The configuration format is very much like the named.conf format
594 used in bind8, in fact this routine can parse most named.conf files.
595 Sectional config files are like bind's named.conf where there are
596 sections like 'zone "foo.org" { .. };' This causes each section to be
597 added in with a tag like "zone::foo.org" instead of being split
598 tag/value. AsSectional enables Sectional parsing.*/
599 bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional,
600 unsigned const &Depth)
601 {
602 // Open the stream for reading
603 ifstream F(FName.c_str(),ios::in);
604 if (!F != 0)
605 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
606
607 string LineBuffer;
608 string Stack[100];
609 unsigned int StackPos = 0;
610
611 // Parser state
612 string ParentTag;
613
614 int CurLine = 0;
615 bool InComment = false;
616 while (F.eof() == false)
617 {
618 // The raw input line.
619 std::string Input;
620 // The input line with comments stripped.
621 std::string Fragment;
622
623 // Grab the next line of F and place it in Input.
624 do
625 {
626 char *Buffer = new char[1024];
627
628 F.clear();
629 F.getline(Buffer,sizeof(Buffer) / 2);
630
631 Input += Buffer;
632 delete[] Buffer;
633 }
634 while (F.fail() && !F.eof());
635
636 // Expand tabs in the input line and remove leading and trailing
637 // whitespace.
638 {
639 const int BufferSize = Input.size() * 8 + 1;
640 char *Buffer = new char[BufferSize];
641 try
642 {
643 memcpy(Buffer, Input.c_str(), Input.size() + 1);
644
645 _strtabexpand(Buffer, BufferSize);
646 _strstrip(Buffer);
647 Input = Buffer;
648 }
649 catch(...)
650 {
651 delete[] Buffer;
652 throw;
653 }
654 delete[] Buffer;
655 }
656 CurLine++;
657
658 // Now strip comments; if the whole line is contained in a
659 // comment, skip this line.
660
661 // The first meaningful character in the current fragment; will
662 // be adjusted below as we remove bytes from the front.
663 std::string::const_iterator Start = Input.begin();
664 // The last meaningful character in the current fragment.
665 std::string::const_iterator End = Input.end();
666
667 // Multi line comment
668 if (InComment == true)
669 {
670 for (std::string::const_iterator I = Start;
671 I != End; ++I)
672 {
673 if (*I == '*' && I + 1 != End && I[1] == '/')
674 {
675 Start = I + 2;
676 InComment = false;
677 break;
678 }
679 }
680 if (InComment == true)
681 continue;
682 }
683
684 // Discard single line comments
685 bool InQuote = false;
686 for (std::string::const_iterator I = Start;
687 I != End; ++I)
688 {
689 if (*I == '"')
690 InQuote = !InQuote;
691 if (InQuote == true)
692 continue;
693
694 if ((*I == '/' && I + 1 != End && I[1] == '/') ||
695 (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 &&
696 strcmp(string(I,I+8).c_str(),"#include") != 0))
697 {
698 End = I;
699 break;
700 }
701 }
702
703 // Look for multi line comments and build up the
704 // fragment.
705 Fragment.reserve(End - Start);
706 InQuote = false;
707 for (std::string::const_iterator I = Start;
708 I != End; ++I)
709 {
710 if (*I == '"')
711 InQuote = !InQuote;
712 if (InQuote == true)
713 Fragment.push_back(*I);
714 else if (*I == '/' && I + 1 != End && I[1] == '*')
715 {
716 InComment = true;
717 for (std::string::const_iterator J = I;
718 J != End; ++J)
719 {
720 if (*J == '*' && J + 1 != End && J[1] == '/')
721 {
722 // Pretend we just finished walking over the
723 // comment, and don't add anything to the output
724 // fragment.
725 I = J + 1;
726 InComment = false;
727 break;
728 }
729 }
730
731 if (InComment == true)
732 break;
733 }
734 else
735 Fragment.push_back(*I);
736 }
737
738 // Skip blank lines.
739 if (Fragment.empty())
740 continue;
741
742 // The line has actual content; interpret what it means.
743 InQuote = false;
744 Start = Fragment.begin();
745 End = Fragment.end();
746 for (std::string::const_iterator I = Start;
747 I != End; ++I)
748 {
749 if (*I == '"')
750 InQuote = !InQuote;
751
752 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
753 {
754 // Put the last fragment into the buffer
755 std::string::const_iterator NonWhitespaceStart = Start;
756 std::string::const_iterator NonWhitespaceStop = I;
757 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart)
758 ;
759 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop)
760 ;
761 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
762 LineBuffer += ' ';
763 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
764
765 // Drop this from the input string, saving the character
766 // that terminated the construct we just closed. (i.e., a
767 // brace or a semicolon)
768 char TermChar = *I;
769 Start = I + 1;
770
771 // Syntax Error
772 if (TermChar == '{' && LineBuffer.empty() == true)
773 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
774
775 // No string on this line
776 if (LineBuffer.empty() == true)
777 {
778 if (TermChar == '}')
779 {
780 if (StackPos == 0)
781 ParentTag = string();
782 else
783 ParentTag = Stack[--StackPos];
784 }
785 continue;
786 }
787
788 // Parse off the tag
789 string Tag;
790 const char *Pos = LineBuffer.c_str();
791 if (ParseQuoteWord(Pos,Tag) == false)
792 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
793
794 // Parse off the word
795 string Word;
796 bool NoWord = false;
797 if (ParseCWord(Pos,Word) == false &&
798 ParseQuoteWord(Pos,Word) == false)
799 {
800 if (TermChar != '{')
801 {
802 Word = Tag;
803 Tag = "";
804 }
805 else
806 NoWord = true;
807 }
808 if (strlen(Pos) != 0)
809 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
810
811 // Go down a level
812 if (TermChar == '{')
813 {
814 if (StackPos <= 100)
815 Stack[StackPos++] = ParentTag;
816
817 /* Make sectional tags incorperate the section into the
818 tag string */
819 if (AsSectional == true && Word.empty() == false)
820 {
821 Tag += "::" ;
822 Tag += Word;
823 Word = "";
824 }
825
826 if (ParentTag.empty() == true)
827 ParentTag = Tag;
828 else
829 ParentTag += string("::") + Tag;
830 Tag = string();
831 }
832
833 // Generate the item name
834 string Item;
835 if (ParentTag.empty() == true)
836 Item = Tag;
837 else
838 {
839 if (TermChar != '{' || Tag.empty() == false)
840 Item = ParentTag + "::" + Tag;
841 else
842 Item = ParentTag;
843 }
844
845 // Specials
846 if (Tag.length() >= 1 && Tag[0] == '#')
847 {
848 if (ParentTag.empty() == false)
849 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
850 Tag.erase(Tag.begin());
851 if (Tag == "clear")
852 Conf.Clear(Word);
853 else if (Tag == "include")
854 {
855 if (Depth > 10)
856 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
857 if (Word.length() > 2 && Word.end()[-1] == '/')
858 {
859 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
860 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
861 }
862 else
863 {
864 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
865 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
866 }
867 }
868 else
869 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
870 }
871 else if (Tag.empty() == true && NoWord == false && Word == "#clear")
872 return _error->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName.c_str(),CurLine);
873 else
874 {
875 // Set the item in the configuration class
876 if (NoWord == false)
877 Conf.Set(Item,Word);
878 }
879
880 // Empty the buffer
881 LineBuffer.clear();
882
883 // Move up a tag, but only if there is no bit to parse
884 if (TermChar == '}')
885 {
886 if (StackPos == 0)
887 ParentTag.clear();
888 else
889 ParentTag = Stack[--StackPos];
890 }
891
892 }
893 }
894
895 // Store the remaining text, if any, in the current line buffer.
896
897 // NB: could change this to use string-based operations; I'm
898 // using strstrip now to ensure backwards compatibility.
899 // -- dburrows 2008-04-01
900 {
901 char *Buffer = new char[End - Start + 1];
902 try
903 {
904 std::copy(Start, End, Buffer);
905 Buffer[End - Start] = '\0';
906
907 const char *Stripd = _strstrip(Buffer);
908 if (*Stripd != 0 && LineBuffer.empty() == false)
909 LineBuffer += " ";
910 LineBuffer += Stripd;
911 }
912 catch(...)
913 {
914 delete[] Buffer;
915 throw;
916 }
917 delete[] Buffer;
918 }
919 }
920
921 if (LineBuffer.empty() == false)
922 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
923 return true;
924 }
925 /*}}}*/
926 // ReadConfigDir - Read a directory of config files /*{{{*/
927 // ---------------------------------------------------------------------
928 /* */
929 bool ReadConfigDir(Configuration &Conf,const string &Dir,
930 bool const &AsSectional, unsigned const &Depth)
931 {
932 vector<string> const List = GetListOfFilesInDir(Dir, "conf", true, true);
933
934 // Read the files
935 for (vector<string>::const_iterator I = List.begin(); I != List.end(); ++I)
936 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
937 return false;
938 return true;
939 }
940 /*}}}*/
941 // MatchAgainstConfig Constructor /*{{{*/
942 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config)
943 {
944 std::vector<std::string> const strings = _config->FindVector(Config);
945 for (std::vector<std::string>::const_iterator s = strings.begin();
946 s != strings.end(); ++s)
947 {
948 regex_t *p = new regex_t;
949 if (regcomp(p, s->c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0)
950 patterns.push_back(p);
951 else
952 {
953 regfree(p);
954 delete p;
955 _error->Warning("Invalid regular expression '%s' in configuration "
956 "option '%s' will be ignored.",
957 s->c_str(), Config);
958 continue;
959 }
960 }
961 if (strings.size() == 0)
962 patterns.push_back(NULL);
963 }
964 /*}}}*/
965 // MatchAgainstConfig Destructor /*{{{*/
966 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
967 {
968 clearPatterns();
969 }
970 void Configuration::MatchAgainstConfig::clearPatterns()
971 {
972 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
973 p != patterns.end(); ++p)
974 {
975 if (*p == NULL) continue;
976 regfree(*p);
977 delete *p;
978 }
979 patterns.clear();
980 }
981 /*}}}*/
982 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
983 bool Configuration::MatchAgainstConfig::Match(char const * str) const
984 {
985 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
986 p != patterns.end(); ++p)
987 if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0)
988 return true;
989
990 return false;
991 }
992 /*}}}*/