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>
44 Configuration
*_config
= new Configuration
;
46 // Configuration::Configuration - Constructor /*{{{*/
47 // ---------------------------------------------------------------------
49 Configuration::Configuration() : ToFree(true)
53 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
57 // Configuration::~Configuration - Destructor /*{{{*/
58 // ---------------------------------------------------------------------
60 Configuration::~Configuration()
74 while (Top
!= 0 && Top
->Next
== 0)
76 Item
*Parent
= Top
->Parent
;
82 Item
*Next
= Top
->Next
;
89 // Configuration::Lookup - Lookup a single item /*{{{*/
90 // ---------------------------------------------------------------------
91 /* This will lookup a single item by name below another item. It is a
92 helper function for the main lookup function */
93 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
94 unsigned long const &Len
,bool const &Create
)
97 Item
*I
= Head
->Child
;
98 Item
**Last
= &Head
->Child
;
100 // Empty strings match nothing. They are used for lists.
103 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
104 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
108 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
116 I
->Tag
.assign(S
,Len
);
123 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
124 // ---------------------------------------------------------------------
125 /* This performs a fully scoped lookup of a given name, possibly creating
127 Configuration::Item
*Configuration::Lookup(const char *Name
,bool const &Create
)
132 const char *Start
= Name
;
133 const char *End
= Start
+ strlen(Name
);
134 const char *TagEnd
= Name
;
136 for (; End
- TagEnd
>= 2; TagEnd
++)
138 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
140 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
143 TagEnd
= Start
= TagEnd
+ 2;
147 // This must be a trailing ::, we create unique items in a list
148 if (End
- Start
== 0)
154 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
158 // Configuration::Find - Find a value /*{{{*/
159 // ---------------------------------------------------------------------
161 string
Configuration::Find(const char *Name
,const char *Default
) const
163 const Item
*Itm
= Lookup(Name
);
164 if (Itm
== 0 || Itm
->Value
.empty() == true)
175 // Configuration::FindFile - Find a Filename /*{{{*/
176 // ---------------------------------------------------------------------
177 /* Directories are stored as the base dir in the Parent node and the
178 sub directory in sub nodes with the final node being the end filename
180 string
Configuration::FindFile(const char *Name
,const char *Default
) const
182 const Item
*RootItem
= Lookup("RootDir");
183 std::string result
= (RootItem
== 0) ? "" : RootItem
->Value
;
184 if(result
.empty() == false && result
[result
.size() - 1] != '/')
185 result
.push_back('/');
187 const Item
*Itm
= Lookup(Name
);
188 if (Itm
== 0 || Itm
->Value
.empty() == true)
191 result
.append(Default
);
195 string val
= Itm
->Value
;
196 while (Itm
->Parent
!= 0)
198 if (Itm
->Parent
->Value
.empty() == true)
205 if (val
.length() >= 1 && val
[0] == '/')
207 if (val
.compare(0, 9, "/dev/null") == 0)
213 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
217 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
220 if (Itm
->Parent
->Value
.end()[-1] != '/')
223 val
.insert(0, Itm
->Parent
->Value
);
228 return flNormalize(result
);
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 vector
<string
> Configuration::FindVector(const char *Name
, std::string
const &Default
, bool const Keys
) const
253 const Item
*Top
= Lookup(Name
);
255 return VectorizeString(Default
, ',');
257 if (Top
->Value
.empty() == false)
258 return VectorizeString(Top
->Value
, ',');
260 Item
*I
= Top
->Child
;
263 Vec
.push_back(Keys
? I
->Tag
: I
->Value
);
266 if (Vec
.empty() == true)
267 return VectorizeString(Default
, ',');
272 // Configuration::FindI - Find an integer value /*{{{*/
273 // ---------------------------------------------------------------------
275 int Configuration::FindI(const char *Name
,int const &Default
) const
277 const Item
*Itm
= Lookup(Name
);
278 if (Itm
== 0 || Itm
->Value
.empty() == true)
282 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
283 if (End
== Itm
->Value
.c_str())
289 // Configuration::FindB - Find a boolean type /*{{{*/
290 // ---------------------------------------------------------------------
292 bool Configuration::FindB(const char *Name
,bool const &Default
) const
294 const Item
*Itm
= Lookup(Name
);
295 if (Itm
== 0 || Itm
->Value
.empty() == true)
298 return StringToBool(Itm
->Value
,Default
);
301 // Configuration::FindAny - Find an arbitrary type /*{{{*/
302 // ---------------------------------------------------------------------
303 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
304 string
Configuration::FindAny(const char *Name
,const char *Default
) const
309 if (key
.size() > 2 && key
.end()[-2] == '/')
311 type
= key
.end()[-1];
312 key
.resize(key
.size() - 2);
319 return FindFile(key
.c_str(), Default
);
323 return FindDir(key
.c_str(), Default
);
327 return FindB(key
, Default
) ? "true" : "false";
333 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
339 return Find(Name
, Default
);
342 // Configuration::CndSet - Conditinal Set a value /*{{{*/
343 // ---------------------------------------------------------------------
344 /* This will not overwrite */
345 void Configuration::CndSet(const char *Name
,const string
&Value
)
347 Item
*Itm
= Lookup(Name
,true);
350 if (Itm
->Value
.empty() == true)
354 // Configuration::Set - Set an integer value /*{{{*/
355 // ---------------------------------------------------------------------
357 void Configuration::CndSet(const char *Name
,int const Value
)
359 Item
*Itm
= Lookup(Name
,true);
360 if (Itm
== 0 || Itm
->Value
.empty() == false)
363 snprintf(S
,sizeof(S
),"%i",Value
);
367 // Configuration::Set - Set a value /*{{{*/
368 // ---------------------------------------------------------------------
370 void Configuration::Set(const char *Name
,const string
&Value
)
372 Item
*Itm
= Lookup(Name
,true);
378 // Configuration::Set - Set an integer value /*{{{*/
379 // ---------------------------------------------------------------------
381 void Configuration::Set(const char *Name
,int const &Value
)
383 Item
*Itm
= Lookup(Name
,true);
387 snprintf(S
,sizeof(S
),"%i",Value
);
391 // Configuration::Clear - Clear an single value from a list /*{{{*/
392 // ---------------------------------------------------------------------
394 void Configuration::Clear(string
const &Name
, int const &Value
)
397 snprintf(S
,sizeof(S
),"%i",Value
);
401 // Configuration::Clear - Clear an single value from a list /*{{{*/
402 // ---------------------------------------------------------------------
404 void Configuration::Clear(string
const &Name
, string
const &Value
)
406 Item
*Top
= Lookup(Name
.c_str(),false);
407 if (Top
== 0 || Top
->Child
== 0)
410 Item
*Tmp
, *Prev
, *I
;
411 Prev
= I
= Top
->Child
;
415 if(I
->Value
== Value
)
418 // was first element, point parent to new first element
419 if(Top
->Child
== Tmp
)
420 Top
->Child
= I
->Next
;
432 // Configuration::Clear - Clear everything /*{{{*/
433 // ---------------------------------------------------------------------
434 void Configuration::Clear()
436 const Configuration::Item
*Top
= Tree(0);
439 Clear(Top
->FullTag());
444 // Configuration::Clear - Clear an entire tree /*{{{*/
445 // ---------------------------------------------------------------------
447 void Configuration::Clear(string
const &Name
)
449 Item
*Top
= Lookup(Name
.c_str(),false);
465 while (Top
!= 0 && Top
->Next
== 0)
482 void Configuration::MoveSubTree(char const * const OldRootName
, char const * const NewRootName
)/*{{{*/
484 // prevent NewRoot being a subtree of OldRoot
485 if (OldRootName
== nullptr)
487 if (NewRootName
!= nullptr)
489 if (strcmp(OldRootName
, NewRootName
) == 0)
491 std::string
const oldroot
= std::string(OldRootName
) + "::";
492 if (strcasestr(NewRootName
, oldroot
.c_str()) != NULL
)
497 Item
const * const OldRoot
= Top
= Lookup(OldRootName
, false);
501 if (NewRootName
!= nullptr)
502 NewRoot
.append(NewRootName
).append("::");
505 Item
* const Stop
= Top
;
516 while (Top
!= 0 && Top
->Next
== 0)
518 Set(NewRoot
+ Top
->FullTag(OldRoot
), Top
->Value
);
519 Item
const * const Tmp
= Top
;
527 Set(NewRoot
+ Top
->FullTag(OldRoot
), Top
->Value
);
528 Item
const * const Tmp
= Top
;
535 // Configuration::Exists - Returns true if the Name exists /*{{{*/
536 // ---------------------------------------------------------------------
538 bool Configuration::Exists(const char *Name
) const
540 const Item
*Itm
= Lookup(Name
);
546 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
547 // ---------------------------------------------------------------------
548 /* qualified by /[fdbi] exists */
549 bool Configuration::ExistsAny(const char *Name
) const
553 if (key
.size() > 2 && key
.end()[-2] == '/')
555 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
557 key
.resize(key
.size() - 2);
558 if (Exists(key
.c_str()))
563 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
569 // Configuration::Dump - Dump the config /*{{{*/
570 // ---------------------------------------------------------------------
571 /* Dump the entire configuration space */
572 void Configuration::Dump(ostream
& str
)
574 Dump(str
, NULL
, "%f \"%v\";\n", true);
576 void Configuration::Dump(ostream
& str
, char const * const root
,
577 char const * const formatstr
, bool const emptyValue
)
579 const Configuration::Item
* Top
= Tree(root
);
582 const Configuration::Item
* const Root
= (root
== NULL
) ? NULL
: Top
;
583 std::vector
<std::string
> const format
= VectorizeString(formatstr
, '%');
585 /* Write out all of the configuration directives by walking the
586 configuration tree */
588 if (emptyValue
== true || Top
->Value
.empty() == emptyValue
)
590 std::vector
<std::string
>::const_iterator f
= format
.begin();
592 for (++f
; f
!= format
.end(); ++f
)
594 if (f
->empty() == true)
600 char const type
= (*f
)[0];
602 str
<< Top
->FullTag();
603 else if (type
== 't')
605 else if (type
== 'v')
607 else if (type
== 'F')
608 str
<< QuoteString(Top
->FullTag(), "=\"\n");
609 else if (type
== 'T')
610 str
<< QuoteString(Top
->Tag
, "=\"\n");
611 else if (type
== 'V')
612 str
<< QuoteString(Top
->Value
, "=\"\n");
613 else if (type
== 'n')
615 else if (type
== 'N')
619 str
<< f
->c_str() + 1;
629 while (Top
!= 0 && Top
->Next
== 0)
636 const Configuration::Item
* I
= Top
;
651 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
652 // ---------------------------------------------------------------------
653 /* Stop sets an optional max recursion depth if this item is being viewed as
654 part of a sub tree. */
655 string
Configuration::Item::FullTag(const Item
*Stop
) const
657 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
659 return Parent
->FullTag(Stop
) + "::" + Tag
;
663 // ReadConfigFile - Read a configuration file /*{{{*/
664 // ---------------------------------------------------------------------
665 /* The configuration format is very much like the named.conf format
666 used in bind8, in fact this routine can parse most named.conf files.
667 Sectional config files are like bind's named.conf where there are
668 sections like 'zone "foo.org" { .. };' This causes each section to be
669 added in with a tag like "zone::foo.org" instead of being split
670 tag/value. AsSectional enables Sectional parsing.*/
671 static void leaveCurrentScope(std::stack
<std::string
> &Stack
, std::string
&ParentTag
)
677 ParentTag
= Stack
.top();
681 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
682 unsigned const &Depth
)
684 // Open the stream for reading
685 ifstream
F(FName
.c_str(),ios::in
);
686 if (F
.fail() == true)
687 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
690 std::stack
<std::string
> Stack
;
696 bool InComment
= false;
697 while (F
.eof() == false)
699 // The raw input line.
701 // The input line with comments stripped.
702 std::string Fragment
;
704 // Grab the next line of F and place it in Input.
707 char *Buffer
= new char[1024];
710 F
.getline(Buffer
,sizeof(Buffer
) / 2);
715 while (F
.fail() && !F
.eof());
717 // Expand tabs in the input line and remove leading and trailing
720 const int BufferSize
= Input
.size() * 8 + 1;
721 char *Buffer
= new char[BufferSize
];
724 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
726 _strtabexpand(Buffer
, BufferSize
);
739 // Now strip comments; if the whole line is contained in a
740 // comment, skip this line.
742 // The first meaningful character in the current fragment; will
743 // be adjusted below as we remove bytes from the front.
744 std::string::const_iterator Start
= Input
.begin();
745 // The last meaningful character in the current fragment.
746 std::string::const_iterator End
= Input
.end();
748 // Multi line comment
749 if (InComment
== true)
751 for (std::string::const_iterator I
= Start
;
754 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
761 if (InComment
== true)
765 // Discard single line comments
766 bool InQuote
= false;
767 for (std::string::const_iterator I
= Start
;
775 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
776 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
777 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
784 // Look for multi line comments and build up the
786 Fragment
.reserve(End
- Start
);
788 for (std::string::const_iterator I
= Start
;
794 Fragment
.push_back(*I
);
795 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
798 for (std::string::const_iterator J
= I
;
801 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
803 // Pretend we just finished walking over the
804 // comment, and don't add anything to the output
812 if (InComment
== true)
816 Fragment
.push_back(*I
);
820 if (Fragment
.empty())
823 // The line has actual content; interpret what it means.
825 Start
= Fragment
.begin();
826 End
= Fragment
.end();
827 for (std::string::const_iterator I
= Start
;
833 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
835 // Put the last fragment into the buffer
836 std::string::const_iterator NonWhitespaceStart
= Start
;
837 std::string::const_iterator NonWhitespaceStop
= I
;
838 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
840 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
842 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
844 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
846 // Drop this from the input string, saving the character
847 // that terminated the construct we just closed. (i.e., a
848 // brace or a semicolon)
853 if (TermChar
== '{' && LineBuffer
.empty() == true)
854 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
856 // No string on this line
857 if (LineBuffer
.empty() == true)
860 leaveCurrentScope(Stack
, ParentTag
);
866 const char *Pos
= LineBuffer
.c_str();
867 if (ParseQuoteWord(Pos
,Tag
) == false)
868 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
870 // Parse off the word
873 if (ParseCWord(Pos
,Word
) == false &&
874 ParseQuoteWord(Pos
,Word
) == false)
884 if (strlen(Pos
) != 0)
885 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
890 Stack
.push(ParentTag
);
892 /* Make sectional tags incorperate the section into the
894 if (AsSectional
== true && Word
.empty() == false)
896 Tag
.append("::").append(Word
);
900 if (ParentTag
.empty() == true)
903 ParentTag
.append("::").append(Tag
);
907 // Generate the item name
909 if (ParentTag
.empty() == true)
913 if (TermChar
!= '{' || Tag
.empty() == false)
914 Item
= ParentTag
+ "::" + Tag
;
920 if (Tag
.length() >= 1 && Tag
[0] == '#')
922 if (ParentTag
.empty() == false)
923 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
924 Tag
.erase(Tag
.begin());
927 else if (Tag
== "include")
930 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
931 if (Word
.length() > 2 && Word
.end()[-1] == '/')
933 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
934 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
938 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
939 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
943 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
945 else if (Tag
.empty() == true && NoWord
== false && Word
== "#clear")
946 return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
);
949 // Set the item in the configuration class
957 // Move up a tag, but only if there is no bit to parse
959 leaveCurrentScope(Stack
, ParentTag
);
963 // Store the remaining text, if any, in the current line buffer.
965 // NB: could change this to use string-based operations; I'm
966 // using strstrip now to ensure backwards compatibility.
967 // -- dburrows 2008-04-01
969 char *Buffer
= new char[End
- Start
+ 1];
972 std::copy(Start
, End
, Buffer
);
973 Buffer
[End
- Start
] = '\0';
975 const char *Stripd
= _strstrip(Buffer
);
976 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
978 LineBuffer
+= Stripd
;
989 if (LineBuffer
.empty() == false)
990 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
994 // ReadConfigDir - Read a directory of config files /*{{{*/
995 // ---------------------------------------------------------------------
997 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
998 bool const &AsSectional
, unsigned const &Depth
)
1000 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
1003 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
1004 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
1009 // MatchAgainstConfig Constructor /*{{{*/
1010 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
1012 std::vector
<std::string
> const strings
= _config
->FindVector(Config
);
1013 for (std::vector
<std::string
>::const_iterator s
= strings
.begin();
1014 s
!= strings
.end(); ++s
)
1016 regex_t
*p
= new regex_t
;
1017 if (regcomp(p
, s
->c_str(), REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) == 0)
1018 patterns
.push_back(p
);
1023 _error
->Warning("Invalid regular expression '%s' in configuration "
1024 "option '%s' will be ignored.",
1025 s
->c_str(), Config
);
1029 if (strings
.empty() == true)
1030 patterns
.push_back(NULL
);
1033 // MatchAgainstConfig Destructor /*{{{*/
1034 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
1038 void Configuration::MatchAgainstConfig::clearPatterns()
1040 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1041 p
!= patterns
.end(); ++p
)
1043 if (*p
== NULL
) continue;
1050 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1051 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
1053 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1054 p
!= patterns
.end(); ++p
)
1055 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)