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/init.h>
35 Configuration
*_config
= new Configuration
;
37 // Configuration::Configuration - Constructor /*{{{*/
38 // ---------------------------------------------------------------------
40 Configuration::Configuration() : ToFree(true)
44 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
49 // Configuration::~Configuration - Destructor /*{{{*/
50 // ---------------------------------------------------------------------
52 Configuration::~Configuration()
66 while (Top
!= 0 && Top
->Next
== 0)
68 Item
*Parent
= Top
->Parent
;
74 Item
*Next
= Top
->Next
;
81 // Configuration::Lookup - Lookup a single item /*{{{*/
82 // ---------------------------------------------------------------------
83 /* This will lookup a single item by name below another item. It is a
84 helper function for the main lookup function */
85 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
86 unsigned long const &Len
,bool const &Create
)
89 Item
*I
= Head
->Child
;
90 Item
**Last
= &Head
->Child
;
92 // Empty strings match nothing. They are used for lists.
95 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
96 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
100 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
108 I
->Tag
.assign(S
,Len
);
115 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
116 // ---------------------------------------------------------------------
117 /* This performs a fully scoped lookup of a given name, possibly creating
119 Configuration::Item
*Configuration::Lookup(const char *Name
,bool const &Create
)
124 const char *Start
= Name
;
125 const char *End
= Start
+ strlen(Name
);
126 const char *TagEnd
= Name
;
128 for (; End
- TagEnd
>= 2; TagEnd
++)
130 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
132 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
135 TagEnd
= Start
= TagEnd
+ 2;
139 // This must be a trailing ::, we create unique items in a list
140 if (End
- Start
== 0)
146 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
150 // Configuration::Find - Find a value /*{{{*/
151 // ---------------------------------------------------------------------
153 string
Configuration::Find(const char *Name
,const char *Default
) const
155 const Item
*Itm
= Lookup(Name
);
156 if (Itm
== 0 || Itm
->Value
.empty() == true)
167 // Configuration::FindFile - Find a Filename /*{{{*/
168 // ---------------------------------------------------------------------
169 /* Directories are stored as the base dir in the Parent node and the
170 sub directory in sub nodes with the final node being the end filename
172 string
Configuration::FindFile(const char *Name
,const char *Default
) const
174 const Item
*RootItem
= Lookup("RootDir");
175 std::string result
= (RootItem
== 0) ? "" : RootItem
->Value
;
176 if(result
.empty() == false && result
[result
.size() - 1] != '/')
177 result
.push_back('/');
179 const Item
*Itm
= Lookup(Name
);
180 if (Itm
== 0 || Itm
->Value
.empty() == true)
183 result
.append(Default
);
187 string val
= Itm
->Value
;
188 while (Itm
->Parent
!= 0)
190 if (Itm
->Parent
->Value
.empty() == true)
197 if (val
.length() >= 1 && val
[0] == '/')
199 if (val
.compare(0, 9, "/dev/null") == 0)
205 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
209 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
212 if (Itm
->Parent
->Value
.end()[-1] != '/')
215 val
.insert(0, Itm
->Parent
->Value
);
221 // do some normalisation by removing // and /./ from the path
222 size_t found
= string::npos
;
223 while ((found
= result
.find("/./")) != string::npos
)
224 result
.replace(found
, 3, "/");
225 while ((found
= result
.find("//")) != string::npos
)
226 result
.replace(found
, 2, "/");
231 // Configuration::FindDir - Find a directory name /*{{{*/
232 // ---------------------------------------------------------------------
233 /* This is like findfile execept the result is terminated in a / */
234 string
Configuration::FindDir(const char *Name
,const char *Default
) const
236 string Res
= FindFile(Name
,Default
);
237 if (Res
.end()[-1] != '/')
239 size_t const found
= Res
.rfind("/dev/null");
240 if (found
!= string::npos
&& found
== Res
.size() - 9)
241 return Res
; // /dev/null returning
247 // Configuration::FindVector - Find a vector of values /*{{{*/
248 // ---------------------------------------------------------------------
249 /* Returns a vector of config values under the given item */
250 #if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13)
251 vector
<string
> Configuration::FindVector(const char *Name
) const { return FindVector(Name
, ""); }
253 vector
<string
> Configuration::FindVector(const char *Name
, std::string
const &Default
) const
256 const Item
*Top
= Lookup(Name
);
258 return VectorizeString(Default
, ',');
260 if (Top
->Value
.empty() == false)
261 return VectorizeString(Top
->Value
, ',');
263 Item
*I
= Top
->Child
;
266 Vec
.push_back(I
->Value
);
269 if (Vec
.empty() == true)
270 return VectorizeString(Default
, ',');
275 // Configuration::FindI - Find an integer value /*{{{*/
276 // ---------------------------------------------------------------------
278 int Configuration::FindI(const char *Name
,int const &Default
) const
280 const Item
*Itm
= Lookup(Name
);
281 if (Itm
== 0 || Itm
->Value
.empty() == true)
285 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
286 if (End
== Itm
->Value
.c_str())
292 // Configuration::FindB - Find a boolean type /*{{{*/
293 // ---------------------------------------------------------------------
295 bool Configuration::FindB(const char *Name
,bool const &Default
) const
297 const Item
*Itm
= Lookup(Name
);
298 if (Itm
== 0 || Itm
->Value
.empty() == true)
301 return StringToBool(Itm
->Value
,Default
);
304 // Configuration::FindAny - Find an arbitrary type /*{{{*/
305 // ---------------------------------------------------------------------
306 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
307 string
Configuration::FindAny(const char *Name
,const char *Default
) const
312 if (key
.size() > 2 && key
.end()[-2] == '/')
314 type
= key
.end()[-1];
315 key
.resize(key
.size() - 2);
322 return FindFile(key
.c_str(), Default
);
326 return FindDir(key
.c_str(), Default
);
330 return FindB(key
, Default
) ? "true" : "false";
336 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
342 return Find(Name
, Default
);
345 // Configuration::CndSet - Conditinal Set a value /*{{{*/
346 // ---------------------------------------------------------------------
347 /* This will not overwrite */
348 void Configuration::CndSet(const char *Name
,const string
&Value
)
350 Item
*Itm
= Lookup(Name
,true);
353 if (Itm
->Value
.empty() == true)
357 // Configuration::Set - Set an integer value /*{{{*/
358 // ---------------------------------------------------------------------
360 void Configuration::CndSet(const char *Name
,int const Value
)
362 Item
*Itm
= Lookup(Name
,true);
363 if (Itm
== 0 || Itm
->Value
.empty() == false)
366 snprintf(S
,sizeof(S
),"%i",Value
);
370 // Configuration::Set - Set a value /*{{{*/
371 // ---------------------------------------------------------------------
373 void Configuration::Set(const char *Name
,const string
&Value
)
375 Item
*Itm
= Lookup(Name
,true);
381 // Configuration::Set - Set an integer value /*{{{*/
382 // ---------------------------------------------------------------------
384 void Configuration::Set(const char *Name
,int const &Value
)
386 Item
*Itm
= Lookup(Name
,true);
390 snprintf(S
,sizeof(S
),"%i",Value
);
394 // Configuration::Clear - Clear an single value from a list /*{{{*/
395 // ---------------------------------------------------------------------
397 void Configuration::Clear(string
const &Name
, int const &Value
)
400 snprintf(S
,sizeof(S
),"%i",Value
);
404 // Configuration::Clear - Clear an single value from a list /*{{{*/
405 // ---------------------------------------------------------------------
407 void Configuration::Clear(string
const &Name
, string
const &Value
)
409 Item
*Top
= Lookup(Name
.c_str(),false);
410 if (Top
== 0 || Top
->Child
== 0)
413 Item
*Tmp
, *Prev
, *I
;
414 Prev
= I
= Top
->Child
;
418 if(I
->Value
== Value
)
421 // was first element, point parent to new first element
422 if(Top
->Child
== Tmp
)
423 Top
->Child
= I
->Next
;
435 // Configuration::Clear - Clear everything /*{{{*/
436 // ---------------------------------------------------------------------
437 void Configuration::Clear()
439 const Configuration::Item
*Top
= Tree(0);
442 Clear(Top
->FullTag());
447 // Configuration::Clear - Clear an entire tree /*{{{*/
448 // ---------------------------------------------------------------------
450 void Configuration::Clear(string
const &Name
)
452 Item
*Top
= Lookup(Name
.c_str(),false);
468 while (Top
!= 0 && Top
->Next
== 0)
485 // Configuration::Exists - Returns true if the Name exists /*{{{*/
486 // ---------------------------------------------------------------------
488 bool Configuration::Exists(const char *Name
) const
490 const Item
*Itm
= Lookup(Name
);
496 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
497 // ---------------------------------------------------------------------
498 /* qualified by /[fdbi] exists */
499 bool Configuration::ExistsAny(const char *Name
) const
503 if (key
.size() > 2 && key
.end()[-2] == '/')
505 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
507 key
.resize(key
.size() - 2);
508 if (Exists(key
.c_str()))
513 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
519 // Configuration::Dump - Dump the config /*{{{*/
520 // ---------------------------------------------------------------------
521 /* Dump the entire configuration space */
522 void Configuration::Dump(ostream
& str
)
524 Dump(str
, NULL
, "%f \"%v\";\n", true);
526 void Configuration::Dump(ostream
& str
, char const * const root
,
527 char const * const formatstr
, bool const emptyValue
)
529 const Configuration::Item
* Top
= Tree(root
);
532 const Configuration::Item
* const Root
= (root
== NULL
) ? NULL
: Top
;
533 std::vector
<std::string
> const format
= VectorizeString(formatstr
, '%');
535 /* Write out all of the configuration directives by walking the
536 configuration tree */
538 if (emptyValue
== true || Top
->Value
.empty() == emptyValue
)
540 std::vector
<std::string
>::const_iterator f
= format
.begin();
542 for (++f
; f
!= format
.end(); ++f
)
544 if (f
->empty() == true)
550 char const type
= (*f
)[0];
552 str
<< Top
->FullTag();
553 else if (type
== 't')
555 else if (type
== 'v')
557 else if (type
== 'F')
558 str
<< QuoteString(Top
->FullTag(), "=\"\n");
559 else if (type
== 'T')
560 str
<< QuoteString(Top
->Tag
, "=\"\n");
561 else if (type
== 'V')
562 str
<< QuoteString(Top
->Value
, "=\"\n");
563 else if (type
== 'n')
565 else if (type
== 'N')
569 str
<< f
->c_str() + 1;
579 while (Top
!= 0 && Top
->Next
== 0)
586 const Configuration::Item
* I
= Top
;
601 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
602 // ---------------------------------------------------------------------
603 /* Stop sets an optional max recursion depth if this item is being viewed as
604 part of a sub tree. */
605 string
Configuration::Item::FullTag(const Item
*Stop
) const
607 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
609 return Parent
->FullTag(Stop
) + "::" + Tag
;
613 // ReadConfigFile - Read a configuration file /*{{{*/
614 // ---------------------------------------------------------------------
615 /* The configuration format is very much like the named.conf format
616 used in bind8, in fact this routine can parse most named.conf files.
617 Sectional config files are like bind's named.conf where there are
618 sections like 'zone "foo.org" { .. };' This causes each section to be
619 added in with a tag like "zone::foo.org" instead of being split
620 tag/value. AsSectional enables Sectional parsing.*/
621 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
622 unsigned const &Depth
)
624 // Open the stream for reading
625 ifstream
F(FName
.c_str(),ios::in
);
627 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
631 unsigned int StackPos
= 0;
637 bool InComment
= false;
638 while (F
.eof() == false)
640 // The raw input line.
642 // The input line with comments stripped.
643 std::string Fragment
;
645 // Grab the next line of F and place it in Input.
648 char *Buffer
= new char[1024];
651 F
.getline(Buffer
,sizeof(Buffer
) / 2);
656 while (F
.fail() && !F
.eof());
658 // Expand tabs in the input line and remove leading and trailing
661 const int BufferSize
= Input
.size() * 8 + 1;
662 char *Buffer
= new char[BufferSize
];
665 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
667 _strtabexpand(Buffer
, BufferSize
);
680 // Now strip comments; if the whole line is contained in a
681 // comment, skip this line.
683 // The first meaningful character in the current fragment; will
684 // be adjusted below as we remove bytes from the front.
685 std::string::const_iterator Start
= Input
.begin();
686 // The last meaningful character in the current fragment.
687 std::string::const_iterator End
= Input
.end();
689 // Multi line comment
690 if (InComment
== true)
692 for (std::string::const_iterator I
= Start
;
695 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
702 if (InComment
== true)
706 // Discard single line comments
707 bool InQuote
= false;
708 for (std::string::const_iterator I
= Start
;
716 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
717 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
718 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
725 // Look for multi line comments and build up the
727 Fragment
.reserve(End
- Start
);
729 for (std::string::const_iterator I
= Start
;
735 Fragment
.push_back(*I
);
736 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
739 for (std::string::const_iterator J
= I
;
742 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
744 // Pretend we just finished walking over the
745 // comment, and don't add anything to the output
753 if (InComment
== true)
757 Fragment
.push_back(*I
);
761 if (Fragment
.empty())
764 // The line has actual content; interpret what it means.
766 Start
= Fragment
.begin();
767 End
= Fragment
.end();
768 for (std::string::const_iterator I
= Start
;
774 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
776 // Put the last fragment into the buffer
777 std::string::const_iterator NonWhitespaceStart
= Start
;
778 std::string::const_iterator NonWhitespaceStop
= I
;
779 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
781 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
783 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
785 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
787 // Drop this from the input string, saving the character
788 // that terminated the construct we just closed. (i.e., a
789 // brace or a semicolon)
794 if (TermChar
== '{' && LineBuffer
.empty() == true)
795 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
797 // No string on this line
798 if (LineBuffer
.empty() == true)
803 ParentTag
= string();
805 ParentTag
= Stack
[--StackPos
];
812 const char *Pos
= LineBuffer
.c_str();
813 if (ParseQuoteWord(Pos
,Tag
) == false)
814 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
816 // Parse off the word
819 if (ParseCWord(Pos
,Word
) == false &&
820 ParseQuoteWord(Pos
,Word
) == false)
830 if (strlen(Pos
) != 0)
831 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
836 if (StackPos
< sizeof(Stack
)/sizeof(std::string
))
837 Stack
[StackPos
++] = ParentTag
;
839 /* Make sectional tags incorperate the section into the
841 if (AsSectional
== true && Word
.empty() == false)
848 if (ParentTag
.empty() == true)
851 ParentTag
+= string("::") + Tag
;
855 // Generate the item name
857 if (ParentTag
.empty() == true)
861 if (TermChar
!= '{' || Tag
.empty() == false)
862 Item
= ParentTag
+ "::" + Tag
;
868 if (Tag
.length() >= 1 && Tag
[0] == '#')
870 if (ParentTag
.empty() == false)
871 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
872 Tag
.erase(Tag
.begin());
875 else if (Tag
== "include")
878 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
879 if (Word
.length() > 2 && Word
.end()[-1] == '/')
881 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
882 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
886 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
887 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
891 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
893 else if (Tag
.empty() == true && NoWord
== false && Word
== "#clear")
894 return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
);
897 // Set the item in the configuration class
905 // Move up a tag, but only if there is no bit to parse
911 ParentTag
= Stack
[--StackPos
];
917 // Store the remaining text, if any, in the current line buffer.
919 // NB: could change this to use string-based operations; I'm
920 // using strstrip now to ensure backwards compatibility.
921 // -- dburrows 2008-04-01
923 char *Buffer
= new char[End
- Start
+ 1];
926 std::copy(Start
, End
, Buffer
);
927 Buffer
[End
- Start
] = '\0';
929 const char *Stripd
= _strstrip(Buffer
);
930 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
932 LineBuffer
+= Stripd
;
943 if (LineBuffer
.empty() == false)
944 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
948 // ReadConfigDir - Read a directory of config files /*{{{*/
949 // ---------------------------------------------------------------------
951 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
952 bool const &AsSectional
, unsigned const &Depth
)
954 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
957 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
958 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
963 // MatchAgainstConfig Constructor /*{{{*/
964 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
966 std::vector
<std::string
> const strings
= _config
->FindVector(Config
);
967 for (std::vector
<std::string
>::const_iterator s
= strings
.begin();
968 s
!= strings
.end(); ++s
)
970 regex_t
*p
= new regex_t
;
971 if (regcomp(p
, s
->c_str(), REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) == 0)
972 patterns
.push_back(p
);
977 _error
->Warning("Invalid regular expression '%s' in configuration "
978 "option '%s' will be ignored.",
983 if (strings
.empty() == true)
984 patterns
.push_back(NULL
);
987 // MatchAgainstConfig Destructor /*{{{*/
988 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
992 void Configuration::MatchAgainstConfig::clearPatterns()
994 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
995 p
!= patterns
.end(); ++p
)
997 if (*p
== NULL
) continue;
1004 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1005 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
1007 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1008 p
!= patterns
.end(); ++p
)
1009 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)