]>
git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
1 // -*- mode: cpp; mode: fold -*-
3 // $Id: configuration.cc,v 1.15 2001/02/20 07:03:17 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>
34 Configuration
*_config
= new Configuration
;
36 // Configuration::Configuration - Constructor /*{{{*/
37 // ---------------------------------------------------------------------
39 Configuration::Configuration() : ToFree(true)
43 Configuration::Configuration(const Item
*Root
) : Root((Item
*)Root
), ToFree(false)
48 // Configuration::~Configuration - Destructor /*{{{*/
49 // ---------------------------------------------------------------------
51 Configuration::~Configuration()
65 while (Top
!= 0 && Top
->Next
== 0)
67 Item
*Parent
= Top
->Parent
;
73 Item
*Next
= Top
->Next
;
80 // Configuration::Lookup - Lookup a single item /*{{{*/
81 // ---------------------------------------------------------------------
82 /* This will lookup a single item by name below another item. It is a
83 helper function for the main lookup function */
84 Configuration::Item
*Configuration::Lookup(Item
*Head
,const char *S
,
85 unsigned long Len
,bool Create
)
88 Item
*I
= Head
->Child
;
89 Item
**Last
= &Head
->Child
;
91 // Empty strings match nothing. They are used for lists.
94 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
)
95 if ((Res
= stringcasecmp(I
->Tag
.begin(),I
->Tag
.end(),S
,S
+ Len
)) == 0)
99 for (; I
!= 0; Last
= &I
->Next
, I
= I
->Next
);
107 I
->Tag
= string(S
,Len
);
114 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
115 // ---------------------------------------------------------------------
116 /* This performs a fully scoped lookup of a given name, possibly creating
118 Configuration::Item
*Configuration::Lookup(const char *Name
,bool Create
)
123 const char *Start
= Name
;
124 const char *End
= Start
+ strlen(Name
);
125 const char *TagEnd
= Name
;
127 for (; End
- TagEnd
>= 2; TagEnd
++)
129 if (TagEnd
[0] == ':' && TagEnd
[1] == ':')
131 Itm
= Lookup(Itm
,Start
,TagEnd
- Start
,Create
);
134 TagEnd
= Start
= TagEnd
+ 2;
138 // This must be a trailing ::, we create unique items in a list
139 if (End
- Start
== 0)
145 Itm
= Lookup(Itm
,Start
,End
- Start
,Create
);
149 // Configuration::Find - Find a value /*{{{*/
150 // ---------------------------------------------------------------------
152 string
Configuration::Find(const char *Name
,const char *Default
) const
154 const Item
*Itm
= Lookup(Name
);
155 if (Itm
== 0 || Itm
->Value
.empty() == true)
166 // Configuration::FindFile - Find a Filename /*{{{*/
167 // ---------------------------------------------------------------------
168 /* Directories are stored as the base dir in the Parent node and the
169 sub directory in sub nodes with the final node being the end filename
171 string
Configuration::FindFile(const char *Name
,const char *Default
) const
173 const Item
*Itm
= Lookup(Name
);
174 if (Itm
== 0 || Itm
->Value
.empty() == true)
182 string val
= Itm
->Value
;
183 while (Itm
->Parent
!= 0 && Itm
->Parent
->Value
.empty() == false)
186 if (val
.length() >= 1 && val
[0] == '/')
190 if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/')
194 if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/')
197 if (Itm
->Parent
->Value
.end()[-1] != '/')
200 val
.insert(0, Itm
->Parent
->Value
);
207 // Configuration::FindDir - Find a directory name /*{{{*/
208 // ---------------------------------------------------------------------
209 /* This is like findfile execept the result is terminated in a / */
210 string
Configuration::FindDir(const char *Name
,const char *Default
) const
212 string Res
= FindFile(Name
,Default
);
213 if (Res
.end()[-1] != '/')
218 // Configuration::FindI - Find an integer value /*{{{*/
219 // ---------------------------------------------------------------------
221 int Configuration::FindI(const char *Name
,int Default
) const
223 const Item
*Itm
= Lookup(Name
);
224 if (Itm
== 0 || Itm
->Value
.empty() == true)
228 int Res
= strtol(Itm
->Value
.c_str(),&End
,0);
229 if (End
== Itm
->Value
.c_str())
235 // Configuration::FindB - Find a boolean type /*{{{*/
236 // ---------------------------------------------------------------------
238 bool Configuration::FindB(const char *Name
,bool Default
) const
240 const Item
*Itm
= Lookup(Name
);
241 if (Itm
== 0 || Itm
->Value
.empty() == true)
244 return StringToBool(Itm
->Value
,Default
);
247 // Configuration::FindAny - Find an arbitrary type /*{{{*/
248 // ---------------------------------------------------------------------
249 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
250 string
Configuration::FindAny(const char *Name
,const char *Default
) const
255 if (key
.size() > 2 && key
.end()[-2] == '/')
257 type
= key
.end()[-1];
258 key
.resize(key
.size() - 2);
265 return FindFile(key
.c_str(), Default
);
269 return FindDir(key
.c_str(), Default
);
273 return FindB(key
, Default
) ? "true" : "false";
279 snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default
));
285 return Find(Name
, Default
);
288 // Configuration::CndSet - Conditinal Set a value /*{{{*/
289 // ---------------------------------------------------------------------
290 /* This will not overwrite */
291 void Configuration::CndSet(const char *Name
,string Value
)
293 Item
*Itm
= Lookup(Name
,true);
296 if (Itm
->Value
.empty() == true)
300 // Configuration::Set - Set a value /*{{{*/
301 // ---------------------------------------------------------------------
303 void Configuration::Set(const char *Name
,string Value
)
305 Item
*Itm
= Lookup(Name
,true);
311 // Configuration::Set - Set an integer value /*{{{*/
312 // ---------------------------------------------------------------------
314 void Configuration::Set(const char *Name
,int Value
)
316 Item
*Itm
= Lookup(Name
,true);
320 snprintf(S
,sizeof(S
),"%i",Value
);
324 // Configuration::Clear - Clear an entire tree /*{{{*/
325 // ---------------------------------------------------------------------
327 void Configuration::Clear(string Name
)
329 Item
*Top
= Lookup(Name
.c_str(),false);
333 Top
->Value
= string();
345 while (Top
!= 0 && Top
->Next
== 0)
362 // Configuration::Exists - Returns true if the Name exists /*{{{*/
363 // ---------------------------------------------------------------------
365 bool Configuration::Exists(const char *Name
) const
367 const Item
*Itm
= Lookup(Name
);
373 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
374 // ---------------------------------------------------------------------
375 /* qualified by /[fdbi] exists */
376 bool Configuration::ExistsAny(const char *Name
) const
380 if (key
.size() > 2 && key
.end()[-2] == '/' &&
381 key
.find_first_of("fdbi",key
.size()-1) < key
.size())
383 key
.resize(key
.size() - 2);
384 if (Exists(key
.c_str()))
391 // Configuration::Dump - Dump the config /*{{{*/
392 // ---------------------------------------------------------------------
393 /* Dump the entire configuration space */
394 void Configuration::Dump()
396 /* Write out all of the configuration directives by walking the
397 configuration tree */
398 const Configuration::Item
*Top
= Tree(0);
401 clog
<< Top
->FullTag() << " \"" << Top
->Value
<< "\";" << endl
;
409 while (Top
!= 0 && Top
->Next
== 0)
417 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
418 // ---------------------------------------------------------------------
419 /* Stop sets an optional max recursion depth if this item is being viewed as
420 part of a sub tree. */
421 string
Configuration::Item::FullTag(const Item
*Stop
) const
423 if (Parent
== 0 || Parent
->Parent
== 0 || Parent
== Stop
)
425 return Parent
->FullTag(Stop
) + "::" + Tag
;
429 // ReadConfigFile - Read a configuration file /*{{{*/
430 // ---------------------------------------------------------------------
431 /* The configuration format is very much like the named.conf format
432 used in bind8, in fact this routine can parse most named.conf files.
433 Sectional config files are like bind's named.conf where there are
434 sections like 'zone "foo.org" { .. };' This causes each section to be
435 added in with a tag like "zone::foo.org" instead of being split
437 bool ReadConfigFile(Configuration
&Conf
,string FName
,bool AsSectional
,
440 // Open the stream for reading
441 ifstream
F(FName
.c_str(),ios::in
| ios::nocreate
);
443 return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str());
448 unsigned int StackPos
= 0;
454 bool InComment
= false;
455 while (F
.eof() == false)
457 F
.getline(Buffer
,sizeof(Buffer
));
459 _strtabexpand(Buffer
,sizeof(Buffer
));
462 // Multi line comment
463 if (InComment
== true)
465 for (const char *I
= Buffer
; *I
!= 0; I
++)
467 if (*I
== '*' && I
[1] == '/')
469 memmove(Buffer
,I
+2,strlen(I
+2) + 1);
474 if (InComment
== true)
478 // Discard single line comments
479 bool InQuote
= false;
480 for (char *I
= Buffer
; *I
!= 0; I
++)
487 if (*I
== '/' && I
[1] == '/')
494 // Look for multi line comments
496 for (char *I
= Buffer
; *I
!= 0; I
++)
503 if (*I
== '/' && I
[1] == '*')
506 for (char *J
= Buffer
; *J
!= 0; J
++)
508 if (*J
== '*' && J
[1] == '/')
510 memmove(I
,J
+2,strlen(J
+2) + 1);
516 if (InComment
== true)
528 // We now have a valid line fragment
530 for (char *I
= Buffer
; *I
!= 0;)
535 if (InQuote
== false && (*I
== '{' || *I
== ';' || *I
== '}'))
537 // Put the last fragment into the buffer
538 char *Start
= Buffer
;
540 for (; Start
!= I
&& isspace(*Start
) != 0; Start
++);
541 for (; Stop
!= Start
&& isspace(Stop
[-1]) != 0; Stop
--);
542 if (LineBuffer
.empty() == false && Stop
- Start
!= 0)
544 LineBuffer
+= string(Start
,Stop
- Start
);
546 // Remove the fragment
548 memmove(Buffer
,I
+ 1,strlen(I
+ 1) + 1);
552 if (TermChar
== '{' && LineBuffer
.empty() == true)
553 return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
);
555 // No string on this line
556 if (LineBuffer
.empty() == true)
561 ParentTag
= string();
563 ParentTag
= Stack
[--StackPos
];
570 const char *Pos
= LineBuffer
.c_str();
571 if (ParseQuoteWord(Pos
,Tag
) == false)
572 return _error
->Error(_("Syntax error %s:%u: Malformed Tag"),FName
.c_str(),CurLine
);
574 // Parse off the word
576 if (ParseCWord(Pos
,Word
) == false &&
577 ParseQuoteWord(Pos
,Word
) == false)
585 if (strlen(Pos
) != 0)
586 return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
);
592 Stack
[StackPos
++] = ParentTag
;
594 /* Make sectional tags incorperate the section into the
596 if (AsSectional
== true && Word
.empty() == false)
603 if (ParentTag
.empty() == true)
606 ParentTag
+= string("::") + Tag
;
610 // Generate the item name
612 if (ParentTag
.empty() == true)
616 if (TermChar
!= '{' || Tag
.empty() == false)
617 Item
= ParentTag
+ "::" + Tag
;
623 if (Tag
.length() >= 1 && Tag
[0] == '#')
625 if (ParentTag
.empty() == false)
626 return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
);
627 Tag
.erase(Tag
.begin());
630 else if (Tag
== "include")
633 return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
);
634 if (Word
.length() > 2 && Word
.end()[-1] == '/')
636 if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false)
637 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
641 if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false)
642 return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
);
646 return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str());
650 // Set the item in the configuration class
655 LineBuffer
= string();
657 // Move up a tag, but only if there is no bit to parse
661 ParentTag
= string();
663 ParentTag
= Stack
[--StackPos
];
671 // Store the fragment
672 const char *Stripd
= _strstrip(Buffer
);
673 if (*Stripd
!= 0 && LineBuffer
.empty() == false)
675 LineBuffer
+= Stripd
;
678 if (LineBuffer
.empty() == false)
679 return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
);
683 // ReadConfigDir - Read a directory of config files /*{{{*/
684 // ---------------------------------------------------------------------
686 bool ReadConfigDir(Configuration
&Conf
,string Dir
,bool AsSectional
,
689 static const char *BadExts
[] = {".disabled",".dpkg-old",".dpkg-dist",
690 ".rpmsave",".rpmorig","~",",v",0};
692 DIR *D
= opendir(Dir
.c_str());
694 return _error
->Errno("opendir",_("Unable to read %s"),Dir
.c_str());
698 for (struct dirent
*Ent
= readdir(D
); Ent
!= 0; Ent
= readdir(D
))
700 if (strcmp(Ent
->d_name
,".") == 0 ||
701 strcmp(Ent
->d_name
,"..") == 0)
704 // Skip bad extensions
706 for (I
= BadExts
; *I
!= 0; I
++)
708 if (strcmp(Ent
->d_name
+ strlen(Ent
->d_name
) - strlen(*I
),*I
) == 0)
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)