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> 
  31 Configuration 
*_config 
= new Configuration
; 
  33 // Configuration::Configuration - Constructor                           /*{{{*/ 
  34 // --------------------------------------------------------------------- 
  36 Configuration::Configuration() : ToFree(true) 
  40 Configuration::Configuration(const Item 
*Root
) : Root((Item 
*)Root
), ToFree(false) 
  45 // Configuration::~Configuration - Destructor                           /*{{{*/ 
  46 // --------------------------------------------------------------------- 
  48 Configuration::~Configuration() 
  62       while (Top 
!= 0 && Top
->Next 
== 0) 
  64          Item 
*Parent 
= Top
->Parent
; 
  70          Item 
*Next 
= Top
->Next
; 
  77 // Configuration::Lookup - Lookup a single item                         /*{{{*/ 
  78 // --------------------------------------------------------------------- 
  79 /* This will lookup a single item by name below another item. It is a  
  80    helper function for the main lookup function */ 
  81 Configuration::Item 
*Configuration::Lookup(Item 
*Head
,const char *S
, 
  82                                            unsigned long const &Len
,bool const &Create
) 
  85    Item 
*I 
= Head
->Child
; 
  86    Item 
**Last 
= &Head
->Child
; 
  88    // Empty strings match nothing. They are used for lists. 
  91       for (; I 
!= 0; Last 
= &I
->Next
, I 
= I
->Next
) 
  92          if ((Res 
= stringcasecmp(I
->Tag
,S
,S 
+ Len
)) == 0) 
  96       for (; I 
!= 0; Last 
= &I
->Next
, I 
= I
->Next
); 
 104    I
->Tag
.assign(S
,Len
); 
 111 // Configuration::Lookup - Lookup a fully scoped item                   /*{{{*/ 
 112 // --------------------------------------------------------------------- 
 113 /* This performs a fully scoped lookup of a given name, possibly creating 
 115 Configuration::Item 
*Configuration::Lookup(const char *Name
,bool const &Create
) 
 120    const char *Start 
= Name
; 
 121    const char *End 
= Start 
+ strlen(Name
); 
 122    const char *TagEnd 
= Name
; 
 124    for (; End 
- TagEnd 
>= 2; TagEnd
++) 
 126       if (TagEnd
[0] == ':' && TagEnd
[1] == ':') 
 128          Itm 
= Lookup(Itm
,Start
,TagEnd 
- Start
,Create
); 
 131          TagEnd 
= Start 
= TagEnd 
+ 2;     
 135    // This must be a trailing ::, we create unique items in a list 
 136    if (End 
- Start 
== 0) 
 142    Itm 
= Lookup(Itm
,Start
,End 
- Start
,Create
); 
 146 // Configuration::Find - Find a value                                   /*{{{*/ 
 147 // --------------------------------------------------------------------- 
 149 string 
Configuration::Find(const char *Name
,const char *Default
) const 
 151    const Item 
*Itm 
= Lookup(Name
); 
 152    if (Itm 
== 0 || Itm
->Value
.empty() == true) 
 163 // Configuration::FindFile - Find a Filename                            /*{{{*/ 
 164 // --------------------------------------------------------------------- 
 165 /* Directories are stored as the base dir in the Parent node and the 
 166    sub directory in sub nodes with the final node being the end filename 
 168 string 
Configuration::FindFile(const char *Name
,const char *Default
) const 
 170    const Item 
*RootItem 
= Lookup("RootDir"); 
 171    std::string rootDir 
=  (RootItem 
== 0) ? "" : RootItem
->Value
; 
 172    if(rootDir
.size() > 0 && rootDir
[rootDir
.size() - 1] != '/') 
 173      rootDir
.push_back('/'); 
 175    const Item 
*Itm 
= Lookup(Name
); 
 176    if (Itm 
== 0 || Itm
->Value
.empty() == true) 
 181          return rootDir 
+ Default
; 
 184    string val 
= Itm
->Value
; 
 185    while (Itm
->Parent 
!= 0 && Itm
->Parent
->Value
.empty() == false) 
 188       if (val
.length() >= 1 && val
[0] == '/') 
 192       if (val
.length() >= 2 && (val
[0] == '~' || val
[0] == '.') && val
[1] == '/') 
 196       if (val
.length() >= 3 && val
[0] == '.' && val
[1] == '.' && val
[2] == '/') 
 199       if (Itm
->Parent
->Value
.end()[-1] != '/') 
 202       val
.insert(0, Itm
->Parent
->Value
); 
 206    return rootDir 
+ val
; 
 209 // Configuration::FindDir - Find a directory name                       /*{{{*/ 
 210 // --------------------------------------------------------------------- 
 211 /* This is like findfile execept the result is terminated in a / */ 
 212 string 
