]>
git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: configuration.cc,v 1.25 2002/11/09 19:52:03 doogie 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 entire tree /*{{{*/
331 // ---------------------------------------------------------------------
333 void Configuration::Clear(string Name
)
335 Item
*Top
= Lookup(Name
.c_str(),false);
339 Top
->Value
= string();
351 while (Top
!= 0 && Top
->Next
== 0)
368 // Configuration::Exists - Returns true if the Name exists /*{{{*/
369 // ---------------------------------------------------------------------
371 bool Configuration::Exists(const char *Name
) const
373 const Item
*Itm
= Lookup(Name
);
379 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
380 // ---------------------------------------------------------------------
381 /* qualified by /[fdbi] exists */
382 bool Configuration::ExistsAny(const char *Name
) const
386 if (key
.size() > 2 && key
.end()[-2] == '/' &&
387 key
.find_first_of("fdbi",key
.size()-1) < key
.size())
389 key
.resize(key
.size() - 2);
390 if (Exists(key
.c_str()))
397 // Configuration::Dump - Dump the config /*{{{*/
398 // ---------------------------------------------------------------------
399 /* Dump the entire configuration space */
400 void Configuration::Dump(ostream
& str
)
402 /* Write out all of the configuration directives by walking the
403 configuration tree */
404 const Configuration::Item
*Top
= Tree(0);
407 str
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
415 while (Top
!= 0 && Top
->Next
== 0)
423 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
424 // ---------------------------------------------------------------------
425 /* Stop sets an optional max recursion depth if this item is being viewed as
426 part of a sub tree. */
427 string
Configuration::Item::FullTag(const Item
*Stop
) const
429 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
431 return Parent
->FullTag(Stop
) + "::" + Tag
;
435 // ReadConfigFile - Read a configuration file /*{{{*/
436 // ---------------------------------------------------------------------
437 /* The configuration format is very much like the named.conf format
438 used in bind8, in fact this routine can parse most named.conf files.
439 Sectional config files are like bind's named.conf where there are
440 sections like 'zone "foo.org" { .. };' This causes each section to be
441 added in with a tag like "zone::foo.org" instead of being split
442 tag/value. AsSectional enables Sectional parsing.*/
443 bool ReadConfigFile(Configuration
&Conf
,string FName
,bool AsSectional
,
446 // Open the stream for reading
447 ifstream
F(FName
.c_str(),ios::in
);
449 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
454 unsigned int StackPos
= 0;
460 bool InComment
= false;
461 while (F
.eof() == false)
463 F
.getline(Buffer
,sizeof(Buffer
));
465 _strtabexpand(Buffer
,sizeof(Buffer
));
468 // Multi line comment
469 if (InComment
== true)
471 for (const char *I
= Buffer
; *I
!= 0; I
++)
473 if (*I
== '*' && I
[1] == '/')
475 memmove(Buffer
,I
+2,strlen(I
+2) + 1);
480 if (InComment
== true)
484 // Discard single line comments
485 bool InQuote
= false;
486 for (char *I
= Buffer
; *I
!= 0; I
++)
493 if (*I
== '/' && I
[1] == '/')
500 // Look for multi line comments
502 for (char *I
= Buffer
; *I
!= 0; I
++)
509 if (*I
== '/' && I
[1] == '*')
512 for (char *J
= Buffer
; *J
!= 0; J
++)
514 if (*J
== '*' && J
[1] == '/')
516 memmove(I
,J
+2,strlen(J
+2) + 1);
522 if (InComment
== true)
534 // We now have a valid line fragment
536 for (char *I
= Buffer
; *I
!= 0;)
541 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
543 // Put the last fragment into the buffer
544 char *Start
= Buffer
;
546 for (; Start
!= I
&& isspace(*Start
) != 0; Start
++);
547 for (; Stop
!= Start
&& isspace(Stop
[-1]) != 0; Stop
--);
548 if (LineBuffer
.empty() == false && Stop
- Start
!= 0)
550 LineBuffer
+= string(Start
,Stop
- Start
);
552 // Remove the fragment
554 memmove(Buffer
,I
+ 1,strlen(I
+ 1) + 1);
558 if (TermChar
== '{' && LineBuffer
.empty() == true)
559 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
561 // No string on this line
562 if (LineBuffer
.empty() == true)
567 ParentTag
= string();
569 ParentTag
= Stack
[--StackPos
];
576 const char *Pos
= LineBuffer
.c_str();
577 if (ParseQuoteWord(Pos
,Tag
) == false)
578 return _error
->Error(_("Syntax error %s:%u: Malformed Tag"),FName
.c_str(),CurLine
);
580 // Parse off the word
583 if (ParseCWord(Pos
,Word
) == false &&
584 ParseQuoteWord(Pos
,Word
) == false)
594 if (strlen(Pos
) != 0)
595 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
601 Stack
[StackPos
++] = ParentTag
;
603 /* Make sectional tags incorperate the section into the
605 if (AsSectional
== true && Word
.empty() == false)
612 if (ParentTag
.empty() == true)
615 ParentTag
+= string("::") + Tag
;
619 // Generate the item name
621 if (ParentTag
.empty() == true)
625 if (TermChar
!= '{' || Tag
.empty() == false)
626 Item
= ParentTag
+ "::" + Tag
;
632 if (Tag
.length() >= 1 && Tag
[0] == '#')
634 if (ParentTag
.empty() == false)
635 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
636 Tag
.erase(Tag
.begin());
639 else if (Tag
== "include")
642 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
643 if (Word
.length() > 2 && Word
.end()[-1] == '/')
645 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
646 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
650 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
651 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
655 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
659 // Set the item in the configuration class
665 LineBuffer
= string();
667 // Move up a tag, but only if there is no bit to parse
671 ParentTag
= string();
673 ParentTag
= Stack
[--StackPos
];
681 // Store the fragment
682 const char *Stripd
= _strstrip(Buffer
);
683 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
685 LineBuffer
+= Stripd
;
688 if (LineBuffer
.empty() == false)
689 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
693 // ReadConfigDir - Read a directory of config files /*{{{*/
694 // ---------------------------------------------------------------------
696 bool ReadConfigDir(Configuration
&Conf
,string Dir
,bool AsSectional
,
699 DIR *D
= opendir(Dir
.c_str());
701 return _error
->Errno("opendir",_("Unable to read %s"),Dir
.c_str());
705 for (struct dirent
*Ent
= readdir(D
); Ent
!= 0; Ent
= readdir(D
))
707 if (Ent
->d_name
[0] == '.')
710 // Skip bad file names ala run-parts
711 const char *C
= Ent
->d_name
;
713 if (isalpha(*C
) == 0 && isdigit(*C
) == 0 && *C
!= '_' && *C
!= '-')
718 // Make sure it is a file and not something else
719 string File
= flCombine(Dir
,Ent
->d_name
);
721 if (stat(File
.c_str(),&St
) != 0 || S_ISREG(St
.st_mode
) == 0)
724 List
.push_back(File
);
728 sort(List
.begin(),List
.end());
731 for (vector
<string
>::const_iterator I
= List
.begin(); I
!= List
.end(); I
++)
732 if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false)