]>
git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
4b2c0fbb51977e53bdd21eb4f9ddd01e6c9ab03b
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 /*{{{*/
19 #pragma implementation "apt-pkg/configuration.h"
21 #include <apt-pkg/configuration.h>
22 #include <apt-pkg/error.h>
23 #include <apt-pkg/strutl.h>
24 #include <apt-pkg/fileutl.h>
40 Configuration
*_config
= new Configuration
;
42 // Configuration::Configuration - Constructor /*{{{*/
43 // ---------------------------------------------------------------------
45 Configuration::Configuration() : ToFree(true)
49 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
54 // Configuration::~Configuration - Destructor /*{{{*/
55 // ---------------------------------------------------------------------
57 Configuration::~Configuration()
71 while (Top
!= 0 && Top
->Next
== 0)
73 Item
*Parent
= Top
->Parent
;
79 Item
*Next
= Top
->Next
;
86 // Configuration::Lookup - Lookup a single item /*{{{*/
87 // ---------------------------------------------------------------------
88 /* This will lookup a single item by name below another item. It is a
89 helper function for the main lookup function */
90 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
91 unsigned long Len
,bool Create
)
94 Item
*I
= Head
->Child
;
95 Item
**Last
= &Head
->Child
;
97 // Empty strings match nothing. They are used for lists.
100 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
101 if ((Res
= stringcasecmp(I
->Tag
,S
,S
+ Len
)) == 0)
105 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
113 I
->Tag
= string(S
,Len
);
120 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
121 // ---------------------------------------------------------------------
122 /* This performs a fully scoped lookup of a given name, possibly creating
124 Configuration::Item
*Configuration::Lookup(const char *Name
,bool Create
)
129 const char *Start
= Name
;
130 const char *End
= Start
+ strlen(Name
);
131 const char *TagEnd
= Name
;
133 for (; End
- TagEnd
>= 2; TagEnd
++)
135 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
137 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
140 TagEnd
= Start
= TagEnd
+ 2;
144 // This must be a trailing ::, we create unique items in a list
145 if (End
- Start
== 0)
151 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
155 // Configuration::Find - Find a value /*{{{*/
156 // ---------------------------------------------------------------------
158 string
Configuration::Find(const char *Name
,const char *Default
) const
160 const Item
*Itm
= Lookup(Name
);
161 if (Itm
== 0 || Itm
->Value
.empty() == true)
172 // Configuration::FindFile - Find a Filename /*{{{*/
173 // ---------------------------------------------------------------------
174 /* Directories are stored as the base dir in the Parent node and the
175 sub directory in sub nodes with the final node being the end filename
177 string
Configuration::FindFile(const char *Name
,const char *Default
) const
179 const Item
*Itm
= Lookup(Name
);
180 if (Itm
== 0 || Itm
->Value
.empty() == true)
188 string val
= Itm
->Value
;
189 while (Itm
->Parent
!= 0 && Itm
->Parent
->Value
.empty() == false)
192 if (val
.length() >= 1 && val
[0] == '/')
196 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
200 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
203 if (Itm
->Parent
->Value
.end()[-1] != '/')
206 val
.insert(0, Itm
->Parent
->Value
);
213 // Configuration::FindDir - Find a directory name /*{{{*/
214 // ---------------------------------------------------------------------
215 /* This is like findfile execept the result is terminated in a / */
216 string
Configuration::FindDir(const char *Name
,const char *Default
) const
218 string Res
= FindFile(Name
,Default
);
219 if (Res
.end()[-1] != '/')
224 // Configuration::FindI - Find an integer value /*{{{*/
225 // ---------------------------------------------------------------------
227 int Configuration::FindI(const char *Name
,int Default
) const
229 const Item
*Itm
= Lookup(Name
);
230 if (Itm
== 0 || Itm
->Value
.empty() == true)
234 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
235 if (End
== Itm
->Value
.c_str())
241 // Configuration::FindB - Find a boolean type /*{{{*/
242 // ---------------------------------------------------------------------
244 bool Configuration::FindB(const char *Name
,bool Default
) const
246 const Item
*Itm
= Lookup(Name
);
247 if (Itm
== 0 || Itm
->Value
.empty() == true)
250 return StringToBool(Itm
->Value
,Default
);
253 // Configuration::FindAny - Find an arbitrary type /*{{{*/
254 // ---------------------------------------------------------------------
255 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
256 string
Configuration::FindAny(const char *Name
,const char *Default
) const
261 if (key
.size() > 2 && key
.end()[-2] == '/')
263 type
= key
.end()[-1];
264 key
.resize(key
.size() - 2);
271 return FindFile(key
.c_str(), Default
);
275 return FindDir(key
.c_str(), Default
);
279 return FindB(key
, Default
) ? "true" : "false";
285 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
? atoi(Default
) : 0 ));
291 return Find(Name
, Default
);
294 // Configuration::CndSet - Conditinal Set a value /*{{{*/
295 // ---------------------------------------------------------------------
296 /* This will not overwrite */
297 void Configuration::CndSet(const char *Name
,string Value
)
299 Item
*Itm
= Lookup(Name
,true);
302 if (Itm
->Value
.empty() == true)
306 // Configuration::Set - Set a value /*{{{*/
307 // ---------------------------------------------------------------------
309 void Configuration::Set(const char *Name
,string Value
)
311 Item
*Itm
= Lookup(Name
,true);
317 // Configuration::Set - Set an integer value /*{{{*/
318 // ---------------------------------------------------------------------
320 void Configuration::Set(const char *Name
,int Value
)
322 Item
*Itm
= Lookup(Name
,true);
326 snprintf(S
,sizeof(S
),"%i",Value
);
330 // Configuration::Clear - Clear an single value from a list /*{{{*/
331 // ---------------------------------------------------------------------
333 void Configuration::Clear(string Name
, int Value
)
336 snprintf(S
,sizeof(S
),"%i",Value
);
340 // Configuration::Clear - Clear an single value from a list /*{{{*/
341 // ---------------------------------------------------------------------
343 void Configuration::Clear(string Name
, string Value
)
345 Item
*Top
= Lookup(Name
.c_str(),false);
346 if (Top
== 0 || Top
->Child
== 0)
349 Item
*Tmp
, *Prev
, *I
;
350 Prev
= I
= Top
->Child
;
354 if(I
->Value
== Value
)
357 // was first element, point parent to new first element
358 if(Top
->Child
== Tmp
)
359 Top
->Child
= I
->Next
;
371 // Configuration::Clear - Clear an entire tree /*{{{*/
372 // ---------------------------------------------------------------------
374 void Configuration::Clear(string Name
)
376 Item
*Top
= Lookup(Name
.c_str(),false);
378 cout
<< "config item: " << Name
<< " not found" << endl
;
381 Top
->Value
= string();
393 while (Top
!= 0 && Top
->Next
== 0)
410 // Configuration::Exists - Returns true if the Name exists /*{{{*/
411 // ---------------------------------------------------------------------
413 bool Configuration::Exists(const char *Name
) const
415 const Item
*Itm
= Lookup(Name
);
421 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
422 // ---------------------------------------------------------------------
423 /* qualified by /[fdbi] exists */
424 bool Configuration::ExistsAny(const char *Name
) const
428 if (key
.size() > 2 && key
.end()[-2] == '/')
429 if (key
.find_first_of("fdbi",key
.size()-1) < key
.size())
431 key
.resize(key
.size() - 2);
432 if (Exists(key
.c_str()))
437 _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]);
443 // Configuration::Dump - Dump the config /*{{{*/
444 // ---------------------------------------------------------------------
445 /* Dump the entire configuration space */
446 void Configuration::Dump(ostream
& str
)
448 /* Write out all of the configuration directives by walking the
449 configuration tree */
450 const Configuration::Item
*Top
= Tree(0);
453 str
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
461 while (Top
!= 0 && Top
->Next
== 0)
469 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
470 // ---------------------------------------------------------------------
471 /* Stop sets an optional max recursion depth if this item is being viewed as
472 part of a sub tree. */
473 string
Configuration::Item::FullTag(const Item
*Stop
) const
475 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
477 return Parent
->FullTag(Stop
) + "::" + Tag
;
481 // ReadConfigFile - Read a configuration file /*{{{*/
482 // ---------------------------------------------------------------------
483 /* The configuration format is very much like the named.conf format
484 used in bind8, in fact this routine can parse most named.conf files.
485 Sectional config files are like bind's named.conf where there are
486 sections like 'zone "foo.org" { .. };' This causes each section to be
487 added in with a tag like "zone::foo.org" instead of being split
488 tag/value. AsSectional enables Sectional parsing.*/
489 bool ReadConfigFile(Configuration
&Conf
,string FName
,bool AsSectional
,
492 // Open the stream for reading
493 ifstream
F(FName
.c_str(),ios::in
);
495 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
500 unsigned int StackPos
= 0;
506 bool InComment
= false;
507 while (F
.eof() == false)
509 F
.getline(Buffer
,sizeof(Buffer
));
511 // This should be made to work instead, but this is better than looping
512 if (F
.fail() && !F
.eof())
513 return _error
->Error(_("Line %d too long (max %d)"), CurLine
, sizeof(Buffer
));
515 _strtabexpand(Buffer
,sizeof(Buffer
));
518 // Multi line comment
519 if (InComment
== true)
521 for (const char *I
= Buffer
; *I
!= 0; I
++)
523 if (*I
== '*' && I
[1] == '/')
525 memmove(Buffer
,I
+2,strlen(I
+2) + 1);
530 if (InComment
== true)
534 // Discard single line comments
535 bool InQuote
= false;
536 for (char *I
= Buffer
; *I
!= 0; I
++)
543 if (*I
== '/' && I
[1] == '/')
550 // Look for multi line comments
552 for (char *I
= Buffer
; *I
!= 0; I
++)
559 if (*I
== '/' && I
[1] == '*')
562 for (char *J
= Buffer
; *J
!= 0; J
++)
564 if (*J
== '*' && J
[1] == '/')
566 memmove(I
,J
+2,strlen(J
+2) + 1);
572 if (InComment
== true)
584 // We now have a valid line fragment
586 for (char *I
= Buffer
; *I
!= 0;)
591 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
593 // Put the last fragment into the buffer
594 char *Start
= Buffer
;
596 for (; Start
!= I
&& isspace(*Start
) != 0; Start
++);
597 for (; Stop
!= Start
&& isspace(Stop
[-1]) != 0; Stop
--);
598 if (LineBuffer
.empty() == false && Stop
- Start
!= 0)
600 LineBuffer
+= string(Start
,Stop
- Start
);
602 // Remove the fragment
604 memmove(Buffer
,I
+ 1,strlen(I
+ 1) + 1);
608 if (TermChar
== '{' && LineBuffer
.empty() == true)
609 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
611 // No string on this line
612 if (LineBuffer
.empty() == true)
617 ParentTag
= string();
619 ParentTag
= Stack
[--StackPos
];
626 const char *Pos
= LineBuffer
.c_str();
627 if (ParseQuoteWord(Pos
,Tag
) == false)
628 return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
);
630 // Parse off the word
633 if (ParseCWord(Pos
,Word
) == false &&
634 ParseQuoteWord(Pos
,Word
) == false)
644 if (strlen(Pos
) != 0)
645 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
651 Stack
[StackPos
++] = ParentTag
;
653 /* Make sectional tags incorperate the section into the
655 if (AsSectional
== true && Word
.empty() == false)
662 if (ParentTag
.empty() == true)
665 ParentTag
+= string("::") + Tag
;
669 // Generate the item name
671 if (ParentTag
.empty() == true)
675 if (TermChar
!= '{' || Tag
.empty() == false)
676 Item
= ParentTag
+ "::" + Tag
;
682 if (Tag
.length() >= 1 && Tag
[0] == '#')
684 if (ParentTag
.empty() == false)
685 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
686 Tag
.erase(Tag
.begin());
689 else if (Tag
== "include")
692 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
693 if (Word
.length() > 2 && Word
.end()[-1] == '/')
695 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
696 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
700 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
701 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
705 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
709 // Set the item in the configuration class
715 LineBuffer
= string();
717 // Move up a tag, but only if there is no bit to parse
721 ParentTag
= string();
723 ParentTag
= Stack
[--StackPos
];
731 // Store the fragment
732 const char *Stripd
= _strstrip(Buffer
);
733 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
735 LineBuffer
+= Stripd
;
738 if (LineBuffer
.empty() == false)
739 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
743 // ReadConfigDir - Read a directory of config files /*{{{*/
744 // ---------------------------------------------------------------------
746 bool ReadConfigDir(Configuration
&Conf
,string Dir
,bool AsSectional
,
749 DIR *D
= opendir(Dir
.c_str());
751 return _error
->Errno("opendir",_("Unable to read %s"),Dir
.c_str());
755 for (struct dirent
*Ent
= readdir(D
); Ent
!= 0; Ent
= readdir(D
))
757 if (Ent
->d_name
[0] == '.')
760 // Skip bad file names ala run-parts
761 const char *C
= Ent
->d_name
;
763 if (isalpha(*C
) == 0 && isdigit(*C
) == 0 && *C
!= '_' && *C
!= '-')
768 // Make sure it is a file and not something else
769 string File
= flCombine(Dir
,Ent
->d_name
);
771 if (stat(File
.c_str(),&St
) != 0 || S_ISREG(St
.st_mode
) == 0)
774 List
.push_back(File
);
778 sort(List
.begin(),List
.end());
781 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); I
++)
782 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)