]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
properly format multiline error messages
[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>
453b82a3 24#include <apt-pkg/macros.h>
6c139d6e 25
453b82a3
DK
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>
b2e465d6 34#include <vector>
b2e465d6 35#include <fstream>
851a45a8 36
ea542140
DK
37#include <apti18n.h>
38
851a45a8 39using namespace std;
6c139d6e 40 /*}}}*/
9c14e3d6
AL
41
42Configuration *_config = new Configuration;
6c139d6e
AL
43
44// Configuration::Configuration - Constructor /*{{{*/
45// ---------------------------------------------------------------------
46/* */
b2e465d6 47Configuration::Configuration() : ToFree(true)
6c139d6e
AL
48{
49 Root = new Item;
b2e465d6
AL
50}
51Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
52{
d3e8fbb3 53}
b2e465d6
AL
54 /*}}}*/
55// Configuration::~Configuration - Destructor /*{{{*/
56// ---------------------------------------------------------------------
57/* */
58Configuration::~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 }
6c139d6e
AL
85}
86 /*}}}*/
0a8a80e5 87// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
88// ---------------------------------------------------------------------
89/* This will lookup a single item by name below another item. It is a
90 helper function for the main lookup function */
91Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
ce857f32 92 unsigned long const &Len,bool const &Create)
6c139d6e
AL
93{
94 int Res = 1;
95 Item *I = Head->Child;
96 Item **Last = &Head->Child;
9c14e3d6 97
7f25bdff
AL
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)
851a45a8 102 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
7f25bdff
AL
103 break;
104 }
105 else
106 for (; I != 0; Last = &I->Next, I = I->Next);
107
6c139d6e
AL
108 if (Res == 0)
109 return I;
110 if (Create == false)
111 return 0;
112
113 I = new Item;
171c75f1 114 I->Tag.assign(S,Len);
6c139d6e 115 I->Next = *Last;
9c14e3d6 116 I->Parent = Head;
6c139d6e
AL
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 */
ce857f32 125Configuration::Item *Configuration::Lookup(const char *Name,bool const &Create)
6c139d6e 126{
0a8a80e5
AL
127 if (Name == 0)
128 return Root->Child;
129
6c139d6e
AL
130 const char *Start = Name;
131 const char *End = Start + strlen(Name);
132 const char *TagEnd = Name;
133 Item *Itm = Root;
7f25bdff 134 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
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
7f25bdff
AL
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
6c139d6e 152 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
153 return Itm;
154}
155 /*}}}*/
156// Configuration::Find - Find a value /*{{{*/
157// ---------------------------------------------------------------------
158/* */
b2e465d6 159string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 160{
b2e465d6 161 const Item *Itm = Lookup(Name);
6c139d6e 162 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
163 {
164 if (Default == 0)
171c75f1 165 return "";
9c14e3d6
AL
166 else
167 return Default;
168 }
169
6c139d6e
AL
170 return Itm->Value;
171}
172 /*}}}*/
3b5421b4 173// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
174// ---------------------------------------------------------------------
175/* Directories are stored as the base dir in the Parent node and the
3b5421b4 176 sub directory in sub nodes with the final node being the end filename
9c14e3d6 177 */
b2e465d6 178string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 179{
db2cca11 180 const Item *RootItem = Lookup("RootDir");
ec76891f
DK
181 std::string result = (RootItem == 0) ? "" : RootItem->Value;
182 if(result.empty() == false && result[result.size() - 1] != '/')
183 result.push_back('/');
db2cca11 184
b2e465d6 185 const Item *Itm = Lookup(Name);
9c14e3d6
AL
186 if (Itm == 0 || Itm->Value.empty() == true)
187 {
ec76891f
DK
188 if (Default != 0)
189 result.append(Default);
9c14e3d6 190 }
ec76891f 191 else
017f9fd6 192 {
ec76891f
DK
193 string val = Itm->Value;
194 while (Itm->Parent != 0)
017f9fd6 195 {
ec76891f
DK
196 if (Itm->Parent->Value.empty() == true)
197 {
198 Itm = Itm->Parent;
199 continue;
200 }
017f9fd6 201
ec76891f
DK
202 // Absolute
203 if (val.length() >= 1 && val[0] == '/')
af13d143
DK
204 {
205 if (val.compare(0, 9, "/dev/null") == 0)
206 val.erase(9);
ec76891f 207 break;
af13d143 208 }
b2e465d6 209
ec76891f
DK
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);
b2e465d6
AL
225 }
226
ec76891f
DK
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;
9c14e3d6
AL
235}
236 /*}}}*/
3b5421b4
AL
237// Configuration::FindDir - Find a directory name /*{{{*/
238// ---------------------------------------------------------------------
239/* This is like findfile execept the result is terminated in a / */
b2e465d6 240string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
241{
242 string Res = FindFile(Name,Default);
243 if (Res.end()[-1] != '/')
af13d143
DK
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
3b5421b4 248 return Res + '/';
af13d143 249 }
3b5421b4
AL
250 return Res;
251}
252 /*}}}*/
50c409c4
DK
253// Configuration::FindVector - Find a vector of values /*{{{*/
254// ---------------------------------------------------------------------
255/* Returns a vector of config values under the given item */
1e0f0f28 256vector<string> Configuration::FindVector(const char *Name, std::string const &Default, bool const Keys) const
50c409c4
DK
257{
258 vector<string> Vec;
259 const Item *Top = Lookup(Name);
260 if (Top == NULL)
a5414e56
DK
261 return VectorizeString(Default, ',');
262
263 if (Top->Value.empty() == false)
264 return VectorizeString(Top->Value, ',');
50c409c4
DK
265
266 Item *I = Top->Child;
267 while(I != NULL)
268 {
1e0f0f28 269 Vec.push_back(Keys ? I->Tag : I->Value);
50c409c4
DK
270 I = I->Next;
271 }
a5414e56
DK
272 if (Vec.empty() == true)
273 return VectorizeString(Default, ',');
274
50c409c4
DK
275 return Vec;
276}
277 /*}}}*/
6c139d6e
AL
278// Configuration::FindI - Find an integer value /*{{{*/
279// ---------------------------------------------------------------------
280/* */
ce857f32 281int Configuration::FindI(const char *Name,int const &Default) const
6c139d6e 282{
b2e465d6 283 const Item *Itm = Lookup(Name);
6c139d6e
AL
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
08e8f724
AL
292 return Res;
293}
294 /*}}}*/
295// Configuration::FindB - Find a boolean type /*{{{*/
296// ---------------------------------------------------------------------
297/* */
ce857f32 298bool Configuration::FindB(const char *Name,bool const &Default) const
08e8f724 299{
b2e465d6 300 const Item *Itm = Lookup(Name);
08e8f724
AL
301 if (Itm == 0 || Itm->Value.empty() == true)
302 return Default;
303
3b5421b4 304 return StringToBool(Itm->Value,Default);
6c139d6e
AL
305}
306 /*}}}*/
b2e465d6
AL
307// Configuration::FindAny - Find an arbitrary type /*{{{*/
308// ---------------------------------------------------------------------
309/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
310string 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];
cc99a102 339 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
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 */
171c75f1 351void Configuration::CndSet(const char *Name,const string &Value)
b2e465d6
AL
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 /*}}}*/
78485ab2
DK
360// Configuration::Set - Set an integer value /*{{{*/
361// ---------------------------------------------------------------------
362/* */
363void 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 /*}}}*/
6c139d6e
AL
373// Configuration::Set - Set a value /*{{{*/
374// ---------------------------------------------------------------------
375/* */
171c75f1 376void Configuration::Set(const char *Name,const string &Value)
6c139d6e
AL
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/* */
0df8c3dc 387void Configuration::Set(const char *Name,int const &Value)
6c139d6e
AL
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;
75ef8f14
MV
395}
396 /*}}}*/
397// Configuration::Clear - Clear an single value from a list /*{{{*/
398// ---------------------------------------------------------------------
399/* */
ce857f32 400void Configuration::Clear(string const &Name, int const &Value)
75ef8f14
MV
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/* */
ce857f32 410void Configuration::Clear(string const &Name, string const &Value)
75ef8f14
MV
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
d8a06f6e
MV
436}
437 /*}}}*/
438// Configuration::Clear - Clear everything /*{{{*/
439// ---------------------------------------------------------------------
440void Configuration::Clear()
441{
442 const Configuration::Item *Top = Tree(0);
443 while( Top != 0 )
444 {
445 Clear(Top->FullTag());
446 Top = Top->Next;
447 }
6c139d6e
AL
448}
449 /*}}}*/
b2e465d6
AL
450// Configuration::Clear - Clear an entire tree /*{{{*/
451// ---------------------------------------------------------------------
452/* */
ce857f32 453void Configuration::Clear(string const &Name)
b2e465d6
AL
454{
455 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 456 if (Top == 0)
b2e465d6 457 return;
75ef8f14 458
171c75f1 459 Top->Value.clear();
b2e465d6
AL
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 /*}}}*/
c3ded84c
DK
488void 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 /*}}}*/
08e8f724
AL
541// Configuration::Exists - Returns true if the Name exists /*{{{*/
542// ---------------------------------------------------------------------
543/* */
b2e465d6 544bool Configuration::Exists(const char *Name) const
08e8f724 545{
b2e465d6 546 const Item *Itm = Lookup(Name);
08e8f724
AL
547 if (Itm == 0)
548 return false;
549 return true;
550}
551 /*}}}*/
b2e465d6
AL
552// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
553// ---------------------------------------------------------------------
554/* qualified by /[fdbi] exists */
555bool Configuration::ExistsAny(const char *Name) const
556{
557 string key = Name;
558
8d998026 559 if (key.size() > 2 && key.end()[-2] == '/')
ff6bf1be 560 {
8d998026
AL
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 {
69a63027 569 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 570 }
ff6bf1be 571 }
b2e465d6
AL
572 return Exists(Name);
573}
574 /*}}}*/
93bf083d
AL
575// Configuration::Dump - Dump the config /*{{{*/
576// ---------------------------------------------------------------------
577/* Dump the entire configuration space */
ff2a211a 578void Configuration::Dump(ostream& str)
93bf083d 579{
2f416270
DK
580 Dump(str, NULL, "%f \"%v\";\n", true);
581}
582void 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
93bf083d 592 configuration tree */
2f416270
DK
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
93bf083d
AL
629 if (Top->Child != 0)
630 {
631 Top = Top->Child;
632 continue;
633 }
2f416270 634
93bf083d
AL
635 while (Top != 0 && Top->Next == 0)
636 Top = Top->Parent;
637 if (Top != 0)
638 Top = Top->Next;
2f416270
DK
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);
93bf083d
AL
654}
655 /*}}}*/
08e8f724 656
0a8a80e5
AL
657// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
658// ---------------------------------------------------------------------
b2e465d6
AL
659/* Stop sets an optional max recursion depth if this item is being viewed as
660 part of a sub tree. */
661string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 662{
b2e465d6 663 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 664 return Tag;
b2e465d6 665 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
666}
667 /*}}}*/
668
08e8f724
AL
669// ReadConfigFile - Read a configuration file /*{{{*/
670// ---------------------------------------------------------------------
671/* The configuration format is very much like the named.conf format
b2e465d6
AL
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
6df63aa6 676 tag/value. AsSectional enables Sectional parsing.*/
ce857f32
DK
677bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional,
678 unsigned const &Depth)
2b4cead3 679{
08e8f724 680 // Open the stream for reading
2b4cead3
DK
681 ifstream F(FName.c_str(),ios::in);
682 if (F.fail() == true)
b2e465d6 683 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
3f807f6c 684
08e8f724 685 string LineBuffer;
a4e87467
AL
686 string Stack[100];
687 unsigned int StackPos = 0;
2b4cead3 688
08e8f724
AL
689 // Parser state
690 string ParentTag;
2b4cead3 691
08e8f724
AL
692 int CurLine = 0;
693 bool InComment = false;
694 while (F.eof() == false)
695 {
3f807f6c
DB
696 // The raw input line.
697 std::string Input;
698 // The input line with comments stripped.
699 std::string Fragment;
8a27f5da
DB
700
701 // Grab the next line of F and place it in Input.
3f807f6c
DB
702 do
703 {
704 char *Buffer = new char[1024];
705
706 F.clear();
707 F.getline(Buffer,sizeof(Buffer) / 2);
708
709 Input += Buffer;
8aea8c3f 710 delete[] Buffer;
3f807f6c
DB
711 }
712 while (F.fail() && !F.eof());
713
8a27f5da
DB
714 // Expand tabs in the input line and remove leading and trailing
715 // whitespace.
3f807f6c 716 {
3f807f6c
DB
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 }
08e8f724 734 CurLine++;
5e14c452 735
8a27f5da
DB
736 // Now strip comments; if the whole line is contained in a
737 // comment, skip this line.
738
3f807f6c
DB
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();
08e8f724
AL
744
745 // Multi line comment
746 if (InComment == true)
747 {
3f807f6c
DB
748 for (std::string::const_iterator I = Start;
749 I != End; ++I)
08e8f724 750 {
3f807f6c 751 if (*I == '*' && I + 1 != End && I[1] == '/')
08e8f724 752 {
3f807f6c 753 Start = I + 2;
08e8f724
AL
754 InComment = false;
755 break;
756 }
757 }
758 if (InComment == true)
759 continue;
760 }
761
762 // Discard single line comments
bfd22fc0 763 bool InQuote = false;
3f807f6c
DB
764 for (std::string::const_iterator I = Start;
765 I != End; ++I)
08e8f724 766 {
bfd22fc0
AL
767 if (*I == '"')
768 InQuote = !InQuote;
769 if (InQuote == true)
770 continue;
c914647f
DK
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 {
3f807f6c 776 End = I;
08e8f724
AL
777 break;
778 }
779 }
7834cb57 780
3f807f6c
DB
781 // Look for multi line comments and build up the
782 // fragment.
783 Fragment.reserve(End - Start);
7834cb57 784 InQuote = false;
3f807f6c
DB
785 for (std::string::const_iterator I = Start;
786 I != End; ++I)
08e8f724 787 {
bfd22fc0
AL
788 if (*I == '"')
789 InQuote = !InQuote;
790 if (InQuote == true)
3f807f6c
DB
791 Fragment.push_back(*I);
792 else if (*I == '/' && I + 1 != End && I[1] == '*')
08e8f724
AL
793 {
794 InComment = true;
3f807f6c
DB
795 for (std::string::const_iterator J = I;
796 J != End; ++J)
08e8f724 797 {
3f807f6c 798 if (*J == '*' && J + 1 != End && J[1] == '/')
08e8f724 799 {
3f807f6c
DB
800 // Pretend we just finished walking over the
801 // comment, and don't add anything to the output
802 // fragment.
803 I = J + 1;
08e8f724
AL
804 InComment = false;
805 break;
806 }
807 }
808
809 if (InComment == true)
3f807f6c 810 break;
08e8f724 811 }
3f807f6c
DB
812 else
813 Fragment.push_back(*I);
08e8f724 814 }
3f807f6c
DB
815
816 // Skip blank lines.
817 if (Fragment.empty())
08e8f724
AL
818 continue;
819
8a27f5da 820 // The line has actual content; interpret what it means.
7834cb57 821 InQuote = false;
3f807f6c
DB
822 Start = Fragment.begin();
823 End = Fragment.end();
824 for (std::string::const_iterator I = Start;
825 I != End; ++I)
08e8f724 826 {
7834cb57
AL
827 if (*I == '"')
828 InQuote = !InQuote;
829
830 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 831 {
b2e465d6 832 // Put the last fragment into the buffer
a4dcbf1c
DB
833 std::string::const_iterator NonWhitespaceStart = Start;
834 std::string::const_iterator NonWhitespaceStop = I;
f7f0d6c7 835 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart)
3f807f6c 836 ;
f7f0d6c7 837 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop)
3f807f6c 838 ;
a4dcbf1c 839 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
08e8f724 840 LineBuffer += ' ';
a4dcbf1c 841 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
3f807f6c
DB
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)
08e8f724 846 char TermChar = *I;
3f807f6c 847 Start = I + 1;
08e8f724
AL
848
849 // Syntax Error
850 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 851 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 852
b2e465d6 853 // No string on this line
08e8f724 854 if (LineBuffer.empty() == true)
b2e465d6
AL
855 {
856 if (TermChar == '}')
857 {
858 if (StackPos == 0)
859 ParentTag = string();
860 else
861 ParentTag = Stack[--StackPos];
862 }
08e8f724 863 continue;
b2e465d6
AL
864 }
865
08e8f724 866 // Parse off the tag
7f25bdff
AL
867 string Tag;
868 const char *Pos = LineBuffer.c_str();
869 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 870 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
871
872 // Parse off the word
873 string Word;
bcae6dd4 874 bool NoWord = false;
b2e465d6
AL
875 if (ParseCWord(Pos,Word) == false &&
876 ParseQuoteWord(Pos,Word) == false)
877 {
878 if (TermChar != '{')
879 {
880 Word = Tag;
881 Tag = "";
bcae6dd4 882 }
4ae405e9
AL
883 else
884 NoWord = true;
b2e465d6
AL
885 }
886 if (strlen(Pos) != 0)
887 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
888
08e8f724
AL
889 // Go down a level
890 if (TermChar == '{')
891 {
26d5b68a 892 if (StackPos < sizeof(Stack)/sizeof(std::string))
a4e87467 893 Stack[StackPos++] = ParentTag;
b2e465d6
AL
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
08e8f724
AL
904 if (ParentTag.empty() == true)
905 ParentTag = Tag;
906 else
907 ParentTag += string("::") + Tag;
908 Tag = string();
909 }
bfd22fc0 910
08e8f724
AL
911 // Generate the item name
912 string Item;
913 if (ParentTag.empty() == true)
914 Item = Tag;
915 else
916 {
7f25bdff 917 if (TermChar != '{' || Tag.empty() == false)
08e8f724 918 Item = ParentTag + "::" + Tag;
7f25bdff
AL
919 else
920 Item = ParentTag;
08e8f724
AL
921 }
922
b2e465d6
AL
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 }
c3a3a1b1
DK
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);
b2e465d6
AL
951 else
952 {
953 // Set the item in the configuration class
bcae6dd4
AL
954 if (NoWord == false)
955 Conf.Set(Item,Word);
b2e465d6
AL
956 }
957
08e8f724 958 // Empty the buffer
171c75f1 959 LineBuffer.clear();
b2e465d6
AL
960
961 // Move up a tag, but only if there is no bit to parse
962 if (TermChar == '}')
963 {
964 if (StackPos == 0)
171c75f1 965 ParentTag.clear();
b2e465d6
AL
966 else
967 ParentTag = Stack[--StackPos];
968 }
969
08e8f724 970 }
08e8f724
AL
971 }
972
3f807f6c
DB
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 }
08e8f724 997 }
b2e465d6
AL
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/* */
a74cd17a 1007bool ReadConfigDir(Configuration &Conf,const string &Dir,
be4eec61 1008 bool const &AsSectional, unsigned const &Depth)
52643bec 1009{
e29a6bb1 1010 vector<string> const List = GetListOfFilesInDir(Dir, "conf", true, true);
b2e465d6
AL
1011
1012 // Read the files
f7f0d6c7 1013 for (vector<string>::const_iterator I = List.begin(); I != List.end(); ++I)
b2e465d6
AL
1014 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
1015 return false;
08e8f724
AL
1016 return true;
1017}
1018 /*}}}*/
1f2933a8
DK
1019// MatchAgainstConfig Constructor /*{{{*/
1020Configuration::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;
44edc41e
MV
1033 _error->Warning("Invalid regular expression '%s' in configuration "
1034 "option '%s' will be ignored.",
1035 s->c_str(), Config);
1036 continue;
1f2933a8 1037 }
b093a199 1038 }
69c2ecbd 1039 if (strings.empty() == true)
b093a199 1040 patterns.push_back(NULL);
1f2933a8
DK
1041}
1042 /*}}}*/
1043// MatchAgainstConfig Destructor /*{{{*/
1044Configuration::MatchAgainstConfig::~MatchAgainstConfig()
b093a199
DK
1045{
1046 clearPatterns();
1047}
1048void Configuration::MatchAgainstConfig::clearPatterns()
1f2933a8
DK
1049{
1050 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1051 p != patterns.end(); ++p)
1052 {
b093a199 1053 if (*p == NULL) continue;
1f2933a8
DK
1054 regfree(*p);
1055 delete *p;
1056 }
44edc41e 1057 patterns.clear();
1f2933a8
DK
1058}
1059 /*}}}*/
1060// MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1061bool Configuration::MatchAgainstConfig::Match(char const * str) const
1062{
1063 for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1064 p != patterns.end(); ++p)
b093a199 1065 if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0)
1f2933a8
DK
1066 return true;
1067
1068 return false;
1069}
1070 /*}}}*/