]>
git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: configuration.cc,v 1.22 2001/05/14 05:47:30 jgg Exp $
4 /* ######################################################################
8 This class provides a configuration file and command line parser
9 for a tree-oriented configuration environment. All runtime configuration
12 ##################################################################### */
14 // Include files /*{{{*/
16 #pragma implementation "apt-pkg/configuration.h"
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
= string(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
, atoi(Default
)));
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
,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
,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 entire tree /*{{{*/
328 // ---------------------------------------------------------------------
330 void Configuration::Clear(string Name
)
332 Item
*Top
= Lookup(Name
.c_str(),false);
336 Top
->Value
= string();
348 while (Top
!= 0 && Top
->Next
== 0)
365 // Configuration::Exists - Returns true if the Name exists /*{{{*/
366 // ---------------------------------------------------------------------
368 bool Configuration::Exists(const char *Name
) const
370 const Item
*Itm
= Lookup(Name
);
376 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
377 // ---------------------------------------------------------------------
378 /* qualified by /[fdbi] exists */
379 bool Configuration::ExistsAny(const char *Name
) const
383 if (key
.size() > 2 && key
.end()[-2] == '/' &&
384 key
.find_first_of("fdbi",key
.size()-1) < key
.size())
386 key
.resize(key
.size() - 2);
387 if (Exists(key
.c_str()))
394 // Configuration::Dump - Dump the config /*{{{*/
395 // ---------------------------------------------------------------------
396 /* Dump the entire configuration space */
397 void Configuration::Dump()
399 /* Write out all of the configuration directives by walking the
400 configuration tree */
401 const Configuration::Item
*Top
= Tree(0);
404 clog
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
412 while (Top
!= 0 && Top
->Next
== 0)
420 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
421 // ---------------------------------------------------------------------
422 /* Stop sets an optional max recursion depth if this item is being viewed as
423 part of a sub tree. */
424 string
Configuration::Item::FullTag(const Item
*Stop
) const
426 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
428 return Parent
->FullTag(Stop
) + "::" + Tag
;
432 // ReadConfigFile - Read a configuration file /*{{{*/
433 // ---------------------------------------------------------------------
434 /* The configuration format is very much like the named.conf format
435 used in bind8, in fact this routine can parse most named.conf files.
436 Sectional config files are like bind's named.conf where there are
437 sections like 'zone "foo.org" { .. };' This causes each section to be
438 added in with a tag like "zone::foo.org" instead of being split
439 tag/value. AsSectional enables Sectional parsing.*/
440 bool ReadConfigFile(Configuration
&Conf
,string FName
,bool AsSectional
,
443 // Open the stream for reading
444 ifstream
F(FName
.c_str(),ios::in
);
446 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
451 unsigned int StackPos
= 0;
457 bool InComment
= false;
458 while (F
.eof() == false)
460 F
.getline(Buffer
,sizeof(Buffer
));
462 _strtabexpand(Buffer
,sizeof(Buffer
));
465 // Multi line comment
466 if (InComment
== true)
468 for (const char *I
= Buffer
; *I
!= 0; I
++)
470 if (*I
== '*' && I
[1] == '/')
472 memmove(Buffer
,I
+2,strlen(I
+2) + 1);
477 if (InComment
== true)
481 // Discard single line comments
482 bool InQuote
= false;
483 for (char *I
= Buffer
; *I
!= 0; I
++)
490 if (*I
== '/' && I
[1] == '/')
497 // Look for multi line comments
499 for (char *I
= Buffer
; *I
!= 0; I
++)
506 if (*I
== '/' && I
[1] == '*')
509 for (char *J
= Buffer
; *J
!= 0; J
++)
511 if (*J
== '*' && J
[1] == '/')
513 memmove(I
,J
+2,strlen(J
+2) + 1);
519 if (InComment
== true)
531 // We now have a valid line fragment
533 for (char *I
= Buffer
; *I
!= 0;)
538 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
540 // Put the last fragment into the buffer
541 char *Start
= Buffer
;
543 for (; Start
!= I
&& isspace(*Start
) != 0; Start
++);
544 for (; Stop
!= Start
&& isspace(Stop
[-1]) != 0; Stop
--);
545 if (LineBuffer
.empty() == false && Stop
- Start
!= 0)
547 LineBuffer
+= string(Start
,Stop
- Start
);
549 // Remove the fragment
551 memmove(Buffer
,I
+ 1,strlen(I
+ 1) + 1);
555 if (TermChar
== '{' && LineBuffer
.empty() == true)
556 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
558 // No string on this line
559 if (LineBuffer
.empty() == true)
564 ParentTag
= string();
566 ParentTag
= Stack
[--StackPos
];
573 const char *Pos
= LineBuffer
.c_str();
574 if (ParseQuoteWord(Pos
,Tag
) == false)
575 return _error
->Error(_("Syntax error %s:%u: Malformed Tag"),FName
.c_str(),CurLine
);
577 // Parse off the word
580 if (ParseCWord(Pos
,Word
) == false &&
581 ParseQuoteWord(Pos
,Word
) == false)
591 if (strlen(Pos
) != 0)
592 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
598 Stack
[StackPos
++] = ParentTag
;
600 /* Make sectional tags incorperate the section into the
602 if (AsSectional
== true && Word
.empty() == false)
609 if (ParentTag
.empty() == true)
612 ParentTag
+= string("::") + Tag
;
616 // Generate the item name
618 if (ParentTag
.empty() == true)
622 if (TermChar
!= '{' || Tag
.empty() == false)
623 Item
= ParentTag
+ "::" + Tag
;
629 if (Tag
.length() >= 1 && Tag
[0] == '#')
631 if (ParentTag
.empty() == false)
632 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
633 Tag
.erase(Tag
.begin());
636 else if (Tag
== "include")
639 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
640 if (Word
.length() > 2 && Word
.end()[-1] == '/')
642 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
643 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
647 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
648 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
652 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
656 // Set the item in the configuration class
662 LineBuffer
= string();
664 // Move up a tag, but only if there is no bit to parse
668 ParentTag
= string();
670 ParentTag
= Stack
[--StackPos
];
678 // Store the fragment
679 const char *Stripd
= _strstrip(Buffer
);
680 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
682 LineBuffer
+= Stripd
;
685 if (LineBuffer
.empty() == false)
686 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
690 // ReadConfigDir - Read a directory of config files /*{{{*/
691 // ---------------------------------------------------------------------
693 bool ReadConfigDir(Configuration
&Conf
,string Dir
,bool AsSectional
,
696 DIR *D
= opendir(Dir
.c_str());
698 return _error
->Errno("opendir",_("Unable to read %s"),Dir
.c_str());
702 for (struct dirent
*Ent
= readdir(D
); Ent
!= 0; Ent
= readdir(D
))
704 if (Ent
->d_name
[0] == '.')
707 // Skip bad file names ala run-parts
708 const char *C
= Ent
->d_name
;
710 if (isalpha(*C
) == 0 && isdigit(*C
) == 0 && *C
!= '_' && *C
!= '-')
715 // Make sure it is a file and not something else
716 string File
= flCombine(Dir
,Ent
->d_name
);
718 if (stat(File
.c_str(),&St
) != 0 || S_ISREG(St
.st_mode
) == 0)
721 List
.push_back(File
);
725 sort(List
.begin(),List
.end());
728 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); I
++)
729 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)