]>
git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
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>
37 Configuration
*_config
= new Configuration
;
39 // Configuration::Configuration - Constructor /*{{{*/
40 // ---------------------------------------------------------------------
42 Configuration::Configuration() : ToFree(true)
46 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
51 // Configuration::~Configuration - Destructor /*{{{*/
52 // ---------------------------------------------------------------------
54 Configuration::~Configuration()
68 while (Top
!= 0 && Top
->Next
== 0)
70 Item
*Parent
= Top
->Parent
;
76 Item
*Next
= Top
->Next
;
83 // Configuration::Lookup - Lookup a single item /*{{{*/
84 // ---------------------------------------------------------------------
85 /* This will lookup a single item by name below another item. It is a
86 helper function for the main lookup function */
87 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
88 unsigned long Len
,bool Create
)
91 Item
*I
= Head
->Child
;
92 Item
**Last
= &Head
->Child
;
94 // Empty strings match nothing. They are used for lists.
97 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
98 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
102 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
110 I
->Tag
.assign(S
,Len
);
117 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
118 // ---------------------------------------------------------------------
119 /* This performs a fully scoped lookup of a given name, possibly creating
121 Configuration::Item
*Configuration::Lookup(const char *Name
,bool Create
)
126 const char *Start
= Name
;
127 const char *End
= Start
+ strlen(Name
);
128 const char *TagEnd
= Name
;
130 for (; End
- TagEnd
>= 2; TagEnd
++)
132 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
134 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
137 TagEnd
= Start
= TagEnd
+ 2;
141 // This must be a trailing ::, we create unique items in a list
142 if (End
- Start
== 0)
148 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
152 // Configuration::Find - Find a value /*{{{*/
153 // ---------------------------------------------------------------------
155 string
Configuration::Find(const char *Name
,const char *Default
) const
157 const Item
*Itm
= Lookup(Name
);
158 if (Itm
== 0 || Itm
->Value
.empty() == true)
169 // Configuration::FindFile - Find a Filename /*{{{*/
170 // ---------------------------------------------------------------------
171 /* Directories are stored as the base dir in the Parent node and the
172 sub directory in sub nodes with the final node being the end filename
174 string
Configuration::FindFile(const char *Name
,const char *Default
) const
176 const Item
*Itm
= Lookup(Name
);
177 if (Itm
== 0 || Itm
->Value
.empty() == true)
185 string val
= Itm
->Value
;
186 while (Itm
->Parent
!= 0 && Itm
->Parent
->Value
.empty() == false)
189 if (val
.length() >= 1 && val
[0] == '/')
193 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
197 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
200 if (Itm
->Parent
->Value
.end()[-1] != '/')
203 val
.insert(0, Itm
->Parent
->Value
);
210 // Configuration::FindDir - Find a directory name /*{{{*/
211 // ---------------------------------------------------------------------
212 /* This is like findfile execept the result is terminated in a / */
213 string
Configuration::FindDir(const char *Name
,const char *Default
) const
215 string Res
= FindFile(Name
,Default
);
216 if (Res
.end()[-1] != '/')
221 // Configuration::FindI - Find an integer value /*{{{*/
222 // ---------------------------------------------------------------------
224 int Configuration::FindI(const char *Name
,int Default
) const
226 const Item
*Itm
= Lookup(Name
);
227 if (Itm
== 0 || Itm
->Value
.empty() == true)
231 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
232 if (End
== Itm
->Value
.c_str())
238 // Configuration::FindB - Find a boolean type /*{{{*/
239 // ---------------------------------------------------------------------
241 bool Configuration::FindB(const char *Name
,bool Default
) const
243 const Item
*Itm
= Lookup(Name
);
244 if (Itm
== 0 || Itm
->Value
.empty() == true)
247 return StringToBool(Itm
->Value
,Default
);
250 // Configuration::FindAny - Find an arbitrary type /*{{{*/
251 // ---------------------------------------------------------------------
252 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
253 string
Configuration::FindAny(const char *Name
,const char *Default
) const
258 if (key
.size() > 2 && key
.end()[-2] == '/')
260 type
= key
.end()[-1];
261 key
.resize(key
.size() - 2);
268 return FindFile(key
.c_str(), Default
);
272 return FindDir(key
.c_str(), Default
);
276 return FindB(key
, Default
) ? "true" : "false";
282 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
288 return Find(Name
, Default
);
291 // Configuration::CndSet - Conditinal Set a value /*{{{*/
292 // ---------------------------------------------------------------------
293 /* This will not overwrite */
294 void Configuration::CndSet(const char *Name
,const string
&Value
)
296 Item
*Itm
= Lookup(Name
,true);
299 if (Itm
->Value
.empty() == true)
303 // Configuration::Set - Set a value /*{{{*/
304 // ---------------------------------------------------------------------
306 void Configuration::Set(const char *Name
,const string
&Value
)
308 Item
*Itm
= Lookup(Name
,true);
314 // Configuration::Set - Set an integer value /*{{{*/
315 // ---------------------------------------------------------------------
317 void Configuration::Set(const char *Name
,int Value
)
319 Item
*Itm
= Lookup(Name
,true);
323 snprintf(S
,sizeof(S
),"%i",Value
);
327 // Configuration::Clear - Clear an single value from a list /*{{{*/
328 // ---------------------------------------------------------------------
330 void Configuration::Clear(const string Name
, int Value
)
333 snprintf(S
,sizeof(S
),"%i",Value
);
337 // Configuration::Clear - Clear an single value from a list /*{{{*/
338 // ---------------------------------------------------------------------
340 void Configuration::Clear(const string Name
, string Value
)
342 Item
*Top
= Lookup(Name
.c_str(),false);
343 if (Top
== 0 || Top
->Child
== 0)
346 Item
*Tmp
, *Prev
, *I
;
347 Prev
= I
= Top
->Child
;
351 if(I
->Value
== Value
)
354 // was first element, point parent to new first element
355 if(Top
->Child
== Tmp
)
356 Top
->Child
= I
->Next
;
368 // Configuration::Clear - Clear an entire tree /*{{{*/
369 // ---------------------------------------------------------------------
371 void Configuration::Clear(string Name
)
373 Item
*Top
= Lookup(Name
.c_str(),false);
389 while (Top
!= 0 && Top
->Next
== 0)
406 // Configuration::Exists - Returns true if the Name exists /*{{{*/
407 // ---------------------------------------------------------------------
409 bool Configuration::Exists(const char *Name
) const
411 const Item
*Itm
= Lookup(Name
);
417 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
418 // ---------------------------------------------------------------------
419 /* qualified by /[fdbi] exists */
420 bool Configuration::ExistsAny(const char *Name
) const
424 if (key
.size() > 2 && key
.end()[-2] == '/')
426 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
428 key
.resize(key
.size() - 2);
429 if (Exists(key
.c_str()))
434 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
440 // Configuration::Dump - Dump the config /*{{{*/
441 // ---------------------------------------------------------------------
442 /* Dump the entire configuration space */
443 void Configuration::Dump(ostream
& str
)
445 /* Write out all of the configuration directives by walking the
446 configuration tree */
447 const Configuration::Item
*Top
= Tree(0);
450 str
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
458 while (Top
!= 0 && Top
->Next
== 0)
466 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
467 // ---------------------------------------------------------------------
468 /* Stop sets an optional max recursion depth if this item is being viewed as
469 part of a sub tree. */
470 string
Configuration::Item::FullTag(const Item
*Stop
) const
472 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
474 return Parent
->FullTag(Stop
) + "::" + Tag
;
478 // ReadConfigFile - Read a configuration file /*{{{*/
479 // ---------------------------------------------------------------------
480 /* The configuration format is very much like the named.conf format
481 used in bind8, in fact this routine can parse most named.conf files.
482 Sectional config files are like bind's named.conf where there are
483 sections like 'zone "foo.org" { .. };' This causes each section to be
484 added in with a tag like "zone::foo.org" instead of being split
485 tag/value. AsSectional enables Sectional parsing.*/
486 bool ReadConfigFile(Configuration
&Conf
,const string
&FName
,bool AsSectional
,
489 // Open the stream for reading
490 ifstream
F(FName
.c_str(),ios::in
);
492 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
497 unsigned int StackPos
= 0;
503 bool InComment
= false;
504 while (F
.eof() == false)
506 F
.getline(Buffer
,sizeof(Buffer
));
508 // This should be made to work instead, but this is better than looping
509 if (F
.fail() && !F
.eof())
510 return _error
->Error(_("Line %d too long (max %lu)"), CurLine
, sizeof(Buffer
));
512 _strtabexpand(Buffer
,sizeof(Buffer
));
515 // Multi line comment
516 if (InComment
== true)
518 for (const char *I
= Buffer
; *I
!= 0; I
++)
520 if (*I
== '*' && I
[1] == '/')
522 memmove(Buffer
,I
+2,strlen(I
+2) + 1);
527 if (InComment
== true)
531 // Discard single line comments
532 bool InQuote
= false;
533 for (char *I
= Buffer
; *I
!= 0; I
++)
540 if (*I
== '/' && I
[1] == '/')
547 // Look for multi line comments
549 for (char *I
= Buffer
; *I
!= 0; I
++)
556 if (*I
== '/' && I
[1] == '*')
559 for (char *J
= Buffer
; *J
!= 0; J
++)
561 if (*J
== '*' && J
[1] == '/')
563 memmove(I
,J
+2,strlen(J
+2) + 1);
569 if (InComment
== true)
581 // We now have a valid line fragment
583 for (char *I
= Buffer
; *I
!= 0;)
588 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
590 // Put the last fragment into the buffer
591 char *Start
= Buffer
;
593 for (; Start
!= I
&& isspace(*Start
) != 0; Start
++);
594 for (; Stop
!= Start
&& isspace(Stop
[-1]) != 0; Stop
--);
595 if (LineBuffer
.empty() == false && Stop
- Start
!= 0)
597 LineBuffer
+= string(Start
,Stop
- Start
);
599 // Remove the fragment
601 memmove(Buffer
,I
+ 1,strlen(I
+ 1) + 1);
605 if (TermChar
== '{' && LineBuffer
.empty() == true)
606 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
608 // No string on this line
609 if (LineBuffer
.empty() == true)
614 ParentTag
= string();
616 ParentTag
= Stack
[--StackPos
];
623 const char *Pos
= LineBuffer
.c_str();
624 if (ParseQuoteWord(Pos
,Tag
) == false)
625 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
627 // Parse off the word
630 if (ParseCWord(Pos
,Word
) == false &&
631 ParseQuoteWord(Pos
,Word
) == false)
641 if (strlen(Pos
) != 0)
642 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
648 Stack
[StackPos
++] = ParentTag
;
650 /* Make sectional tags incorperate the section into the
652 if (AsSectional
== true && Word
.empty() == false)
659 if (ParentTag
.empty() == true)
662 ParentTag
+= string("::") + Tag
;
666 // Generate the item name
668 if (ParentTag
.empty() == true)
672 if (TermChar
!= '{' || Tag
.empty() == false)
673 Item
= ParentTag
+ "::" + Tag
;
679 if (Tag
.length() >= 1 && Tag
[0] == '#')
681 if (ParentTag
.empty() == false)
682 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
683 Tag
.erase(Tag
.begin());
686 else if (Tag
== "include")
689 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
690 if (Word
.length() > 2 && Word
.end()[-1] == '/')
692 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
693 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
697 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
698 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
702 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
706 // Set the item in the configuration class
714 // Move up a tag, but only if there is no bit to parse
720 ParentTag
= Stack
[--StackPos
];
728 // Store the fragment
729 const char *Stripd
= _strstrip(Buffer
);
730 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
732 LineBuffer
+= Stripd
;
735 if (LineBuffer
.empty() == false)
736 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
740 // ReadConfigDir - Read a directory of config files /*{{{*/
741 // ---------------------------------------------------------------------
743 bool ReadConfigDir(Configuration
&Conf
,const string
&Dir
,bool AsSectional
,
746 DIR *D
= opendir(Dir
.c_str());
748 return _error
->Errno("opendir",_("Unable to read %s"),Dir
.c_str());
752 for (struct dirent
*Ent
= readdir(D
); Ent
!= 0; Ent
= readdir(D
))
754 if (Ent
->d_name
[0] == '.')
757 // Skip bad file names ala run-parts
758 const char *C
= Ent
->d_name
;
760 if (isalpha(*C
) == 0 && isdigit(*C
) == 0 && *C
!= '_' && *C
!= '-')
765 // Make sure it is a file and not something else
766 string File
= flCombine(Dir
,Ent
->d_name
);
768 if (stat(File
.c_str(),&St
) != 0 || S_ISREG(St
.st_mode
) == 0)
771 List
.push_back(File
);
775 sort(List
.begin(),List
.end());
778 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); I
++)
779 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)