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