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 /*{{{*/
18 #include <apt-pkg/configuration.h>
19 #include <apt-pkg/error.h>
20 #include <apt-pkg/strutl.h>
21 #include <apt-pkg/fileutl.h>
31 Configuration
*_config
= new Configuration
;
33 // Configuration::Configuration - Constructor /*{{{*/
34 // ---------------------------------------------------------------------
36 Configuration::Configuration() : ToFree(true)
40 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
45 // Configuration::~Configuration - Destructor /*{{{*/
46 // ---------------------------------------------------------------------
48 Configuration::~Configuration()
62 while (Top
!= 0 && Top
->Next
== 0)
64 Item
*Parent
= Top
->Parent
;
70 Item
*Next
= Top
->Next
;
77 // Configuration::Lookup - Lookup a single item /*{{{*/
78 // ---------------------------------------------------------------------
79 /* This will lookup a single item by name below another item. It is a
80 helper function for the main lookup function */
81 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
82 unsigned long Len
,bool Create
)
85 Item
*I
= Head
->Child
;
86 Item
**Last
= &Head
->Child
;
88 // Empty strings match nothing. They are used for lists.
91 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
92 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
96 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
104 I
->Tag
.assign(S
,Len
);
111 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
112 // ---------------------------------------------------------------------
113 /* This performs a fully scoped lookup of a given name, possibly creating
115 Configuration::Item
*Configuration::Lookup(const char *Name
,bool Create
)
120 const char *Start
= Name
;
121 const char *End
= Start
+ strlen(Name
);
122 const char *TagEnd
= Name
;
124 for (; End
- TagEnd
>= 2; TagEnd
++)
126 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
128 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
131 TagEnd
= Start
= TagEnd
+ 2;
135 // This must be a trailing ::, we create unique items in a list
136 if (End
- Start
== 0)
142 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
146 // Configuration::Find - Find a value /*{{{*/
147 // ---------------------------------------------------------------------
149 string
Configuration::Find(const char *Name
,const char *Default
) const
151 const Item
*Itm
= Lookup(Name
);
152 if (Itm
== 0 || Itm
->Value
.empty() == true)
163 // Configuration::FindFile - Find a Filename /*{{{*/
164 // ---------------------------------------------------------------------
165 /* Directories are stored as the base dir in the Parent node and the
166 sub directory in sub nodes with the final node being the end filename
168 string
Configuration::FindFile(const char *Name
,const char *Default
) const
170 const Item
*RootItem
= Lookup("RootDir");
171 std::string rootDir
= (RootItem
== 0) ? "" : RootItem
->Value
;
172 if(rootDir
.size() > 0 && rootDir
[rootDir
.size() - 1] != '/')
173 rootDir
.push_back('/');
175 const Item
*Itm
= Lookup(Name
);
176 if (Itm
== 0 || Itm
->Value
.empty() == true)
181 return rootDir
+ Default
;
184 string val
= Itm
->Value
;
185 while (Itm
->Parent
!= 0 && Itm
->Parent
->Value
.empty() == false)
188 if (val
.length() >= 1 && val
[0] == '/')
192 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
196 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
199 if (Itm
->Parent
->Value
.end()[-1] != '/')
202 val
.insert(0, Itm
->Parent
->Value
);
206 return rootDir
+ val
;
209 // Configuration::FindDir - Find a directory name /*{{{*/
210 // ---------------------------------------------------------------------
211 /* This is like findfile execept the result is terminated in a / */
212 string
Configuration::FindDir(const char *Name
,const char *Default
) const
214 string Res
= FindFile(Name
,Default
);
215 if (Res
.end()[-1] != '/')
220 // Configuration::FindVector - Find a vector of values /*{{{*/
221 // ---------------------------------------------------------------------
222 /* Returns a vector of config values under the given item */
223 vector
<string
> Configuration::FindVector(const char *Name
) const
226 const Item
*Top
= Lookup(Name
);
230 Item
*I
= Top
->Child
;
233 Vec
.push_back(I
->Value
);
239 // Configuration::FindI - Find an integer value /*{{{*/
240 // ---------------------------------------------------------------------
242 int Configuration::FindI(const char *Name
,int Default
) const
244 const Item
*Itm
= Lookup(Name
);
245 if (Itm
== 0 || Itm
->Value
.empty() == true)
249 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
250 if (End
== Itm
->Value
.c_str())
256 // Configuration::FindB - Find a boolean type /*{{{*/
257 // ---------------------------------------------------------------------
259 bool Configuration::FindB(const char *Name
,bool Default
) const
261 const Item
*Itm
= Lookup(Name
);
262 if (Itm
== 0 || Itm
->Value
.empty() == true)
265 return StringToBool(Itm
->Value
,Default
);
268 // Configuration::FindAny - Find an arbitrary type /*{{{*/
269 // ---------------------------------------------------------------------
270 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
271 string
Configuration::FindAny(const char *Name
,const char *Default
) const
276 if (key
.size() > 2 && key
.end()[-2] == '/')
278 type
= key
.end()[-1];
279 key
.resize(key
.size() - 2);
286 return FindFile(key
.c_str(), Default
);
290 return FindDir(key
.c_str(), Default
);
294 return FindB(key
, Default
) ? "true" : "false";
300 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
306 return Find(Name
, Default
);
309 // Configuration::CndSet - Conditinal Set a value /*{{{*/
310 // ---------------------------------------------------------------------
311 /* This will not overwrite */
312 void Configuration::CndSet(const char *Name
,const string
&Value
)
314 Item
*Itm
= Lookup(Name
,true);
317 if (Itm
->Value
.empty() == true)
321 // Configuration::Set - Set a value /*{{{*/
322 // ---------------------------------------------------------------------
324 void Configuration::Set(const char *Name
,const string
&Value
)
326 Item
*Itm
= Lookup(Name
,true);
332 // Configuration::Set - Set an integer value /*{{{*/
333 // ---------------------------------------------------------------------
335 void Configuration::Set(const char *Name
,int Value
)
337 Item
*Itm
= Lookup(Name
,true);
341 snprintf(S
,sizeof(S
),"%i",Value
);
345 // Configuration::Clear - Clear an single value from a list /*{{{*/
346 // ---------------------------------------------------------------------
348 void Configuration::Clear(const string Name
, int Value
)
351 snprintf(S
,sizeof(S
),"%i",Value
);
355 // Configuration::Clear - Clear an single value from a list /*{{{*/
356 // ---------------------------------------------------------------------
358 void Configuration::Clear(const string Name
, string Value
)
360 Item
*Top
= Lookup(Name
.c_str(),false);
361 if (Top
== 0 || Top
->Child
== 0)
364 Item
*Tmp
, *Prev
, *I
;
365 Prev
= I
= Top
->Child
;
369 if(I
->Value
== Value
)
372 // was first element, point parent to new first element
373 if(Top
->Child
== Tmp
)
374 Top
->Child
= I
->Next
;
386 // Configuration::Clear - Clear an entire tree /*{{{*/
387 // ---------------------------------------------------------------------
389 void Configuration::Clear(string Name
)
391 Item
*Top
= Lookup(Name
.c_str(),false);
407 while (Top
!= 0 && Top
->Next
== 0)
424 // Configuration::Exists - Returns true if the Name exists /*{{{*/
425 // ---------------------------------------------------------------------
427 bool Configuration::Exists(const char *Name
) const
429 const Item
*Itm
= Lookup(Name
);
435 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
436 // ---------------------------------------------------------------------
437 /* qualified by /[fdbi] exists */
438 bool Configuration::ExistsAny(const char *Name
) const
442 if (key
.size() > 2 && key
.end()[-2] == '/')
444 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
446 key
.resize(key
.size() - 2);
447 if (Exists(key
.c_str()))
452 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
458 // Configuration::Dump - Dump the config /*{{{*/
459 // ---------------------------------------------------------------------
460 /* Dump the entire configuration space */
461 void Configuration::Dump(ostream
& str
)
463 /* Write out all of the configuration directives by walking the
464 configuration tree */
465 const Configuration::Item
*Top
= Tree(0);
468 str
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
476 while (Top
!= 0 && Top
->Next
== 0)
484 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
485 // ---------------------------------------------------------------------
486 /* Stop sets an optional max recursion depth if this item is being viewed as
487 part of a sub tree. */
488 string
Configuration::Item::FullTag(const Item
*Stop
) const
490 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
492 return Parent
->FullTag(Stop
) + "::" + Tag
;
496 // ReadConfigFile - Read a configuration file /*{{{*/
497 // ---------------------------------------------------------------------
498 /* The configuration format is very much like the named.conf format
499 used in bind8, in fact this routine can parse most named.conf files.
500 Sectional config files are like bind's named.conf where there are
501 sections like 'zone "foo.org" { .. };' This causes each section to be
502 added in with a tag like "zone::foo.org" instead of being split
503 tag/value. AsSectional enables Sectional parsing.*/
504 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool AsSectional
,
507 // Open the stream for reading
508 ifstream
F(FName
.c_str(),ios::in
);
510 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
514 unsigned int StackPos
= 0;
520 bool InComment
= false;
521 while (F
.eof() == false)
523 // The raw input line.
525 // The input line with comments stripped.
526 std::string Fragment
;
528 // Grab the next line of F and place it in Input.
531 char *Buffer
= new char[1024];
534 F
.getline(Buffer
,sizeof(Buffer
) / 2);
539 while (F
.fail() && !F
.eof());
541 // Expand tabs in the input line and remove leading and trailing
544 const int BufferSize
= Input
.size() * 8 + 1;
545 char *Buffer
= new char[BufferSize
];
548 memcpy(Buffer
, Input
.c_str(), Input
.size() + 1);
550 _strtabexpand(Buffer
, BufferSize
);
563 // Now strip comments; if the whole line is contained in a
564 // comment, skip this line.
566 // The first meaningful character in the current fragment; will
567 // be adjusted below as we remove bytes from the front.
568 std::string::const_iterator Start
= Input
.begin();
569 // The last meaningful character in the current fragment.
570 std::string::const_iterator End
= Input
.end();
572 // Multi line comment
573 if (InComment
== true)
575 for (std::string::const_iterator I
= Start
;
578 if (*I
== '*' && I
+ 1 != End
&& I
[1] == '/')
585 if (InComment
== true)
589 // Discard single line comments
590 bool InQuote
= false;
591 for (std::string::const_iterator I
= Start
;
599 if ((*I
== '/' && I
+ 1 != End
&& I
[1] == '/') ||
600 (*I
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 &&
601 strcmp(string(I
,I
+8).c_str(),"#include") != 0))
608 // Look for multi line comments and build up the
610 Fragment
.reserve(End
- Start
);
612 for (std::string::const_iterator I
= Start
;
618 Fragment
.push_back(*I
);
619 else if (*I
== '/' && I
+ 1 != End
&& I
[1] == '*')
622 for (std::string::const_iterator J
= I
;
625 if (*J
== '*' && J
+ 1 != End
&& J
[1] == '/')
627 // Pretend we just finished walking over the
628 // comment, and don't add anything to the output
636 if (InComment
== true)
640 Fragment
.push_back(*I
);
644 if (Fragment
.empty())
647 // The line has actual content; interpret what it means.
649 Start
= Fragment
.begin();
650 End
= Fragment
.end();
651 for (std::string::const_iterator I
= Start
;
657 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
659 // Put the last fragment into the buffer
660 std::string::const_iterator NonWhitespaceStart
= Start
;
661 std::string::const_iterator NonWhitespaceStop
= I
;
662 for (; NonWhitespaceStart
!= I
&& isspace(*NonWhitespaceStart
) != 0; NonWhitespaceStart
++)
664 for (; NonWhitespaceStop
!= NonWhitespaceStart
&& isspace(NonWhitespaceStop
[-1]) != 0; NonWhitespaceStop
--)
666 if (LineBuffer
.empty() == false && NonWhitespaceStop
- NonWhitespaceStart
!= 0)
668 LineBuffer
+= string(NonWhitespaceStart
, NonWhitespaceStop
);
670 // Drop this from the input string, saving the character
671 // that terminated the construct we just closed. (i.e., a
672 // brace or a semicolon)
677 if (TermChar
== '{' && LineBuffer
.empty() == true)
678 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
680 // No string on this line
681 if (LineBuffer
.empty() == true)
686 ParentTag
= string();
688 ParentTag
= Stack
[--StackPos
];
695 const char *Pos
= LineBuffer
.c_str();
696 if (ParseQuoteWord(Pos
,Tag
) == false)
697 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
699 // Parse off the word
702 if (ParseCWord(Pos
,Word
) == false &&
703 ParseQuoteWord(Pos
,Word
) == false)
713 if (strlen(Pos
) != 0)
714 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
720 Stack
[StackPos
++] = ParentTag
;
722 /* Make sectional tags incorperate the section into the
724 if (AsSectional
== true && Word
.empty() == false)
731 if (ParentTag
.empty() == true)
734 ParentTag
+= string("::") + Tag
;
738 // Generate the item name
740 if (ParentTag
.empty() == true)
744 if (TermChar
!= '{' || Tag
.empty() == false)
745 Item
= ParentTag
+ "::" + Tag
;
751 if (Tag
.length() >= 1 && Tag
[0] == '#')
753 if (ParentTag
.empty() == false)
754 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
755 Tag
.erase(Tag
.begin());
758 else if (Tag
== "include")
761 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
762 if (Word
.length() > 2 && Word
.end()[-1] == '/')
764 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
765 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
769 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
770 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
774 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
778 // Set the item in the configuration class
786 // Move up a tag, but only if there is no bit to parse
792 ParentTag
= Stack
[--StackPos
];
798 // Store the remaining text, if any, in the current line buffer.
800 // NB: could change this to use string-based operations; I'm
801 // using strstrip now to ensure backwards compatibility.
802 // -- dburrows 2008-04-01
804 char *Buffer
= new char[End
- Start
+ 1];
807 std::copy(Start
, End
, Buffer
);
808 Buffer
[End
- Start
] = '\0';
810 const char *Stripd
= _strstrip(Buffer
);
811 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
813 LineBuffer
+= Stripd
;
824 if (LineBuffer
.empty() == false)
825 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
829 // ReadConfigDir - Read a directory of config files /*{{{*/
830 // ---------------------------------------------------------------------
832 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,
833 bool AsSectional
, unsigned Depth
)
835 vector
<string
> const List
= GetListOfFilesInDir(Dir
, "conf", true, true);
838 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); I
++)
839 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)