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>
34 Configuration
*_config
= new Configuration
;
36 // Configuration::Configuration - Constructor /*{{{*/
37 // ---------------------------------------------------------------------
39 Configuration::Configuration() : ToFree(true)
43 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
48 // Configuration::~Configuration - Destructor /*{{{*/
49 // ---------------------------------------------------------------------
51 Configuration::~Configuration()
65 while (Top
!= 0 && Top
->Next
== 0)
67 Item
*Parent
= Top
->Parent
;
73 Item
*Next
= Top
->Next
;
80 // Configuration::Lookup - Lookup a single item /*{{{*/
81 // ---------------------------------------------------------------------
82 /* This will lookup a single item by name below another item. It is a
83 helper function for the main lookup function */
84 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
85 unsigned long const &Len
,bool const &Create
)
88 Item
*I
= Head
->Child
;
89 Item
**Last
= &Head
->Child
;
91 // Empty strings match nothing. They are used for lists.
94 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
95 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
99 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
107 I
->Tag
.assign(S
,Len
);
114 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
115 // ---------------------------------------------------------------------
116 /* This performs a fully scoped lookup of a given name, possibly creating
118 Configuration::Item
*Configuration::Lookup(const char *Name
,bool const &Create
)
123 const char *Start
= Name
;
124 const char *End
= Start
+ strlen(Name
);
125 const char *TagEnd
= Name
;
127 for (; End
- TagEnd
>= 2; TagEnd
++)
129 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
131 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
134 TagEnd
= Start
= TagEnd
+ 2;
138 // This must be a trailing ::, we create unique items in a list
139 if (End
- Start
== 0)
145 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
149 // Configuration::Find - Find a value /*{{{*/
150 // ---------------------------------------------------------------------
152 string
Configuration::Find(const char *Name
,const char *Default
) const
154 const Item
*Itm
= Lookup(Name
);
155 if (Itm
== 0 || Itm
->Value
.empty() == true)
166 // Configuration::FindFile - Find a Filename /*{{{*/
167 // ---------------------------------------------------------------------
168 /* Directories are stored as the base dir in the Parent node and the
169 sub directory in sub nodes with the final node being the end filename
171 string
Configuration::FindFile(const char *Name
,const char *Default
) const
173 const Item
*RootItem
= Lookup("RootDir");
174 std::string rootDir
= (RootItem
== 0) ? "" : RootItem
->Value
;
175 if(rootDir
.size() > 0 && rootDir
[rootDir
.size() - 1] != '/')
176 rootDir
.push_back('/');
178 const Item
*Itm
= Lookup(Name
);
179 if (Itm
== 0 || Itm
->Value
.empty() == true)
184 return rootDir
+ 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] == '/')
201 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
205 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
208 if (Itm
->Parent
->Value
.end()[-1] != '/')
211 val
.insert(0, Itm
->Parent
->Value
);
215 return rootDir
+ val
;
218 // Configuration::FindDir - Find a directory name /*{{{*/
219 // ---------------------------------------------------------------------
220 /* This is like findfile execept the result is terminated in a / */
221 string
Configuration::FindDir(const char *Name
,const char *Default
) const
223 string Res
= FindFile(Name
,Default
);
224 if (Res
.end()[-1] != '/')
229 // Configuration::FindVector - Find a vector of values /*{{{*/
230 // ---------------------------------------------------------------------
231 /* Returns a vector of config values under the given item */
232 vector
<string
> Configuration::FindVector(const char *Name
) const
235 const Item
*Top
= Lookup(Name
);
239 Item
*I
= Top
->Child
;
242 Vec
.push_back(I
->Value
);
248 // Configuration::FindI - Find an integer value /*{{{*/
249 // ---------------------------------------------------------------------
251 int Configuration::FindI(const char *Name
,int const &Default
) const
253 const Item
*Itm
= Lookup(Name
);
254 if (Itm
== 0 || Itm
->Value
.empty() == true)
258 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
259 if (End
== Itm
->Value
.c_str())
265 // Configuration::FindB - Find a boolean type /*{{{*/
266 // ---------------------------------------------------------------------
268 bool Configuration::FindB(const char *Name
,bool const &Default
) const
270 const Item
*Itm
= Lookup(Name
);
271 if (Itm
== 0 || Itm
->Value
.empty() == true)
274 return StringToBool(Itm
->Value
,Default
);
277 // Configuration::FindAny - Find an arbitrary type /*{{{*/
278 // ---------------------------------------------------------------------
279 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
280 string
Configuration::FindAny(const char *Name
,const char *Default
) const
285 if (key
.size() > 2 && key
.end()[-2] == '/')
287 type
= key
.end()[-1];
288 key
.resize(key
.size() - 2);
295 return FindFile(key
.c_str(), Default
);
299 return FindDir(key
.c_str(), Default
);
303 return FindB(key
, Default
) ? "true" : "false";
309 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
315 return Find(Name
, Default
);
318 // Configuration::CndSet - Conditinal Set a value /*{{{*/
319 // ---------------------------------------------------------------------
320 /* This will not overwrite */
321 void Configuration::CndSet(const char *Name
,const string
&Value
)
323 Item
*Itm
= Lookup(Name
,true);
326 if (Itm
->Value
.empty() == true)
330 // Configuration::Set - Set an integer value /*{{{*/
331 // ---------------------------------------------------------------------
333 void Configuration::CndSet(const char *Name
,int const Value
)
335 Item
*Itm
= Lookup(Name
,true);
336 if (Itm
== 0 || Itm
->Value
.empty() == false)
339 snprintf(S
,sizeof(S
),"%i",Value
);
343 // Configuration::Set - Set a value /*{{{*/
344 // ---------------------------------------------------------------------
346 void Configuration::Set(const char *Name
,const string
&Value
)
348 Item
*Itm
= Lookup(Name
,true);
354 // Configuration::Set - Set an integer value /*{{{*/
355 // ---------------------------------------------------------------------
357 void Configuration::Set(const char *Name
,int const &Value
)
359 Item
*Itm
= Lookup(Name
,true);
363 snprintf(S
,sizeof(S
),"%i",Value
);
367 // Configuration::Clear - Clear an single value from a list /*{{{*/
368 // ---------------------------------------------------------------------
370 void Configuration::Clear(string
const &Name
, int const &Value
)
373 snprintf(S
,sizeof(S
),"%i",Value
);
377 // Configuration::Clear - Clear an single value from a list /*{{{*/
378 // ---------------------------------------------------------------------
380 void Configuration::Clear(string
const &Name
, string
const &Value
)
382 Item
*Top
= Lookup(Name
.c_str(),false);
383 if (Top
== 0 || Top
->Child
== 0)
386 Item
*Tmp
, *Prev
, *I
;
387 Prev
= I
= Top
->Child
;
391 if(I
->Value
== Value
)
394 // was first element, point parent to new first element
395 if(Top
->Child
== Tmp
)
396 Top
->Child
= I
->Next
;
408 // Configuration::Clear - Clear an entire tree /*{{{*/
409 // ---------------------------------------------------------------------
411 void Configuration::Clear(string
const &Name
)
413 Item
*Top
= Lookup(Name
.c_str(),false);
429 while (Top
!= 0 && Top
->Next
== 0)
446 // Configuration::Exists - Returns true if the Name exists /*{{{*/
447 // ---------------------------------------------------------------------
449 bool Configuration::Exists(const char *Name
) const
451 const Item
*Itm
= Lookup(Name
);
457 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
458 // ---------------------------------------------------------------------
459 /* qualified by /[fdbi] exists */
460 bool Configuration::ExistsAny(const char *Name
) const
464 if (key
.size() > 2 && key
.end()[-2] == '/')
466 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
468 key
.resize(key
.size() - 2);
469 if (Exists(key
.c_str()))
474 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
480 // Configuration::Dump - Dump the config /*{{{*/
481 // ---------------------------------------------------------------------
482 /* Dump the entire configuration space */
483 void Configuration::Dump(ostream
& str
)
485 /* Write out all of the configuration directives by walking the
486 configuration tree */
487 const Configuration::Item
*Top
= Tree(0);
490 str
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
498 while (Top
!= 0 && Top
->Next
== 0)
506 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
507 // ---------------------------------------------------------------------
508 /* Stop sets an optional max recursion depth if this item is being viewed as
509 part of a sub tree. */
510 string
Configuration::Item::FullTag(const Item
*Stop
) const
512 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
514 return Parent
->FullTag(Stop
) + "::" + Tag
;
518 // ReadConfigFile - Read a configuration file /*{{{*/
519 // ---------------------------------------------------------------------
520 /* The configuration format is very much like the named.conf format
521 used in bind8, in fact this routine can parse most named.conf files.
522 Sectional config files are like bind's named.conf where there are
523 sections like 'zone "foo.org" { .. };' This causes each section to be
524 added in with a tag like "zone::foo.org" instead of being split
525 tag/value. AsSectional enables Sectional parsing.*/
526 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool const &AsSectional
,
527 unsigned const &Depth
)
529 // Open the stream for reading
530 ifstream
F(FName
.c_str(),ios::in
);
532 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
536 unsigned int StackPos
= 0;
542 bool InComment
= false;
543 while (F
.eof() == false)
545 // The raw input line.
547 // The input line with comments stripped.
548 std::string Fragment
;
550 // Grab the next line of F and place it in Input.
553 char *Buffer
= new char[1024];
556 F
.getline(Buffer
,sizeof(Buffer
) / 2);
561 while (F
.fail() && !F
.eof());
563 // Expand tabs in the input line and remove leading and trailing
566 const int BufferSize
= Input
.size() * 8 + 1;
567 char *Buffer
= new char[BufferSize
];
570 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
572 _strtabexpand(Buffer
, BufferSize
);
585 // Now strip comments; if the whole line is contained in a
586 // comment, skip this line.
588 // The first meaningful character in the current fragment; will
589 // be adjusted below as we remove bytes from the front.
590 std::string::const_iterator Start
= Input
.begin();
591 // The last meaningful character in the current fragment.
592 std::string::const_iterator End
= Input
.end();
594 // Multi line comment
595 if (InComment
== true)
597 for (std::string::const_iterator I
= Start
;
600 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
607 if (InComment
== true)
611 // Discard single line comments
612 bool InQuote
= false;
613 for (std::string::const_iterator I
= Start
;
621 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
622 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
623 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
630 // Look for multi line comments and build up the
632 Fragment
.reserve(End
- Start
);
634 for (std::string::const_iterator I
= Start
;
640 Fragment
.push_back(*I
);
641 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
644 for (std::string::const_iterator J
= I
;
647 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
649 // Pretend we just finished walking over the
650 // comment, and don't add anything to the output
658 if (InComment
== true)
662 Fragment
.push_back(*I
);
666 if (Fragment
.empty())
669 // The line has actual content; interpret what it means.
671 Start
= Fragment
.begin();
672 End
= Fragment
.end();
673 for (std::string::const_iterator I
= Start
;
679 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
681 // Put the last fragment into the buffer
682 std::string::const_iterator NonWhitespaceStart
= Start
;
683 std::string::const_iterator NonWhitespaceStop
= I
;
684 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; ++NonWhitespaceStart
)
686 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; --NonWhitespaceStop
)
688 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
690 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
692 // Drop this from the input string, saving the character
693 // that terminated the construct we just closed. (i.e., a
694 // brace or a semicolon)
699 if (TermChar
== '{' && LineBuffer
.empty() == true)
700 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
702 // No string on this line
703 if (LineBuffer
.empty() == true)
708 ParentTag
= string();
710 ParentTag
= Stack
[--StackPos
];
717 const char *Pos
= LineBuffer
.c_str();
718 if (ParseQuoteWord(Pos
,Tag
) == false)
719 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
721 // Parse off the word
724 if (ParseCWord(Pos
,Word
) == false &&
725 ParseQuoteWord(Pos
,Word
) == false)
735 if (strlen(Pos
) != 0)
736 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
742 Stack
[StackPos
++] = ParentTag
;
744 /* Make sectional tags incorperate the section into the
746 if (AsSectional
== true && Word
.empty() == false)
753 if (ParentTag
.empty() == true)
756 ParentTag
+= string("::") + Tag
;
760 // Generate the item name
762 if (ParentTag
.empty() == true)
766 if (TermChar
!= '{' || Tag
.empty() == false)
767 Item
= ParentTag
+ "::" + Tag
;
773 if (Tag
.length() >= 1 && Tag
[0] == '#')
775 if (ParentTag
.empty() == false)
776 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
777 Tag
.erase(Tag
.begin());
780 else if (Tag
== "include")
783 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
784 if (Word
.length() > 2 && Word
.end()[-1] == '/')
786 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
787 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
791 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
792 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
796 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
798 else if (Tag
.empty() == true && NoWord
== false && Word
== "#clear")
799 return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
);
802 // Set the item in the configuration class
810 // Move up a tag, but only if there is no bit to parse
816 ParentTag
= Stack
[--StackPos
];
822 // Store the remaining text, if any, in the current line buffer.
824 // NB: could change this to use string-based operations; I'm
825 // using strstrip now to ensure backwards compatibility.
826 // -- dburrows 2008-04-01
828 char *Buffer
= new char[End
- Start
+ 1];
831 std::copy(Start
, End
, Buffer
);
832 Buffer
[End
- Start
] = '\0';
834 const char *Stripd
= _strstrip(Buffer
);
835 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
837 LineBuffer
+= Stripd
;
848 if (LineBuffer
.empty() == false)
849 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
853 // ReadConfigDir - Read a directory of config files /*{{{*/
854 // ---------------------------------------------------------------------
856 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
857 bool const &AsSectional
, unsigned const &Depth
)
859 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
862 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); ++I
)
863 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)
868 // MatchAgainstConfig Constructor /*{{{*/
869 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
)
871 std::vector
<std::string
> const strings
= _config
->FindVector(Config
);
872 for (std::vector
<std::string
>::const_iterator s
= strings
.begin();
873 s
!= strings
.end(); ++s
)
875 regex_t
*p
= new regex_t
;
876 if (regcomp(p
, s
->c_str(), REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) == 0)
877 patterns
.push_back(p
);
882 _error
->Warning("Invalid regular expression '%s' in configuration "
883 "option '%s' will be ignored.",
888 if (strings
.size() == 0)
889 patterns
.push_back(NULL
);
892 // MatchAgainstConfig Destructor /*{{{*/
893 Configuration::MatchAgainstConfig::~MatchAgainstConfig()
897 void Configuration::MatchAgainstConfig::clearPatterns()
899 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
900 p
!= patterns
.end(); ++p
)
902 if (*p
== NULL
) continue;
909 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
910 bool Configuration::MatchAgainstConfig::Match(char const * str
) const
912 for(std::vector
<regex_t
*>::const_iterator p
= patterns
.begin();
913 p
!= patterns
.end(); ++p
)
914 if (*p
!= NULL
&& regexec(*p
, str
, 0, 0, 0) == 0)