1 // -*- mode: cpp; mode: fold -*-
3 // $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz Exp $
4 /* ######################################################################
8 This class provides a configuration file and command line parser
9 for a tree-oriented configuration environment. All runtime configuration
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>.
15 ##################################################################### */
17 // Include files /*{{{*/
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>
42 Configuration
*_config
= new Configuration
;
44 // Configuration::Configuration - Constructor /*{{{*/
45 // ---------------------------------------------------------------------
47 Configuration::Configuration() : ToFree(true)
51 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
55 // Configuration::~Configuration - Destructor /*{{{*/
56 // ---------------------------------------------------------------------
58 Configuration::~Configuration()
72 while (Top
!= 0 && Top
->Next
== 0)
74 Item
*Parent
= Top
->Parent
;
80 Item
*Next
= Top
->Next
;
87 // Configuration::Lookup - Lookup a single item /*{{{*/
88 // ---------------------------------------------------------------------
89 /* This will lookup a single item by name below another item. It is a
90 helper function for the main lookup function */
91 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
92 unsigned long const &Len
,bool const &Create
)
95 Item
*I
= Head
->Child
;
96 Item
**Last
= &Head
->Child
;
98 // Empty strings match nothing. They are used for lists.
101 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
102 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
106 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
114 I
->Tag
.assign(S
,Len
);
121 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
122 // ---------------------------------------------------------------------
123 /* This performs a fully scoped lookup of a given name, possibly creating
125 Configuration::Item
*Configuration::Lookup(const char *Name
,bool const &Create
)
130 const char *Start
= Name
;
131 const char *End
= Start
+ strlen(Name
);
132 const char *TagEnd
= Name
;
134 for (; End
- TagEnd
>= 2; TagEnd
++)
136 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
138 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
141 TagEnd
= Start
= TagEnd
+ 2;
145 // This must be a trailing ::, we create unique items in a list
146 if (End
- Start
== 0)
152 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
156 // Configuration::Find - Find a value /*{{{*/
157 // ---------------------------------------------------------------------
159 string
Configuration::Find(const char *Name
,const char *Default
) const
161 const Item
*Itm
= Lookup(Name
);
162 if (Itm
== 0 || Itm
->Value
.empty() == true)
173 // Configuration::FindFile - Find a Filename /*{{{*/
174 // ---------------------------------------------------------------------
175 /* Directories are stored as the base dir in the Parent node and the
176 sub directory in sub nodes with the final node being the end filename
178 string
Configuration::FindFile(const char *Name
,const char *Default
) const
180 const Item
*RootItem
= Lookup("RootDir");
181 std::string result
= (RootItem
== 0) ? "" : RootItem
->Value
;
182 if(result
.empty() == false && result
[result
.size() - 1] != '/')
183 result
.push_back('/');
185 const Item
*Itm
= Lookup(Name
);
186 if (Itm
== 0 || Itm
->Value
.empty() == true)
189 result
.append(Default
);
193 string val
= Itm
->Value
;
194 while (Itm
->Parent
!= 0)
196 if (Itm
->Parent
->Value
.empty() == true)
203 if (val
.length() >= 1 && val
[0] == '/')
205 if (val
.compare(0, 9, "/dev/null") == 0)
211 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
215 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
218 if (Itm
->Parent
->Value
.end()[-1] != '/')
221 val
.insert(0, Itm
->Parent
->Value
);
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, "/");
237 // Configuration::FindDir - Find a directory name /*{{{*/
238 // ---------------------------------------------------------------------
239 /* This is like findfile execept the result is terminated in a / */
240 string
Configuration::FindDir(const char *Name
,const char *Default
) const
242 string Res
= FindFile(Name
,Default
);
243 if (Res
.end()[-1] != '/')
245 size_t const found
= Res
.rfind("/dev/null");
246 if (found
!= string::npos
&& found
== Res
.size() - 9)
247 return Res
; // /dev/null returning
253 // Configuration::FindVector - Find a vector of values /*{{{*/
254 // ---------------------------------------------------------------------
255 /* Returns a vector of config values under the given item */
256 vector
<string
> Configuration::FindVector(const char *Name
, std::string
const &Default
, bool const Keys
) const
259 const Item
*Top
= Lookup(Name
);
261 return VectorizeString(Default
, ',');
263 if (Top
->Value
.empty() == false)
264 return VectorizeString(Top
->Value
, ',');
266 Item
*I
= Top
->Child
;
269 Vec
.push_back(Keys
? I
->Tag
: I
->Value
);
272 if (Vec
.empty() == true)
273 return VectorizeString(Default
, ',');
278 // Configuration::FindI - Find an integer value /*{{{*/
279 // ---------------------------------------------------------------------
281 int Configuration::FindI(const char *Name
,int const &Default
) const
283 const Item
*Itm
= Lookup(Name
);
284 if (Itm
== 0 || Itm
->Value
.empty() == true)
288 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
289 if (End
== Itm
->Value
.c_str())
295 // Configuration::FindB - Find a boolean type /*{{{*/
296 // ---------------------------------------------------------------------
298 bool Configuration::FindB(const char *Name
,bool const &Default
) const
300 const Item
*Itm
= Lookup(Name
);
301 if (Itm
== 0 || Itm
->Value
.empty() == true)
304 return StringToBool(Itm
->Value
,Default
);
307 // Configuration::FindAny - Find an arbitrary type /*{{{*/
308 // ---------------------------------------------------------------------
309 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
310 string
Configuration::FindAny(const char *Name
,const char *Default
) const
315 if (key
.size() > 2 && key
.end()[-2] == '/')
317 type
= key
.end()[-1];
318 key
.resize(key
.size() - 2);
325 return FindFile(key
.c_str(), Default
);
329 return FindDir(key
.c_str(), Default
);
333 return FindB(key
, Default
) ? "true" : "false";
339 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
345 return Find(Name
, Default
);
348 // Configuration::CndSet - Conditinal Set a value /*{{{*/
349 // ---------------------------------------------------------------------
350 /* This will not overwrite */
351 void Configuration::CndSet(const char *Name
,const string
&Value
)
353 Item
*Itm
= Lookup(Name
,true);
356 if (Itm
->Value
.empty() == true)
360 // Configuration::Set - Set an integer value /*{{{*/
361 // ---------------------------------------------------------------------
363 void Configuration::CndSet(const char *Name
,int const Value
)
365 Item
*Itm
= Lookup(Name
,true);
366 if (Itm
== 0 || Itm
->Value
.empty() == false)
369 snprintf(S
,sizeof(S
),"%i",Value
);
373 // Configuration::Set - Set a value /*{{{*/
374 // ---------------------------------------------------------------------
376 void Configuration::Set(const char *Name
,const string
&Value
)
378 Item
*Itm
= Lookup(Name
,true);
384 // Configuration::Set - Set an integer value /*{{{*/
385 // ---------------------------------------------------------------------
387 void Configuration::Set(const char *Name
,int const &Value
)
389 Item
*Itm
= Lookup(Name
,true);
393 snprintf(S
,sizeof(S
),"%i",Value
);
397 // Configuration::Clear - Clear an single value from a list /*{{{*/
398 // ---------------------------------------------------------------------
400 void Configuration::Clear(string
const &Name
, int const &Value
)
403 snprintf(S
,sizeof(S
),"%i",Value
);
407 // Configuration::Clear - Clear an single value from a list /*{{{*/
408 // ---------------------------------------------------------------------
410 void Configuration::Clear(string
const &Name
, string
const &Value
)
412 Item
*Top
= Lookup(Name
.c_str(),false);
413 if (Top
== 0 || Top
->Child
== 0)
416 Item
*Tmp
, *Prev
, *I
;
417 Prev
= I
= Top
->Child
;
421 if(I
->Value
== Value
)
424 // was first element, point parent to new first element
425 if(Top
->Child
== Tmp
)
426 Top
->Child
= I
->Next
;
438 // Configuration::Clear - Clear everything /*{{{*/
439 // ---------------------------------------------------------------------
440 void Configuration::Clear()
442 const Configuration::Item
*Top
= Tree(0);
445 Clear(Top
->FullTag());
450 // Configuration::Clear - Clear an entire tree /*{{{*/
451 // ---------------------------------------------------------------------
453 void Configuration::Clear(string
const &Name
)
455 Item
*Top
= Lookup(Name
.c_str(),false);
471 while (Top
!= 0 && Top
->Next
== 0)
488 void Configuration::MoveSubTree(char const * const OldRootName
, char const * const NewRootName
)/*{{{*/
490 // prevent NewRoot being a subtree of OldRoot
491 if (OldRootName
== nullptr)
493 if (NewRootName
!= nullptr)
495 if (strcmp(OldRootName
, NewRootName
) == 0)
497 std::string
const oldroot
= std::string(OldRootName
) + "::";
498 if (strcasestr(NewRootName
, oldroot
.c_str()) != NULL
)
503 Item
const * const OldRoot
= Top
= Lookup(OldRootName
, false);
507 if (NewRootName
!= nullptr)
508 NewRoot
.append(NewRootName
).append("::");
511 Item
* const Stop
= Top
;
522 while (Top
!= 0 && Top
->Next
== 0)
524 Set(NewRoot
+ Top
->FullTag(OldRoot
), Top
->Value
);
525 Item
const * const Tmp
= Top
;
533 Set(NewRoot
+ Top
->FullTag(OldRoot
), Top
->Value
);
534 Item
const * const Tmp
= Top
;
541 // Configuration::Exists - Returns true if the Name exists /*{{{*/
542 // ---------------------------------------------------------------------
544 bool Configuration::Exists(const char *Name
) const
546 const Item
*Itm
= Lookup(Name
);
552 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
553 // ---------------------------------------------------------------------
554 /* qualified by /[fdbi] exists */
555 bool Configuration::ExistsAny(const char *Name
) const
559 if (key
.size() > 2 && key
.end()[-2] == '/')
561 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
563 key
.resize(key
.size() - 2);
564 if (Exists(key
.c_str()))
569 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
575 // Configuration::Dump - Dump the config /*{{{*/
576 // ---------------------------------------------------------------------
577 /* Dump the entire configuration space */
578 void Configuration::Dump(ostream
& str
)
580 Dump(str
, NULL
, "%f \"%v\";\n", true);
582 void Configuration::Dump(ostream
& str
, char const * const root
,
583 char const * const formatstr
, bool const emptyValue
)
585 const Configuration::Item
* Top
= Tree(root
);
588 const Configuration::Item
* const Root
= (root
== NULL
) ? NULL
: Top
;
589 std::vector
<std::string
> const format
= VectorizeString(formatstr
, '%');
591 /* Write out all of the configuration directives by walking the
592 configuration tree */
594 if (emptyValue
== true || Top
->Value
.empty() == emptyValue
)
596 std::vector
<std::string
>::const_iterator f
= format
.begin();
598 for (++f
; f
!= format
.end(); ++f
)
600 if (f
->empty() == true)
606 char const type
= (*f
)[0];
608 str
<< Top
->FullTag();
609 else if (type
== 't')
611 else if (type
== 'v')
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')
621 else if (type
== 'N')
625 str
<< f
->c_str() + 1;
635 while (Top
!= 0 && Top
->Next
== 0)
642 const Configuration::Item
* I
= Top
;
657 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
658 // ---------------------------------------------------------------------
659 /* Stop sets an optional max recursion depth if this item is being viewed as
660 part of a sub tree. */
661 string
Configuration::Item::FullTag(const Item
*Stop
) const
663 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
665 return Parent
->FullTag(Stop
) + "::" + Tag
;
669 // ReadConfigFile - Read a configuration file /*{{{*/
670 // ---------------------------------------------------------------------
671 /* The configuration format is very much like the named.conf format
672 used in bind8, in fact this routine can parse most named.conf files.
673 Sectional config files are like bind's named.conf where there are
674 sections like 'zone "foo.org" { .. };' This causes each section to be
675 added in with a tag like "zone::foo.org" instead of being split
676 tag/value. AsSectional enables Sectional parsing.*/
677 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
678 unsigned const &Depth
)
680 // Open the stream for reading
681 ifstream
F(FName
.c_str(),ios::in
);
682 if (F
.fail() == true)
683 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
687 unsigned int StackPos
= 0;
693 bool InComment
= false;
694 while (F
.eof() == false)
696 // The raw input line.
698 // The input line with comments stripped.
699 std::string Fragment
;
701 // Grab the next line of F and place it in Input.
704 char *Buffer
= new char[1024];
707 F
.getline(Buffer
,sizeof(Buffer
) / 2);
712 while (F
.fail() && !F
.eof());
714 // Expand tabs in the input line and remove leading and trailing
717 const int BufferSize
= Input
.size() * 8 + 1;
718 char *Buffer
= new char[BufferSize
];
721 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
723 _strtabexpand(Buffer
, BufferSize
);
736 // Now strip comments; if the whole line is contained in a
737 // comment, skip this line.
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();
745 // Multi line comment
746 if (InComment
== true)
748 for (std::string::const_iterator I
= Start
;
751 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
758 if (InComment
== true)
762 // Discard single line comments
763 bool InQuote
= false;
764 for (std::string::const_iterator I
= Start
;
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))
781 // Look for multi line comments and build up the
783 Fragment
.reserve(End
- Start
);
785 for (std::string::const_iterator I
= Start
;
791 Fragment
.push_back(*I
);
792 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
795 for (std::string::const_iterator J
= I
;
798 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
800 // Pretend we just finished walking over the
801 // comment, and don't add anything to the output
809 if (InComment
== true)
813 Fragment
.push_back(*I
);
817 if (Fragment
.empty())
820 // The line has actual content; interpret what it means.
822 Start
= Fragment
.begin();
823 End
= Fragment
.end();
824 for (std::string::const_iterator I
= Start
;
830 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
832 // Put the last fragment into the buffer
833 std::string::const_iterator NonWhitespaceStart
= Start
;
834 std::string::const_iterator NonWhitespaceStop
= I
;
835 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
837 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
839 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
841 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
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)
850 if (TermChar
== '{' && LineBuffer
.empty() == true)
851 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
853 // No string on this line
854 if (LineBuffer
.empty() == true)
859 ParentTag
= string();
861 ParentTag
= Stack
[--StackPos
];
868 const char *Pos
= LineBuffer
.c_str();
869 if (ParseQuoteWord(Pos
,Tag
) == false)
870 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
872 // Parse off the word
875 if (ParseCWord(Pos
,Word
) == false &&
876 ParseQuoteWord(Pos
,Word
) == false)
886 if (strlen(Pos
) != 0)
887 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
892 if (StackPos
< sizeof(Stack
)/sizeof(std::string
))
893 Stack
[StackPos
++] = ParentTag
;
895 /* Make sectional tags incorperate the section into the
897 if (AsSectional
== true && Word
.empty() == false)
904 if (ParentTag
.empty() == true)
907 ParentTag
+= string("::") + Tag
;
911 // Generate the item name
913 if (ParentTag
.empty() == true)
917 if (TermChar
!= '{' || Tag
.empty() == false)
918 Item
= ParentTag
+ "::" + Tag
;
924 if (Tag
.length() >= 1 && Tag
[0] == '#')
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());
931 else if (Tag
== "include")
934 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
935 if (Word
.length() > 2 && Word
.end()[-1] == '/')
937 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
938 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
942 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
943 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
947 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
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
);
953 // Set the item in the configuration class
961 // Move up a tag, but only if there is no bit to parse
967 ParentTag
= Stack
[--StackPos
];
973 // Store the remaining text, if any, in the current line buffer.
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
979 char *Buffer
= new char[End
- Start
+ 1];
982 std::copy(Start
, End
, Buffer
);
983 Buffer
[End
- Start
] = '\0';
985 const char *Stripd
= _strstrip(Buffer
);
986 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
988 LineBuffer
+= Stripd
;
999 if (LineBuffer
.empty() == false)
1000 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
1004 // ReadConfigDir - Read a directory of config files /*{{{*/
1005 // ---------------------------------------------------------------------
1007 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
1008 bool const &AsSectional
, unsigned const &Depth
)
1010 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
1013 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
1014 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
1019 // MatchAgainstConfig Constructor /*{{{*/
1020 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
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
)
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
);
1033 _error
->Warning("Invalid regular expression '%s' in configuration "
1034 "option '%s' will be ignored.",
1035 s
->c_str(), Config
);
1039 if (strings
.empty() == true)
1040 patterns
.push_back(NULL
);
1043 // MatchAgainstConfig Destructor /*{{{*/
1044 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
1048 void Configuration::MatchAgainstConfig::clearPatterns()
1050 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1051 p
!= patterns
.end(); ++p
)
1053 if (*p
== NULL
) continue;
1060 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1061 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
1063 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1064 p
!= patterns
.end(); ++p
)
1065 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)