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