Configuration::FindDir(const char *Name
,const char *Default
) const 
 214    string Res 
= FindFile(Name
,Default
); 
 215    if (Res
.end()[-1] != '/') 
 220 // Configuration::FindVector - Find a vector of values                  /*{{{*/ 
 221 // --------------------------------------------------------------------- 
 222 /* Returns a vector of config values under the given item */ 
 223 vector
<string
> Configuration::FindVector(const char *Name
) const 
 226    const Item 
*Top 
= Lookup(Name
); 
 230    Item 
*I 
= Top
->Child
; 
 233       Vec
.push_back(I
->Value
); 
 239 // Configuration::FindI - Find an integer value                         /*{{{*/ 
 240 // --------------------------------------------------------------------- 
 242 int Configuration::FindI(const char *Name
,int const &Default
) const 
 244    const Item 
*Itm 
= Lookup(Name
); 
 245    if (Itm 
== 0 || Itm
->Value
.empty() == true) 
 249    int Res 
= strtol(Itm
->Value
.c_str(),&End
,0); 
 250    if (End 
== Itm
->Value
.c_str()) 
 256 // Configuration::FindB - Find a boolean type                           /*{{{*/ 
 257 // --------------------------------------------------------------------- 
 259 bool Configuration::FindB(const char *Name
,bool const &Default
) const 
 261    const Item 
*Itm 
= Lookup(Name
); 
 262    if (Itm 
== 0 || Itm
->Value
.empty() == true) 
 265    return StringToBool(Itm
->Value
,Default
); 
 268 // Configuration::FindAny - Find an arbitrary type                      /*{{{*/ 
 269 // --------------------------------------------------------------------- 
 270 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */ 
 271 string 
Configuration::FindAny(const char *Name
,const char *Default
) const 
 276    if (key
.size() > 2 && key
.end()[-2] == '/') 
 278       type 
= key
.end()[-1]; 
 279       key
.resize(key
.size() - 2); 
 286       return FindFile(key
.c_str(), Default
); 
 290       return FindDir(key
.c_str(), Default
); 
 294       return FindB(key
, Default
) ? "true" : "false"; 
 300          snprintf(buf
, sizeof(buf
)-1, "%d", FindI(key
, Default 
? atoi(Default
) : 0 )); 
 306    return Find(Name
, Default
); 
 309 // Configuration::CndSet - Conditinal Set a value                       /*{{{*/ 
 310 // --------------------------------------------------------------------- 
 311 /* This will not overwrite */ 
 312 void Configuration::CndSet(const char *Name
,const string 
&Value
) 
 314    Item 
*Itm 
= Lookup(Name
,true); 
 317    if (Itm
->Value
.empty() == true) 
 321 // Configuration::Set - Set an integer value                            /*{{{*/ 
 322 // --------------------------------------------------------------------- 
 324 void Configuration::CndSet(const char *Name
,int const Value
) 
 326    Item 
*Itm 
= Lookup(Name
,true); 
 327    if (Itm 
== 0 || Itm
->Value
.empty() == false) 
 330    snprintf(S
,sizeof(S
),"%i",Value
); 
 334 // Configuration::Set - Set a value                                     /*{{{*/ 
 335 // --------------------------------------------------------------------- 
 337 void Configuration::Set(const char *Name
,const string 
&Value
) 
 339    Item 
*Itm 
= Lookup(Name
,true); 
 345 // Configuration::Set - Set an integer value                            /*{{{*/ 
 346 // --------------------------------------------------------------------- 
 348 void Configuration::Set(const char *Name
,int const &Value
) 
 350    Item 
*Itm 
= Lookup(Name
,true); 
 354    snprintf(S
,sizeof(S
),"%i",Value
); 
 358 // Configuration::Clear - Clear an single value from a list             /*{{{*/ 
 359 // --------------------------------------------------------------------- 
 361 void Configuration::Clear(string 
const &Name
, int const &Value
) 
 364    snprintf(S
,sizeof(S
),"%i",Value
); 
 368 // Configuration::Clear - Clear an single value from a list             /*{{{*/ 
 369 // --------------------------------------------------------------------- 
 371 void Configuration::Clear(string 
const &Name
, string 
const &Value
) 
 373    Item 
