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 #if (APT_PKG_MAJOR >= 4 && APT_PKG_MINOR < 13)
257 vector
<string
> Configuration::FindVector(const char *Name
) const { return FindVector(Name
, ""); }
259 vector
<string
> Configuration::FindVector(const char *Name
, std::string
const &Default
) const
262 const Item
*Top
= Lookup(Name
);
264 return VectorizeString(Default
, ',');
266 if (Top
->Value
.empty() == false)
267 return VectorizeString(Top
->Value
, ',');
269 Item
*I
= Top
->Child
;
272 Vec
.push_back(I
->Value
);
275 if (Vec
.empty() == true)
276 return VectorizeString(Default
, ',');
281 // Configuration::FindI - Find an integer value /*{{{*/
282 // ---------------------------------------------------------------------
284 int Configuration::FindI(const char *Name
,int const &Default
) const
286 const Item
*Itm
= Lookup(Name
);
287 if (Itm
== 0 || Itm
->Value
.empty() == true)
291 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
292 if (End
== Itm
->Value
.c_str())
298 // Configuration::FindB - Find a boolean type /*{{{*/
299 // ---------------------------------------------------------------------
301 bool Configuration::FindB(const char *Name
,bool const &Default
) const
303 const Item
*Itm
= Lookup(Name
);
304 if (Itm
== 0 || Itm
->Value
.empty() == true)
307 return StringToBool(Itm
->Value
,Default
);
310 // Configuration::FindAny - Find an arbitrary type /*{{{*/
311 // ---------------------------------------------------------------------
312 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
313 string
Configuration::FindAny(const char *Name
,const char *Default
) const
318 if (key
.size() > 2 && key
.end()[-2] == '/')
320 type
= key
.end()[-1];
321 key
.resize(key
.size() - 2);
328 return FindFile(key
.c_str(), Default
);
332 return FindDir(key
.c_str(), Default
);
336 return FindB(key
, Default
) ? "true" : "false";
342 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
348 return Find(Name
, Default
);
351 // Configuration::CndSet - Conditinal Set a value /*{{{*/
352 // ---------------------------------------------------------------------
353 /* This will not overwrite */
354 void Configuration::CndSet(const char *Name
,const string
&Value
)
356 Item
*Itm
= Lookup(Name
,true);
359 if (Itm
->Value
.empty() == true)
363 // Configuration::Set - Set an integer value /*{{{*/
364 // ---------------------------------------------------------------------
366 void Configuration::CndSet(const char *Name
,int const Value
)
368 Item
*Itm
= Lookup(Name
,true);
369 if (Itm
== 0 || Itm
->Value
.empty() == false)
372 snprintf(S
,sizeof(S
),"%i",Value
);
376 // Configuration::Set - Set a value /*{{{*/
377 // ---------------------------------------------------------------------
379 void Configuration::Set(const char *Name
,const string
&Value
)
381 Item
*Itm
= Lookup(Name
,true);
387 // Configuration::Set - Set an integer value /*{{{*/
388 // ---------------------------------------------------------------------
390 void Configuration::Set(const char *Name
,int const &Value
)
392 Item
*Itm
= Lookup(Name
,true);
396 snprintf(S
,sizeof(S
),"%i",Value
);
400 // Configuration::Clear - Clear an single value from a list /*{{{*/
401 // ---------------------------------------------------------------------
403 void Configuration::Clear(string
const &Name
, int const &Value
)
406 snprintf(S
,sizeof(S
),"%i",Value
);
410 // Configuration::Clear - Clear an single value from a list /*{{{*/
411 // ---------------------------------------------------------------------
413 void Configuration::Clear(string
const &Name
, string
const &Value
)
415 Item
*Top
= Lookup(Name
.c_str(),false);
416 if (Top
== 0 || Top
->Child
== 0)
419 Item
*Tmp
, *Prev
, *I
;
420 Prev
= I
= Top
->Child
;
424 if(I
->Value
== Value
)
427 // was first element, point parent to new first element
428 if(Top
->Child
== Tmp
)
429 Top
->Child
= I
->Next
;
441 // Configuration::Clear - Clear everything /*{{{*/
442 // ---------------------------------------------------------------------
443 void Configuration::Clear()
445 const Configuration::Item
*Top
= Tree(0);
448 Clear(Top
->FullTag());
453 // Configuration::Clear - Clear an entire tree /*{{{*/
454 // ---------------------------------------------------------------------
456 void Configuration::Clear(string
const &Name
)
458 Item
*Top
= Lookup(Name
.c_str(),false);
474 while (Top
!= 0 && Top
->Next
== 0)
491 // Configuration::Exists - Returns true if the Name exists /*{{{*/
492 // ---------------------------------------------------------------------
494 bool Configuration::Exists(const char *Name
) const
496 const Item
*Itm
= Lookup(Name
);
502 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
503 // ---------------------------------------------------------------------
504 /* qualified by /[fdbi] exists */
505 bool Configuration::ExistsAny(const char *Name
) const
509 if (key
.size() > 2 && key
.end()[-2] == '/')
511 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
513 key
.resize(key
.size() - 2);
514 if (Exists(key
.c_str()))
519 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
525 // Configuration::Dump - Dump the config /*{{{*/
526 // ---------------------------------------------------------------------
527 /* Dump the entire configuration space */
528 void Configuration::Dump(ostream
& str
)
530 Dump(str
, NULL
, "%f \"%v\";\n", true);
532 void Configuration::Dump(ostream
& str
, char const * const root
,
533 char const * const formatstr
, bool const emptyValue
)
535 const Configuration::Item
* Top
= Tree(root
);
538 const Configuration::Item
* const Root
= (root
== NULL
) ? NULL
: Top
;
539 std::vector
<std::string
> const format
= VectorizeString(formatstr
, '%');
541 /* Write out all of the configuration directives by walking the
542 configuration tree */
544 if (emptyValue
== true || Top
->Value
.empty() == emptyValue
)
546 std::vector
<std::string
>::const_iterator f
= format
.begin();
548 for (++f
; f
!= format
.end(); ++f
)
550 if (f
->empty() == true)
556 char const type
= (*f
)[0];
558 str
<< Top
->FullTag();
559 else if (type
== 't')
561 else if (type
== 'v')
563 else if (type
== 'F')
564 str
<< QuoteString(Top
->FullTag(), "=\"\n");
565 else if (type
== 'T')
566 str
<< QuoteString(Top
->Tag
, "=\"\n");
567 else if (type
== 'V')
568 str
<< QuoteString(Top
->Value
, "=\"\n");
569 else if (type
== 'n')
571 else if (type
== 'N')
575 str
<< f
->c_str() + 1;
585 while (Top
!= 0 && Top
->Next
== 0)
592 const Configuration::Item
* I
= Top
;
607 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
608 // ---------------------------------------------------------------------
609 /* Stop sets an optional max recursion depth if this item is being viewed as
610 part of a sub tree. */
611 string
Configuration::Item::FullTag(const Item
*Stop
) const
613 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
615 return Parent
->FullTag(Stop
) + "::" + Tag
;
619 // ReadConfigFile - Read a configuration file /*{{{*/
620 // ---------------------------------------------------------------------
621 /* The configuration format is very much like the named.conf format
622 used in bind8, in fact this routine can parse most named.conf files.
623 Sectional config files are like bind's named.conf where there are
624 sections like 'zone "foo.org" { .. };' This causes each section to be
625 added in with a tag like "zone::foo.org" instead of being split
626 tag/value. AsSectional enables Sectional parsing.*/
627 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
628 unsigned const &Depth
)
630 // Open the stream for reading
631 ifstream
F(FName
.c_str(),ios::in
);
633 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
637 unsigned int StackPos
= 0;
643 bool InComment
= false;
644 while (F
.eof() == false)
646 // The raw input line.
648 // The input line with comments stripped.
649 std::string Fragment
;
651 // Grab the next line of F and place it in Input.
654 char *Buffer
= new char[1024];
657 F
.getline(Buffer
,sizeof(Buffer
) / 2);
662 while (F
.fail() && !F
.eof());
664 // Expand tabs in the input line and remove leading and trailing
667 const int BufferSize
= Input
.size() * 8 + 1;
668 char *Buffer
= new char[BufferSize
];
671 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
673 _strtabexpand(Buffer
, BufferSize
);
686 // Now strip comments; if the whole line is contained in a
687 // comment, skip this line.
689 // The first meaningful character in the current fragment; will
690 // be adjusted below as we remove bytes from the front.
691 std::string::const_iterator Start
= Input
.begin();
692 // The last meaningful character in the current fragment.
693 std::string::const_iterator End
= Input
.end();
695 // Multi line comment
696 if (InComment
== true)
698 for (std::string::const_iterator I
= Start
;
701 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
708 if (InComment
== true)
712 // Discard single line comments
713 bool InQuote
= false;
714 for (std::string::const_iterator I
= Start
;
722 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
723 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
724 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
731 // Look for multi line comments and build up the
733 Fragment
.reserve(End
- Start
);
735 for (std::string::const_iterator I
= Start
;
741 Fragment
.push_back(*I
);
742 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
745 for (std::string::const_iterator J
= I
;
748 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
750 // Pretend we just finished walking over the
751 // comment, and don't add anything to the output
759 if (InComment
== true)
763 Fragment
.push_back(*I
);
767 if (Fragment
.empty())
770 // The line has actual content; interpret what it means.
772 Start
= Fragment
.begin();
773 End
= Fragment
.end();
774 for (std::string::const_iterator I
= Start
;
780 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
782 // Put the last fragment into the buffer
783 std::string::const_iterator NonWhitespaceStart
= Start
;
784 std::string::const_iterator NonWhitespaceStop
= I
;
785 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
787 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
789 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
791 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
793 // Drop this from the input string, saving the character
794 // that terminated the construct we just closed. (i.e., a
795 // brace or a semicolon)
800 if (TermChar
== '{' && LineBuffer
.empty() == true)
801 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
803 // No string on this line
804 if (LineBuffer
.empty() == true)
809 ParentTag
= string();
811 ParentTag
= Stack
[--StackPos
];
818 const char *Pos
= LineBuffer
.c_str();
819 if (ParseQuoteWord(Pos
,Tag
) == false)
820 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
822 // Parse off the word
825 if (ParseCWord(Pos
,Word
) == false &&
826 ParseQuoteWord(Pos
,Word
) == false)
836 if (strlen(Pos
) != 0)
837 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
842 if (StackPos
< sizeof(Stack
)/sizeof(std::string
))
843 Stack
[StackPos
++] = ParentTag
;
845 /* Make sectional tags incorperate the section into the
847 if (AsSectional
== true && Word
.empty() == false)
854 if (ParentTag
.empty() == true)
857 ParentTag
+= string("::") + Tag
;
861 // Generate the item name
863 if (ParentTag
.empty() == true)
867 if (TermChar
!= '{' || Tag
.empty() == false)
868 Item
= ParentTag
+ "::" + Tag
;
874 if (Tag
.length() >= 1 && Tag
[0] == '#')
876 if (ParentTag
.empty() == false)
877 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
878 Tag
.erase(Tag
.begin());
881 else if (Tag
== "include")
884 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
885 if (Word
.length() > 2 && Word
.end()[-1] == '/')
887 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
888 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
892 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
893 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
897 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
899 else if (Tag
.empty() == true && NoWord
== false && Word
== "#clear")
900 return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
);
903 // Set the item in the configuration class
911 // Move up a tag, but only if there is no bit to parse
917 ParentTag
= Stack
[--StackPos
];
923 // Store the remaining text, if any, in the current line buffer.
925 // NB: could change this to use string-based operations; I'm
926 // using strstrip now to ensure backwards compatibility.
927 // -- dburrows 2008-04-01
929 char *Buffer
= new char[End
- Start
+ 1];
932 std::copy(Start
, End
, Buffer
);
933 Buffer
[End
- Start
] = '\0';
935 const char *Stripd
= _strstrip(Buffer
);
936 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
938 LineBuffer
+= Stripd
;
949 if (LineBuffer
.empty() == false)
950 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
954 // ReadConfigDir - Read a directory of config files /*{{{*/
955 // ---------------------------------------------------------------------
957 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
958 bool const &AsSectional
, unsigned const &Depth
)
960 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
963 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
964 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
969 // MatchAgainstConfig Constructor /*{{{*/
970 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
972 std::vector
<std::string
> const strings
= _config
->FindVector(Config
);
973 for (std::vector
<std::string
>::const_iterator s
= strings
.begin();
974 s
!= strings
.end(); ++s
)
976 regex_t
*p
= new regex_t
;
977 if (regcomp(p
, s
->c_str(), REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) == 0)
978 patterns
.push_back(p
);
983 _error
->Warning("Invalid regular expression '%s' in configuration "
984 "option '%s' will be ignored.",
989 if (strings
.empty() == true)
990 patterns
.push_back(NULL
);
993 // MatchAgainstConfig Destructor /*{{{*/
994 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
998 void Configuration::MatchAgainstConfig::clearPatterns()
1000 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1001 p
!= patterns
.end(); ++p
)
1003 if (*p
== NULL
) continue;
1010 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1011 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
1013 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1014 p
!= patterns
.end(); ++p
)
1015 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)