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
);
229 // do some normalisation by removing // and /./ from the path
230 size_t found
= string::npos
;
231 while ((found
= result
.find("/./")) != string::npos
)
232 result
.replace(found
, 3, "/");
233 while ((found
= result
.find("//")) != string::npos
)
234 result
.replace(found
, 2, "/");
239 // Configuration::FindDir - Find a directory name /*{{{*/
240 // ---------------------------------------------------------------------
241 /* This is like findfile execept the result is terminated in a / */
242 string
Configuration::FindDir(const char *Name
,const char *Default
) const
244 string Res
= FindFile(Name
,Default
);
245 if (Res
.end()[-1] != '/')
247 size_t const found
= Res
.rfind("/dev/null");
248 if (found
!= string::npos
&& found
== Res
.size() - 9)
249 return Res
; // /dev/null returning
255 // Configuration::FindVector - Find a vector of values /*{{{*/
256 // ---------------------------------------------------------------------
257 /* Returns a vector of config values under the given item */
258 vector
<string
> Configuration::FindVector(const char *Name
, std::string
const &Default
, bool const Keys
) const
261 const Item
*Top
= Lookup(Name
);
263 return VectorizeString(Default
, ',');
265 if (Top
->Value
.empty() == false)
266 return VectorizeString(Top
->Value
, ',');
268 Item
*I
= Top
->Child
;
271 Vec
.push_back(Keys
? I
->Tag
: I
->Value
);
274 if (Vec
.empty() == true)
275 return VectorizeString(Default
, ',');
280 // Configuration::FindI - Find an integer value /*{{{*/
281 // ---------------------------------------------------------------------
283 int Configuration::FindI(const char *Name
,int const &Default
) const
285 const Item
*Itm
= Lookup(Name
);
286 if (Itm
== 0 || Itm
->Value
.empty() == true)
290 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
291 if (End
== Itm
->Value
.c_str())
297 // Configuration::FindB - Find a boolean type /*{{{*/
298 // ---------------------------------------------------------------------
300 bool Configuration::FindB(const char *Name
,bool const &Default
) const
302 const Item
*Itm
= Lookup(Name
);
303 if (Itm
== 0 || Itm
->Value
.empty() == true)
306 return StringToBool(Itm
->Value
,Default
);
309 // Configuration::FindAny - Find an arbitrary type /*{{{*/
310 // ---------------------------------------------------------------------
311 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
312 string
Configuration::FindAny(const char *Name
,const char *Default
) const
317 if (key
.size() > 2 && key
.end()[-2] == '/')
319 type
= key
.end()[-1];
320 key
.resize(key
.size() - 2);
327 return FindFile(key
.c_str(), Default
);
331 return FindDir(key
.c_str(), Default
);
335 return FindB(key
, Default
) ? "true" : "false";
341 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
347 return Find(Name
, Default
);
350 // Configuration::CndSet - Conditinal Set a value /*{{{*/
351 // ---------------------------------------------------------------------
352 /* This will not overwrite */
353 void Configuration::CndSet(const char *Name
,const string
&Value
)
355 Item
*Itm
= Lookup(Name
,true);
358 if (Itm
->Value
.empty() == true)
362 // Configuration::Set - Set an integer value /*{{{*/
363 // ---------------------------------------------------------------------
365 void Configuration::CndSet(const char *Name
,int const Value
)
367 Item
*Itm
= Lookup(Name
,true);
368 if (Itm
== 0 || Itm
->Value
.empty() == false)
371 snprintf(S
,sizeof(S
),"%i",Value
);
375 // Configuration::Set - Set a value /*{{{*/
376 // ---------------------------------------------------------------------
378 void Configuration::Set(const char *Name
,const string
&Value
)
380 Item
*Itm
= Lookup(Name
,true);
386 // Configuration::Set - Set an integer value /*{{{*/
387 // ---------------------------------------------------------------------
389 void Configuration::Set(const char *Name
,int const &Value
)
391 Item
*Itm
= Lookup(Name
,true);
395 snprintf(S
,sizeof(S
),"%i",Value
);
399 // Configuration::Clear - Clear an single value from a list /*{{{*/
400 // ---------------------------------------------------------------------
402 void Configuration::Clear(string
const &Name
, int const &Value
)
405 snprintf(S
,sizeof(S
),"%i",Value
);
409 // Configuration::Clear - Clear an single value from a list /*{{{*/
410 // ---------------------------------------------------------------------
412 void Configuration::Clear(string
const &Name
, string
const &Value
)
414 Item
*Top
= Lookup(Name
.c_str(),false);
415 if (Top
== 0 || Top
->Child
== 0)
418 Item
*Tmp
, *Prev
, *I
;
419 Prev
= I
= Top
->Child
;
423 if(I
->Value
== Value
)
426 // was first element, point parent to new first element
427 if(Top
->Child
== Tmp
)
428 Top
->Child
= I
->Next
;
440 // Configuration::Clear - Clear everything /*{{{*/
441 // ---------------------------------------------------------------------
442 void Configuration::Clear()
444 const Configuration::Item
*Top
= Tree(0);
447 Clear(Top
->FullTag());
452 // Configuration::Clear - Clear an entire tree /*{{{*/
453 // ---------------------------------------------------------------------
455 void Configuration::Clear(string
const &Name
)
457 Item
*Top
= Lookup(Name
.c_str(),false);
473 while (Top
!= 0 && Top
->Next
== 0)
490 void Configuration::MoveSubTree(char const * const OldRootName
, char const * const NewRootName
)/*{{{*/
492 // prevent NewRoot being a subtree of OldRoot
493 if (OldRootName
== nullptr)
495 if (NewRootName
!= nullptr)
497 if (strcmp(OldRootName
, NewRootName
) == 0)
499 std::string
const oldroot
= std::string(OldRootName
) + "::";
500 if (strcasestr(NewRootName
, oldroot
.c_str()) != NULL
)
505 Item
const * const OldRoot
= Top
= Lookup(OldRootName
, false);
509 if (NewRootName
!= nullptr)
510 NewRoot
.append(NewRootName
).append("::");
513 Item
* const Stop
= Top
;
524 while (Top
!= 0 && Top
->Next
== 0)
526 Set(NewRoot
+ Top
->FullTag(OldRoot
), Top
->Value
);
527 Item
const * const Tmp
= Top
;
535 Set(NewRoot
+ Top
->FullTag(OldRoot
), Top
->Value
);
536 Item
const * const Tmp
= Top
;
543 // Configuration::Exists - Returns true if the Name exists /*{{{*/
544 // ---------------------------------------------------------------------
546 bool Configuration::Exists(const char *Name
) const
548 const Item
*Itm
= Lookup(Name
);
554 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
555 // ---------------------------------------------------------------------
556 /* qualified by /[fdbi] exists */
557 bool Configuration::ExistsAny(const char *Name
) const
561 if (key
.size() > 2 && key
.end()[-2] == '/')
563 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
565 key
.resize(key
.size() - 2);
566 if (Exists(key
.c_str()))
571 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
577 // Configuration::Dump - Dump the config /*{{{*/
578 // ---------------------------------------------------------------------
579 /* Dump the entire configuration space */
580 void Configuration::Dump(ostream
& str
)
582 Dump(str
, NULL
, "%f \"%v\";\n", true);
584 void Configuration::Dump(ostream
& str
, char const * const root
,
585 char const * const formatstr
, bool const emptyValue
)
587 const Configuration::Item
* Top
= Tree(root
);
590 const Configuration::Item
* const Root
= (root
== NULL
) ? NULL
: Top
;
591 std::vector
<std::string
> const format
= VectorizeString(formatstr
, '%');
593 /* Write out all of the configuration directives by walking the
594 configuration tree */
596 if (emptyValue
== true || Top
->Value
.empty() == emptyValue
)
598 std::vector
<std::string
>::const_iterator f
= format
.begin();
600 for (++f
; f
!= format
.end(); ++f
)
602 if (f
->empty() == true)
608 char const type
= (*f
)[0];
610 str
<< Top
->FullTag();
611 else if (type
== 't')
613 else if (type
== 'v')
615 else if (type
== 'F')
616 str
<< QuoteString(Top
->FullTag(), "=\"\n");
617 else if (type
== 'T')
618 str
<< QuoteString(Top
->Tag
, "=\"\n");
619 else if (type
== 'V')
620 str
<< QuoteString(Top
->Value
, "=\"\n");
621 else if (type
== 'n')
623 else if (type
== 'N')
627 str
<< f
->c_str() + 1;
637 while (Top
!= 0 && Top
->Next
== 0)
644 const Configuration::Item
* I
= Top
;
659 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
660 // ---------------------------------------------------------------------
661 /* Stop sets an optional max recursion depth if this item is being viewed as
662 part of a sub tree. */
663 string
Configuration::Item::FullTag(const Item
*Stop
) const
665 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
667 return Parent
->FullTag(Stop
) + "::" + Tag
;
671 // ReadConfigFile - Read a configuration file /*{{{*/
672 // ---------------------------------------------------------------------
673 /* The configuration format is very much like the named.conf format
674 used in bind8, in fact this routine can parse most named.conf files.
675 Sectional config files are like bind's named.conf where there are
676 sections like 'zone "foo.org" { .. };' This causes each section to be
677 added in with a tag like "zone::foo.org" instead of being split
678 tag/value. AsSectional enables Sectional parsing.*/
679 static void leaveCurrentScope(std::stack
<std::string
> &Stack
, std::string
&ParentTag
)
685 ParentTag
= Stack
.top();
689 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
690 unsigned const &Depth
)
692 // Open the stream for reading
693 ifstream
F(FName
.c_str(),ios::in
);
694 if (F
.fail() == true)
695 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
698 std::stack
<std::string
> Stack
;
704 bool InComment
= false;
705 while (F
.eof() == false)
707 // The raw input line.
709 // The input line with comments stripped.
710 std::string Fragment
;
712 // Grab the next line of F and place it in Input.
715 char *Buffer
= new char[1024];
718 F
.getline(Buffer
,sizeof(Buffer
) / 2);
723 while (F
.fail() && !F
.eof());
725 // Expand tabs in the input line and remove leading and trailing
728 const int BufferSize
= Input
.size() * 8 + 1;
729 char *Buffer
= new char[BufferSize
];
732 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
734 _strtabexpand(Buffer
, BufferSize
);
747 // Now strip comments; if the whole line is contained in a
748 // comment, skip this line.
750 // The first meaningful character in the current fragment; will
751 // be adjusted below as we remove bytes from the front.
752 std::string::const_iterator Start
= Input
.begin();
753 // The last meaningful character in the current fragment.
754 std::string::const_iterator End
= Input
.end();
756 // Multi line comment
757 if (InComment
== true)
759 for (std::string::const_iterator I
= Start
;
762 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
769 if (InComment
== true)
773 // Discard single line comments
774 bool InQuote
= false;
775 for (std::string::const_iterator I
= Start
;
783 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
784 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
785 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
792 // Look for multi line comments and build up the
794 Fragment
.reserve(End
- Start
);
796 for (std::string::const_iterator I
= Start
;
802 Fragment
.push_back(*I
);
803 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
806 for (std::string::const_iterator J
= I
;
809 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
811 // Pretend we just finished walking over the
812 // comment, and don't add anything to the output
820 if (InComment
== true)
824 Fragment
.push_back(*I
);
828 if (Fragment
.empty())
831 // The line has actual content; interpret what it means.
833 Start
= Fragment
.begin();
834 End
= Fragment
.end();
835 for (std::string::const_iterator I
= Start
;
841 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
843 // Put the last fragment into the buffer
844 std::string::const_iterator NonWhitespaceStart
= Start
;
845 std::string::const_iterator NonWhitespaceStop
= I
;
846 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
848 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
850 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
852 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
854 // Drop this from the input string, saving the character
855 // that terminated the construct we just closed. (i.e., a
856 // brace or a semicolon)
861 if (TermChar
== '{' && LineBuffer
.empty() == true)
862 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
864 // No string on this line
865 if (LineBuffer
.empty() == true)
868 leaveCurrentScope(Stack
, ParentTag
);
874 const char *Pos
= LineBuffer
.c_str();
875 if (ParseQuoteWord(Pos
,Tag
) == false)
876 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
878 // Parse off the word
881 if (ParseCWord(Pos
,Word
) == false &&
882 ParseQuoteWord(Pos
,Word
) == false)
892 if (strlen(Pos
) != 0)
893 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
898 Stack
.push(ParentTag
);
900 /* Make sectional tags incorperate the section into the
902 if (AsSectional
== true && Word
.empty() == false)
904 Tag
.append("::").append(Word
);
908 if (ParentTag
.empty() == true)
911 ParentTag
.append("::").append(Tag
);
915 // Generate the item name
917 if (ParentTag
.empty() == true)
921 if (TermChar
!= '{' || Tag
.empty() == false)
922 Item
= ParentTag
+ "::" + Tag
;
928 if (Tag
.length() >= 1 && Tag
[0] == '#')
930 if (ParentTag
.empty() == false)
931 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
932 Tag
.erase(Tag
.begin());
935 else if (Tag
== "include")
938 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
939 if (Word
.length() > 2 && Word
.end()[-1] == '/')
941 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
942 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
946 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
947 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
951 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
953 else if (Tag
.empty() == true && NoWord
== false && Word
== "#clear")
954 return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
);
957 // Set the item in the configuration class
965 // Move up a tag, but only if there is no bit to parse
967 leaveCurrentScope(Stack
, ParentTag
);
971 // Store the remaining text, if any, in the current line buffer.
973 // NB: could change this to use string-based operations; I'm
974 // using strstrip now to ensure backwards compatibility.
975 // -- dburrows 2008-04-01
977 char *Buffer
= new char[End
- Start
+ 1];
980 std::copy(Start
, End
, Buffer
);
981 Buffer
[End
- Start
] = '\0';
983 const char *Stripd
= _strstrip(Buffer
);
984 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
986 LineBuffer
+= Stripd
;
997 if (LineBuffer
.empty() == false)
998 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
1002 // ReadConfigDir - Read a directory of config files /*{{{*/
1003 // ---------------------------------------------------------------------
1005 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
1006 bool const &AsSectional
, unsigned const &Depth
)
1008 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
1011 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
1012 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
1017 // MatchAgainstConfig Constructor /*{{{*/
1018 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
1020 std::vector
<std::string
> const strings
= _config
->FindVector(Config
);
1021 for (std::vector
<std::string
>::const_iterator s
= strings
.begin();
1022 s
!= strings
.end(); ++s
)
1024 regex_t
*p
= new regex_t
;
1025 if (regcomp(p
, s
->c_str(), REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) == 0)
1026 patterns
.push_back(p
);
1031 _error
->Warning("Invalid regular expression '%s' in configuration "
1032 "option '%s' will be ignored.",
1033 s
->c_str(), Config
);
1037 if (strings
.empty() == true)
1038 patterns
.push_back(NULL
);
1041 // MatchAgainstConfig Destructor /*{{{*/
1042 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
1046 void Configuration::MatchAgainstConfig::clearPatterns()
1048 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1049 p
!= patterns
.end(); ++p
)
1051 if (*p
== NULL
) continue;
1058 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1059 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
1061 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1062 p
!= patterns
.end(); ++p
)
1063 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)