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