*Top 
= Lookup(Name
.c_str(),false); 
 374    if (Top 
== 0 || Top
->Child 
== 0) 
 377    Item 
*Tmp
, *Prev
, *I
; 
 378    Prev 
= I 
= Top
->Child
; 
 382       if(I
->Value 
== Value
) 
 385          // was first element, point parent to new first element 
 386          if(Top
->Child 
== Tmp
) 
 387             Top
->Child 
= I
->Next
; 
 399 // Configuration::Clear - Clear an entire tree                          /*{{{*/ 
 400 // --------------------------------------------------------------------- 
 402 void Configuration::Clear(string 
const &Name
) 
 404    Item 
*Top 
= Lookup(Name
.c_str(),false); 
 420       while (Top 
!= 0 && Top
->Next 
== 0) 
 437 // Configuration::Exists - Returns true if the Name exists              /*{{{*/ 
 438 // --------------------------------------------------------------------- 
 440 bool Configuration::Exists(const char *Name
) const 
 442    const Item 
*Itm 
= Lookup(Name
); 
 448 // Configuration::ExistsAny - Returns true if the Name, possibly        /*{{{*/ 
 449 // --------------------------------------------------------------------- 
 450 /* qualified by /[fdbi] exists */ 
 451 bool Configuration::ExistsAny(const char *Name
) const 
 455    if (key
.size() > 2 && key
.end()[-2] == '/') 
 457       if (key
.find_first_of("fdbi",key
.size()-1) < key
.size()) 
 459          key
.resize(key
.size() - 2); 
 460          if (Exists(key
.c_str())) 
 465          _error
->Warning(_("Unrecognized type abbreviation: '%c'"), key
.end()[-3]); 
 471 // Configuration::Dump - Dump the config                                /*{{{*/ 
 472 // --------------------------------------------------------------------- 
 473 /* Dump the entire configuration space */ 
 474 void Configuration::Dump(ostream
& str
) 
 476    /* Write out all of the configuration directives by walking the  
 477       configuration tree */ 
 478    const Configuration::Item 
*Top 
= Tree(0); 
 481       str 
<< Top
->FullTag() << " \"" << Top
->Value 
<< "\";" << endl
; 
 489       while (Top 
!= 0 && Top
->Next 
== 0) 
 497 // Configuration::Item::FullTag - Return the fully scoped tag           /*{{{*/ 
 498 // --------------------------------------------------------------------- 
 499 /* Stop sets an optional max recursion depth if this item is being viewed as 
 500    part of a sub tree. */ 
 501 string 
Configuration::Item::FullTag(const Item 
*Stop
) const 
 503    if (Parent 
== 0 || Parent
->Parent 
== 0 || Parent 
== Stop
) 
 505    return Parent
->FullTag(Stop
) + "::" + Tag
; 
 509 // ReadConfigFile - Read a configuration file                           /*{{{*/ 
 510 // --------------------------------------------------------------------- 
 511 /* The configuration format is very much like the named.conf format 
 512    used in bind8, in fact this routine can parse most named.conf files.  
 513    Sectional config files are like bind's named.conf where there are  
 514    sections like 'zone "foo.org" { .. };' This causes each section to be 
 515    added in with a tag like "zone::foo.org" instead of being split  
 516    tag/value. AsSectional enables Sectional parsing.*/ 
 517 bool ReadConfigFile(Configuration 
&Conf
,const string 
&FName
,bool const &AsSectional
, 
 518                     unsigned const &Depth
) 
 520    // Open the stream for reading 
 521    ifstream 
F(FName
.c_str(),ios::in
);  
 523       return _error
->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName
.c_str()); 
 527    unsigned int StackPos 
= 0; 
 533    bool InComment 
= false; 
 534    while (F
.eof() == false) 
 536       // The raw input line. 
 538       // The input line with comments stripped. 
 539       std::string Fragment
; 
 541       // Grab the next line of F and place it in Input. 
 544           char *Buffer 
= new char[1024]; 
 547           F
.getline(Buffer
,sizeof(Buffer
) / 2); 
 552       while (F
.fail() && !F
.eof()); 
 554       // Expand tabs in the input line and remove leading and trailing 
 557         const int BufferSize 
= Input
.size() * 8 + 1; 
 558         char *Buffer 
