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_ABI < 413
257 vector
<string
> Configuration::FindVector(const char *Name
) const
259 return FindVector(Name
, "");
262 vector
<string
> Configuration::FindVector(const char *Name
, std::string
const &Default
) const
265 const Item
*Top
= Lookup(Name
);
267 return VectorizeString(Default
, ',');
269 if (Top
->Value
.empty() == false)
270 return VectorizeString(Top
->Value
, ',');
272 Item
*I
= Top
->Child
;
275 Vec
.push_back(I
->Value
);
278 if (Vec
.empty() == true)
279 return VectorizeString(Default
, ',');
284 // Configuration::FindI - Find an integer value /*{{{*/
285 // ---------------------------------------------------------------------
287 int Configuration::FindI(const char *Name
,int const &Default
) const
289 const Item
*Itm
= Lookup(Name
);
290 if (Itm
== 0 || Itm
->Value
.empty() == true)
294 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
295 if (End
== Itm
->Value
.c_str())
301 // Configuration::FindB - Find a boolean type /*{{{*/
302 // ---------------------------------------------------------------------
304 bool Configuration::FindB(const char *Name
,bool const &Default
) const
306 const Item
*Itm
= Lookup(Name
);
307 if (Itm
== 0 || Itm
->Value
.empty() == true)
310 return StringToBool(Itm
->Value
,Default
);
313 // Configuration::FindAny - Find an arbitrary type /*{{{*/
314 // ---------------------------------------------------------------------
315 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
316 string
Configuration::FindAny(const char *Name
,const char *Default
) const
321 if (key
.size() > 2 && key
.end()[-2] == '/')
323 type
= key
.end()[-1];
324 key
.resize(key
.size() - 2);
331 return FindFile(key
.c_str(), Default
);
335 return FindDir(key
.c_str(), Default
);
339 return FindB(key
, Default
) ? "true" : "false";
345 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
351 return Find(Name
, Default
);
354 // Configuration::CndSet - Conditinal Set a value /*{{{*/
355 // ---------------------------------------------------------------------
356 /* This will not overwrite */
357 void Configuration::CndSet(const char *Name
,const string
&Value
)
359 Item
*Itm
= Lookup(Name
,true);
362 if (Itm
->Value
.empty() == true)
366 // Configuration::Set - Set an integer value /*{{{*/
367 // ---------------------------------------------------------------------
369 void Configuration::CndSet(const char *Name
,int const Value
)
371 Item
*Itm
= Lookup(Name
,true);
372 if (Itm
== 0 || Itm
->Value
.empty() == false)
375 snprintf(S
,sizeof(S
),"%i",Value
);
379 // Configuration::Set - Set a value /*{{{*/
380 // ---------------------------------------------------------------------
382 void Configuration::Set(const char *Name
,const string
&Value
)
384 Item
*Itm
= Lookup(Name
,true);
390 // Configuration::Set - Set an integer value /*{{{*/
391 // ---------------------------------------------------------------------
393 void Configuration::Set(const char *Name
,int const &Value
)
395 Item
*Itm
= Lookup(Name
,true);
399 snprintf(S
,sizeof(S
),"%i",Value
);
403 // Configuration::Clear - Clear an single value from a list /*{{{*/
404 // ---------------------------------------------------------------------
406 void Configuration::Clear(string
const &Name
, int const &Value
)
409 snprintf(S
,sizeof(S
),"%i",Value
);
413 // Configuration::Clear - Clear an single value from a list /*{{{*/
414 // ---------------------------------------------------------------------
416 void Configuration::Clear(string
const &Name
, string
const &Value
)
418 Item
*Top
= Lookup(Name
.c_str(),false);
419 if (Top
== 0 || Top
->Child
== 0)
422 Item
*Tmp
, *Prev
, *I
;
423 Prev
= I
= Top
->Child
;
427 if(I
->Value
== Value
)
430 // was first element, point parent to new first element
431 if(Top
->Child
== Tmp
)
432 Top
->Child
= I
->Next
;
444 // Configuration::Clear - Clear everything /*{{{*/
445 // ---------------------------------------------------------------------
446 void Configuration::Clear()
448 const Configuration::Item
*Top
= Tree(0);
451 Clear(Top
->FullTag());
456 // Configuration::Clear - Clear an entire tree /*{{{*/
457 // ---------------------------------------------------------------------
459 void Configuration::Clear(string
const &Name
)
461 Item
*Top
= Lookup(Name
.c_str(),false);
477 while (Top
!= 0 && Top
->Next
== 0)
494 // Configuration::Exists - Returns true if the Name exists /*{{{*/
495 // ---------------------------------------------------------------------
497 bool Configuration::Exists(const char *Name
) const
499 const Item
*Itm
= Lookup(Name
);
505 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
506 // ---------------------------------------------------------------------
507 /* qualified by /[fdbi] exists */
508 bool Configuration::ExistsAny(const char *Name
) const
512 if (key
.size() > 2 && key
.end()[-2] == '/')
514 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
516 key
.resize(key
.size() - 2);
517 if (Exists(key
.c_str()))
522 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
528 // Configuration::Dump - Dump the config /*{{{*/
529 // ---------------------------------------------------------------------
530 /* Dump the entire configuration space */
531 void Configuration::Dump(ostream
& str
)
533 Dump(str
, NULL
, "%f \"%v\";\n", true);
535 void Configuration::Dump(ostream
& str
, char const * const root
,
536 char const * const formatstr
, bool const emptyValue
)
538 const Configuration::Item
* Top
= Tree(root
);
541 const Configuration::Item
* const Root
= (root
== NULL
) ? NULL
: Top
;
542 std::vector
<std::string
> const format
= VectorizeString(formatstr
, '%');
544 /* Write out all of the configuration directives by walking the
545 configuration tree */
547 if (emptyValue
== true || Top
->Value
.empty() == emptyValue
)
549 std::vector
<std::string
>::const_iterator f
= format
.begin();
551 for (++f
; f
!= format
.end(); ++f
)
553 if (f
->empty() == true)
559 char const type
= (*f
)[0];
561 str
<< Top
->FullTag();
562 else if (type
== 't')
564 else if (type
== 'v')
566 else if (type
== 'F')
567 str
<< QuoteString(Top
->FullTag(), "=\"\n");
568 else if (type
== 'T')
569 str
<< QuoteString(Top
->Tag
, "=\"\n");
570 else if (type
== 'V')
571 str
<< QuoteString(Top
->Value
, "=\"\n");
572 else if (type
== 'n')
574 else if (type
== 'N')
578 str
<< f
->c_str() + 1;
588 while (Top
!= 0 && Top
->Next
== 0)
595 const Configuration::Item
* I
= Top
;
610 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
611 // ---------------------------------------------------------------------
612 /* Stop sets an optional max recursion depth if this item is being viewed as
613 part of a sub tree. */
614 string
Configuration::Item::FullTag(const Item
*Stop
) const
616 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
618 return Parent
->FullTag(Stop
) + "::" + Tag
;
622 // ReadConfigFile - Read a configuration file /*{{{*/
623 // ---------------------------------------------------------------------
624 /* The configuration format is very much like the named.conf format
625 used in bind8, in fact this routine can parse most named.conf files.
626 Sectional config files are like bind's named.conf where there are
627 sections like 'zone "foo.org" { .. };' This causes each section to be
628 added in with a tag like "zone::foo.org" instead of being split
629 tag/value. AsSectional enables Sectional parsing.*/
630 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
631 unsigned const &Depth
)
633 // Open the stream for reading
634 ifstream
F(FName
.c_str(),ios::in
);
635 if (F
.fail() == true)
636 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
640 unsigned int StackPos
= 0;
646 bool InComment
= false;
647 while (F
.eof() == false)
649 // The raw input line.
651 // The input line with comments stripped.
652 std::string Fragment
;
654 // Grab the next line of F and place it in Input.
657 char *Buffer
= new char[1024];
660 F
.getline(Buffer
,sizeof(Buffer
) / 2);
665 while (F
.fail() && !F
.eof());
667 // Expand tabs in the input line and remove leading and trailing
670 const int BufferSize
= Input
.size() * 8 + 1;
671 char *Buffer
= new char[BufferSize
];
674 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
676 _strtabexpand(Buffer
, BufferSize
);
689 // Now strip comments; if the whole line is contained in a
690 // comment, skip this line.
692 // The first meaningful character in the current fragment; will
693 // be adjusted below as we remove bytes from the front.
694 std::string::const_iterator Start
= Input
.begin();
695 // The last meaningful character in the current fragment.
696 std::string::const_iterator End
= Input
.end();
698 // Multi line comment
699 if (InComment
== true)
701 for (std::string::const_iterator I
= Start
;
704 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
711 if (InComment
== true)
715 // Discard single line comments
716 bool InQuote
= false;
717 for (std::string::const_iterator I
= Start
;
725 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
726 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
727 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
734 // Look for multi line comments and build up the
736 Fragment
.reserve(End
- Start
);
738 for (std::string::const_iterator I
= Start
;
744 Fragment
.push_back(*I
);
745 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
748 for (std::string::const_iterator J
= I
;
751 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
753 // Pretend we just finished walking over the
754 // comment, and don't add anything to the output
762 if (InComment
== true)
766 Fragment
.push_back(*I
);
770 if (Fragment
.empty())
773 // The line has actual content; interpret what it means.
775 Start
= Fragment
.begin();
776 End
= Fragment
.end();
777 for (std::string::const_iterator I
= Start
;
783 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
785 // Put the last fragment into the buffer
786 std::string::const_iterator NonWhitespaceStart
= Start
;
787 std::string::const_iterator NonWhitespaceStop
= I
;
788 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
790 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
792 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
794 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
796 // Drop this from the input string, saving the character
797 // that terminated the construct we just closed. (i.e., a
798 // brace or a semicolon)
803 if (TermChar
== '{' && LineBuffer
.empty() == true)
804 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
806 // No string on this line
807 if (LineBuffer
.empty() == true)
812 ParentTag
= string();
814 ParentTag
= Stack
[--StackPos
];
821 const char *Pos
= LineBuffer
.c_str();
822 if (ParseQuoteWord(Pos
,Tag
) == false)
823 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
825 // Parse off the word
828 if (ParseCWord(Pos
,Word
) == false &&
829 ParseQuoteWord(Pos
,Word
) == false)
839 if (strlen(Pos
) != 0)
840 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
845 if (StackPos
< sizeof(Stack
)/sizeof(std::string
))
846 Stack
[StackPos
++] = ParentTag
;
848 /* Make sectional tags incorperate the section into the
850 if (AsSectional
== true && Word
.empty() == false)
857 if (ParentTag
.empty() == true)
860 ParentTag
+= string("::") + Tag
;
864 // Generate the item name
866 if (ParentTag
.empty() == true)
870 if (TermChar
!= '{' || Tag
.empty() == false)
871 Item
= ParentTag
+ "::" + Tag
;
877 if (Tag
.length() >= 1 && Tag
[0] == '#')
879 if (ParentTag
.empty() == false)
880 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
881 Tag
.erase(Tag
.begin());
884 else if (Tag
== "include")
887 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
888 if (Word
.length() > 2 && Word
.end()[-1] == '/')
890 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
891 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
895 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
896 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
900 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
902 else if (Tag
.empty() == true && NoWord
== false && Word
== "#clear")
903 return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
);
906 // Set the item in the configuration class
914 // Move up a tag, but only if there is no bit to parse
920 ParentTag
= Stack
[--StackPos
];
926 // Store the remaining text, if any, in the current line buffer.
928 // NB: could change this to use string-based operations; I'm
929 // using strstrip now to ensure backwards compatibility.
930 // -- dburrows 2008-04-01
932 char *Buffer
= new char[End
- Start
+ 1];
935 std::copy(Start
, End
, Buffer
);
936 Buffer
[End
- Start
] = '\0';
938 const char *Stripd
= _strstrip(Buffer
);
939 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
941 LineBuffer
+= Stripd
;
952 if (LineBuffer
.empty() == false)
953 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
957 // ReadConfigDir - Read a directory of config files /*{{{*/
958 // ---------------------------------------------------------------------
960 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
961 bool const &AsSectional
, unsigned const &Depth
)
963 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
966 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
967 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
972 // MatchAgainstConfig Constructor /*{{{*/
973 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
975 std::vector
<std::string
> const strings
= _config
->FindVector(Config
);
976 for (std::vector
<std::string
>::const_iterator s
= strings
.begin();
977 s
!= strings
.end(); ++s
)
979 regex_t
*p
= new regex_t
;
980 if (regcomp(p
, s
->c_str(), REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) == 0)
981 patterns
.push_back(p
);
986 _error
->Warning("Invalid regular expression '%s' in configuration "
987 "option '%s' will be ignored.",
992 if (strings
.empty() == true)
993 patterns
.push_back(NULL
);
996 // MatchAgainstConfig Destructor /*{{{*/
997 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
1001 void Configuration::MatchAgainstConfig::clearPatterns()
1003 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1004 p
!= patterns
.end(); ++p
)
1006 if (*p
== NULL
) continue;
1013 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1014 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
1016 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
1017 p
!= patterns
.end(); ++p
)
1018 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)