= new char[BufferSize
]; 
 561             memcpy(Buffer
, Input
.c_str(), Input
.size() + 1); 
 563             _strtabexpand(Buffer
, BufferSize
); 
 576       // Now strip comments; if the whole line is contained in a 
 577       // comment, skip this line. 
 579       // The first meaningful character in the current fragment; will 
 580       // be adjusted below as we remove bytes from the front. 
 581       std::string::const_iterator Start 
= Input
.begin(); 
 582       // The last meaningful character in the current fragment. 
 583       std::string::const_iterator End 
= Input
.end(); 
 585       // Multi line comment 
 586       if (InComment 
== true) 
 588         for (std::string::const_iterator I 
= Start
; 
 591             if (*I 
== '*' && I 
+ 1 != End 
&& I
[1] == '/') 
 598          if (InComment 
== true) 
 602       // Discard single line comments 
 603       bool InQuote 
= false; 
 604       for (std::string::const_iterator I 
= Start
; 
 612          if ((*I 
== '/' && I 
+ 1 != End 
&& I
[1] == '/') || 
 613              (*I 
== '#' && strcmp(string(I
,I
+6).c_str(),"#clear") != 0 && 
 614               strcmp(string(I
,I
+8).c_str(),"#include") != 0)) 
 621       // Look for multi line comments and build up the 
 623       Fragment
.reserve(End 
- Start
); 
 625       for (std::string::const_iterator I 
= Start
; 
 631            Fragment
.push_back(*I
); 
 632          else if (*I 
== '/' && I 
+ 1 != End 
&& I
[1] == '*') 
 635             for (std::string::const_iterator J 
= I
; 
 638                if (*J 
== '*' && J 
+ 1 != End 
&& J
[1] == '/') 
 640                   // Pretend we just finished walking over the 
 641                   // comment, and don't add anything to the output 
 649             if (InComment 
== true) 
 653            Fragment
.push_back(*I
); 
 657       if (Fragment
.empty()) 
 660       // The line has actual content; interpret what it means. 
 662       Start 
= Fragment
.begin(); 
 663       End 
= Fragment
.end(); 
 664       for (std::string::const_iterator I 
= Start
; 
 670          if (InQuote 
== false && (*I 
== '{' || *I 
== ';' || *I 
== '}')) 
 672             // Put the last fragment into the buffer 
 673             std::string::const_iterator NonWhitespaceStart 
= Start
; 
 674             std::string::const_iterator NonWhitespaceStop 
= I
; 
 675             for (; NonWhitespaceStart 
!= I 
&& isspace(*NonWhitespaceStart
) != 0; NonWhitespaceStart
++) 
 677             for (; NonWhitespaceStop 
!= NonWhitespaceStart 
&& isspace(NonWhitespaceStop
[-1]) != 0; NonWhitespaceStop
--) 
 679             if (LineBuffer
.empty() == false && NonWhitespaceStop 
- NonWhitespaceStart 
!= 0) 
 681             LineBuffer 
+= string(NonWhitespaceStart
, NonWhitespaceStop
); 
 683             // Drop this from the input string, saving the character 
 684             // that terminated the construct we just closed. (i.e., a 
 685             // brace or a semicolon) 
 690             if (TermChar 
== '{' && LineBuffer
.empty() == true) 
 691                return _error
->Error(_("Syntax error %s:%u: Block starts with no name."),FName
.c_str(),CurLine
); 
 693             // No string on this line 
 694             if (LineBuffer
.empty() == true) 
 699                      ParentTag 
= string(); 
 701                      ParentTag 
= Stack
[--StackPos
]; 
 708             const char *Pos 
= LineBuffer
.c_str(); 
 709             if (ParseQuoteWord(Pos
,Tag
) == false) 
 710                return _error
->Error(_("Syntax error %s:%u: Malformed tag"),FName
.c_str(),CurLine
); 
 712             // Parse off the word 
 715             if (ParseCWord(Pos
,Word
) == false && 
 716                 ParseQuoteWord(Pos
,Word
) == false) 
 726             if (strlen(Pos
) != 0) 
 727                return _error
->Error(_("Syntax error %s:%u: Extra junk after value"),FName
.c_str(),CurLine
); 
 733                   Stack
[StackPos
++] = ParentTag
; 
 735                /* Make sectional tags incorperate the section into the 
 737                if (AsSectional 
== true && Word
.empty() == false) 
 744                if (ParentTag
.empty() == true) 
 747                   ParentTag 
+= string("::") + Tag
; 
 751             // Generate the item name 
 753             if (ParentTag
.empty() == true) 
 757                if (TermChar 
!= '{' || Tag
.empty() == false) 
 758                   Item 
= ParentTag 
+ "::" + Tag
; 
 764             if (Tag
.length() >= 1 && Tag
[0] == '#') 
 766                if (ParentTag
.empty() == false) 
 767                   return _error
->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName
.c_str(),CurLine
); 
 768                Tag
.erase(Tag
.begin()); 
 771                else if (Tag 
== "include") 
 774                      return _error
->Error(_("Syntax error %s:%u: Too many nested includes"),FName
.c_str(),CurLine
); 
 775                   if (Word
.length() > 2 && Word
.end()[-1] == '/') 
 777                      if (ReadConfigDir(Conf
,Word
,AsSectional
,Depth
+1) == false) 
 778                         return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
); 
 782                      if (ReadConfigFile(Conf
,Word
,AsSectional
,Depth
+1) == false) 
 783                         return _error
->Error(_("Syntax error %s:%u: Included from here"),FName
.c_str(),CurLine
); 
 787                   return _error
->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName
.c_str(),CurLine
,Tag
.c_str()); 
 789             else if (Tag
.empty() == true && NoWord 
== false && Word 
== "#clear") 
 790                return _error
->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName
.c_str(),CurLine
); 
 793                // Set the item in the configuration class 
 801             // Move up a tag, but only if there is no bit to parse 
 807                   ParentTag 
= Stack
[--StackPos
]; 
 813       // Store the remaining text, if any, in the current line buffer. 
 815       // NB: could change this to use string-based operations; I'm 
 816       // using strstrip now to ensure backwards compatibility. 
 817       //   -- dburrows 2008-04-01 
 819         char *Buffer 
= new char[End 
- Start 
+ 1]; 
 822             std::copy(Start
, End
, Buffer
); 
 823             Buffer
[End 
- Start
] = '\0'; 
 825             const char *Stripd 
= _strstrip(Buffer
); 
 826             if (*Stripd 
!= 0 && LineBuffer
.empty() == false) 
 828             LineBuffer 
+= Stripd
; 
 839    if (LineBuffer
.empty() == false) 
 840       return _error
->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName
.c_str(),CurLine
); 
 844 // ReadConfigDir - Read a directory of config files                     /*{{{*/ 
 845 // --------------------------------------------------------------------- 
 847 bool ReadConfigDir(Configuration 
&Conf
,const string 
&Dir
, 
 848                    bool const &AsSectional
, unsigned const &Depth
) 
 850    vector
<string
> const List 
= GetListOfFilesInDir(Dir
, "conf", true, true); 
 853    for (vector
<string
>::const_iterator I 
= List
.begin(); I 
!= List
.end(); I
++) 
 854       if (ReadConfigFile(Conf
,*I
,AsSectional
,Depth
) == false) 
 859 // MatchAgainstConfig Constructor                                       /*{{{*/ 
 860 Configuration::MatchAgainstConfig::MatchAgainstConfig(char const * Config
) 
 862    std::vector
<std::string
> const strings 
= _config
->FindVector(Config
); 
 863    for (std::vector
<std::string
>::const_iterator s 
= strings
.begin(); 
 864         s 
!= strings
.end(); ++s
) 
 866       regex_t 
*p 
= new regex_t
; 
 867       if (regcomp(p
, s
->c_str(), REG_EXTENDED 
| REG_ICASE 
| REG_NOSUB
) == 0) 
 868          patterns
.push_back(p
); 
 874          _error
->Warning("Regex compilation error for '%s' in configuration option '%s'", 
 879    if (strings
.size() == 0) 
 880       patterns
.push_back(NULL
); 
 883 // MatchAgainstConfig Destructor                                        /*{{{*/ 
 884 Configuration::MatchAgainstConfig::~MatchAgainstConfig() 
 888 void Configuration::MatchAgainstConfig::clearPatterns() 
 890    for(std::vector
<regex_t 
*>::const_iterator p 
= patterns
.begin(); 
 891         p 
!= patterns
.end(); ++p
) 
 893       if (*p 
== NULL
) continue; 
 899 // MatchAgainstConfig::Match - returns true if a pattern matches        /*{{{*/ 
 900 bool Configuration::MatchAgainstConfig::Match(char const * str
) const 
 902    for(std::vector
<regex_t 
*>::const_iterator p 
= patterns
.begin(); 
 903         p 
!= patterns
.end(); ++p
) 
 904       if (*p 
!= NULL 
&& regexec(*p
, str
, 0, 0, 0) == 0)