- various "cosmetics" cleanups to shut up gcc complaints on higher warning
[apt.git] / apt-pkg / contrib / configuration.cc
1 // -*- mode: c++; mode: fold -*-
2 // Description                                                          /*{{{*/
3 // $Id: configuration.cc,v 1.27 2003/07/26 00:27:36 mdz Exp $
4 /* ######################################################################
5
6    Configuration Class
7    
8    This class provides a configuration file and command line parser
9    for a tree-oriented configuration environment. All runtime configuration
10    is stored in here.
11
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>.
14    
15    ##################################################################### */
16                                                                         /*}}}*/
17 // Include files                                                        /*{{{*/
18 #ifdef __GNUG__
19 #pragma implementation "apt-pkg/configuration.h"
20 #endif
21 #include <apt-pkg/configuration.h>
22 #include <apt-pkg/error.h>
23 #include <apt-pkg/strutl.h>
24 #include <apt-pkg/fileutl.h>
25 #include <apti18n.h>
26
27 #include <vector>
28 #include <algorithm>
29 #include <fstream>
30 #include <iostream>
31     
32 #include <stdio.h>
33 #include <dirent.h>
34 #include <sys/stat.h>
35 #include <unistd.h>
36
37 using namespace std;
38                                                                         /*}}}*/
39
40 Configuration *_config = new Configuration;
41
42 // Configuration::Configuration - Constructor                           /*{{{*/
43 // ---------------------------------------------------------------------
44 /* */
45 Configuration::Configuration() : ToFree(true)
46 {
47    Root = new Item;
48 }
49 Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
50 {
51 }
52
53 // CNC:2003-02-23 - Copy constructor.
54 Configuration::Configuration(Configuration &Conf) : ToFree(true)
55 {
56    Root = new Item;
57    if (Conf.Root->Child)
58       CopyChildren(Conf.Root, Root);
59 }
60
61 void Configuration::CopyChildren(Item *From, Item *To)
62 {
63    Item *Parent = To;
64    To->Child = new Item;
65    From = From->Child;
66    To = To->Child;
67    while (1) {
68       To->Parent = Parent;
69       To->Value = From->Value;
70       To->Tag = From->Tag;
71       if (From->Child)
72          CopyChildren(From, To);
73       From = From->Next;
74       if (From) {
75          To->Next = new Item;
76          To = To->Next;
77       } else {
78          break;
79       }
80    }
81 }
82                                                                         /*}}}*/
83 // Configuration::~Configuration - Destructor                           /*{{{*/
84 // ---------------------------------------------------------------------
85 /* */
86 Configuration::~Configuration()
87 {
88    if (ToFree == false)
89       return;
90    
91    Item *Top = Root;
92    for (; Top != 0;)
93    {
94       if (Top->Child != 0)
95       {
96          Top = Top->Child;
97          continue;
98       }
99             
100       while (Top != 0 && Top->Next == 0)
101       {
102          Item *Parent = Top->Parent;
103          delete Top;
104          Top = Parent;
105       }      
106       if (Top != 0)
107       {
108          Item *Next = Top->Next;
109          delete Top;
110          Top = Next;
111       }
112    }
113 }
114                                                                         /*}}}*/
115 // Configuration::Lookup - Lookup a single item                         /*{{{*/
116 // ---------------------------------------------------------------------
117 /* This will lookup a single item by name below another item. It is a 
118    helper function for the main lookup function */
119 Configuration::Item *Configuration::Lookup(Item *Head,const char *S,
120                                            unsigned long Len,bool Create)
121 {
122    int Res = 1;
123    Item *I = Head->Child;
124    Item **Last = &Head->Child;
125    
126    // Empty strings match nothing. They are used for lists.
127    if (Len != 0)
128    {
129       for (; I != 0; Last = &I->Next, I = I->Next)
130          if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
131             break;
132    }
133    else
134       for (; I != 0; Last = &I->Next, I = I->Next);
135       
136    if (Res == 0)
137       return I;
138    if (Create == false)
139       return 0;
140    
141    I = new Item;
142    I->Tag = string(S,Len);
143    I->Next = *Last;
144    I->Parent = Head;
145    *Last = I;
146    return I;
147 }
148                                                                         /*}}}*/
149 // Configuration::Lookup - Lookup a fully scoped item                   /*{{{*/
150 // ---------------------------------------------------------------------
151 /* This performs a fully scoped lookup of a given name, possibly creating
152    new items */
153 Configuration::Item *Configuration::Lookup(const char *Name,bool Create)
154 {
155    if (Name == 0)
156       return Root->Child;
157    
158    const char *Start = Name;
159    const char *End = Start + strlen(Name);
160    const char *TagEnd = Name;
161    Item *Itm = Root;
162    for (; End - TagEnd >= 2; TagEnd++)
163    {
164       if (TagEnd[0] == ':' && TagEnd[1] == ':')
165       {
166          Itm = Lookup(Itm,Start,TagEnd - Start,Create);
167          if (Itm == 0)
168             return 0;
169          TagEnd = Start = TagEnd + 2;    
170       }
171    }   
172
173    // This must be a trailing ::, we create unique items in a list
174    if (End - Start == 0)
175    {
176       if (Create == false)
177          return 0;
178    }
179    
180    Itm = Lookup(Itm,Start,End - Start,Create);
181    return Itm;
182 }
183                                                                         /*}}}*/
184 // Configuration::Find - Find a value                                   /*{{{*/
185 // ---------------------------------------------------------------------
186 /* */
187 string Configuration::Find(const char *Name,const char *Default) const
188 {
189    const Item *Itm = Lookup(Name);
190    if (Itm == 0 || Itm->Value.empty() == true)
191    {
192       if (Default == 0)
193          return string();
194       else
195          return Default;
196    }
197    
198    return Itm->Value;
199 }
200                                                                         /*}}}*/
201 // Configuration::FindFile - Find a Filename                            /*{{{*/
202 // ---------------------------------------------------------------------
203 /* Directories are stored as the base dir in the Parent node and the
204    sub directory in sub nodes with the final node being the end filename
205  */
206 string Configuration::FindFile(const char *Name,const char *Default) const
207 {
208    const Item *Itm = Lookup(Name);
209    if (Itm == 0 || Itm->Value.empty() == true)
210    {
211       if (Default == 0)
212          return string();
213       else
214          return Default;
215    }
216    
217    string val = Itm->Value;
218    while (Itm->Parent != 0 && Itm->Parent->Value.empty() == false)
219    {     
220       // Absolute
221       if (val.length() >= 1 && val[0] == '/')
222          break;
223
224       // ~/foo or ./foo 
225       if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
226          break;
227          
228       // ../foo 
229       if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
230          break;
231       
232       if (Itm->Parent->Value.end()[-1] != '/')
233          val.insert(0, "/");
234
235       val.insert(0, Itm->Parent->Value);
236       Itm = Itm->Parent;
237    }
238
239    return val;
240 }
241                                                                         /*}}}*/
242 // Configuration::FindDir - Find a directory name                       /*{{{*/
243 // ---------------------------------------------------------------------
244 /* This is like findfile execept the result is terminated in a / */
245 string Configuration::FindDir(const char *Name,const char *Default) const
246 {
247    string Res = FindFile(Name,Default);
248    if (Res.end()[-1] != '/')
249       return Res + '/';
250    return Res;
251 }
252                                                                         /*}}}*/
253 // Configuration::FindI - Find an integer value                         /*{{{*/
254 // ---------------------------------------------------------------------
255 /* */
256 int Configuration::FindI(const char *Name,int Default) const
257 {
258    const Item *Itm = Lookup(Name);
259    if (Itm == 0 || Itm->Value.empty() == true)
260       return Default;
261    
262    char *End;
263    int Res = strtol(Itm->Value.c_str(),&End,0);
264    if (End == Itm->Value.c_str())
265       return Default;
266    
267    return Res;
268 }
269                                                                         /*}}}*/
270 // Configuration::FindB - Find a boolean type                           /*{{{*/
271 // ---------------------------------------------------------------------
272 /* */
273 bool Configuration::FindB(const char *Name,bool Default) const
274 {
275    const Item *Itm = Lookup(Name);
276    if (Itm == 0 || Itm->Value.empty() == true)
277       return Default;
278    
279    return StringToBool(Itm->Value,Default);
280 }
281                                                                         /*}}}*/
282 // Configuration::FindAny - Find an arbitrary type                      /*{{{*/
283 // ---------------------------------------------------------------------
284 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
285 string Configuration::FindAny(const char *Name,const char *Default) const
286 {
287    string key = Name;
288    char type = 0;
289
290    if (key.size() > 2 && key.end()[-2] == '/')
291    {
292       type = key.end()[-1];
293       key.resize(key.size() - 2);
294    }
295
296    switch (type)
297    {
298       // file
299       case 'f': 
300       return FindFile(key.c_str(), Default);
301       
302       // directory
303       case 'd': 
304       return FindDir(key.c_str(), Default);
305       
306       // bool
307       case 'b': 
308       // CNC:2003-11-23
309       return FindB(key, StringToBool(Default)) ? "true" : "false";
310       
311       // int
312       case 'i': 
313       {
314          char buf[16];
315          snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
316          return buf;
317       }
318    }
319
320    // fallback
321    return Find(Name, Default);
322 }
323                                                                         /*}}}*/
324 // Configuration::CndSet - Conditinal Set a value                       /*{{{*/
325 // ---------------------------------------------------------------------
326 /* This will not overwrite */
327 void Configuration::CndSet(const char *Name,string Value)
328 {
329    Item *Itm = Lookup(Name,true);
330    if (Itm == 0)
331       return;
332    if (Itm->Value.empty() == true)
333       Itm->Value = Value;
334 }
335                                                                         /*}}}*/
336 // Configuration::Set - Set a value                                     /*{{{*/
337 // ---------------------------------------------------------------------
338 /* */
339 void Configuration::Set(const char *Name,string Value)
340 {
341    Item *Itm = Lookup(Name,true);
342    if (Itm == 0)
343       return;
344    Itm->Value = Value;
345 }
346                                                                         /*}}}*/
347 // Configuration::Set - Set an integer value                            /*{{{*/
348 // ---------------------------------------------------------------------
349 /* */
350 void Configuration::Set(const char *Name,int Value)
351 {
352    Item *Itm = Lookup(Name,true);
353    if (Itm == 0)
354       return;
355    char S[300];
356    snprintf(S,sizeof(S),"%i",Value);
357    Itm->Value = S;
358 }
359                                                                         /*}}}*/
360 // Configuration::Clear - Clear an entire tree                          /*{{{*/
361 // ---------------------------------------------------------------------
362 /* */
363 void Configuration::Clear(string Name)
364 {
365    Item *Top = Lookup(Name.c_str(),false);
366    if (Top == 0)
367       return;
368    
369    Top->Value = string();
370    Item *Stop = Top;
371    Top = Top->Child;
372    Stop->Child = 0;
373    for (; Top != 0;)
374    {
375       if (Top->Child != 0)
376       {
377          Top = Top->Child;
378          continue;
379       }
380
381       while (Top != 0 && Top->Next == 0)
382       {
383          Item *Tmp = Top;
384          Top = Top->Parent;
385          delete Tmp;
386          
387          if (Top == Stop)
388             return;
389       }
390       
391       Item *Tmp = Top;
392       if (Top != 0)
393          Top = Top->Next;
394       delete Tmp;
395    }
396 }
397                                                                         /*}}}*/
398 // Configuration::Exists - Returns true if the Name exists              /*{{{*/
399 // ---------------------------------------------------------------------
400 /* */
401 bool Configuration::Exists(const char *Name) const
402 {
403    const Item *Itm = Lookup(Name);
404    if (Itm == 0)
405       return false;
406    return true;
407 }
408                                                                         /*}}}*/
409 // Configuration::ExistsAny - Returns true if the Name, possibly        /*{{{*/
410 // ---------------------------------------------------------------------
411 /* qualified by /[fdbi] exists */
412 bool Configuration::ExistsAny(const char *Name) const
413 {
414    string key = Name;
415
416    if (key.size() > 2 && key.end()[-2] == '/')
417       if (key.find_first_of("fdbi",key.size()-1) < key.size())
418       {
419          key.resize(key.size() - 2);
420          if (Exists(key.c_str()))
421             return true;
422       }
423       else
424       {
425          _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
426       }
427
428    return Exists(Name);
429 }
430                                                                         /*}}}*/
431 // Configuration::Dump - Dump the config                                /*{{{*/
432 // ---------------------------------------------------------------------
433 /* Dump the entire configuration space */
434 void Configuration::Dump(ostream& str)
435 {
436    /* Write out all of the configuration directives by walking the 
437       configuration tree */
438    const Configuration::Item *Top = Tree(0);
439    for (; Top != 0;)
440    {
441       str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
442       
443       if (Top->Child != 0)
444       {
445          Top = Top->Child;
446          continue;
447       }
448       
449       while (Top != 0 && Top->Next == 0)
450          Top = Top->Parent;
451       if (Top != 0)
452          Top = Top->Next;
453    }
454 }
455                                                                         /*}}}*/
456
457 // Configuration::Item::FullTag - Return the fully scoped tag           /*{{{*/
458 // ---------------------------------------------------------------------
459 /* Stop sets an optional max recursion depth if this item is being viewed as
460    part of a sub tree. */
461 string Configuration::Item::FullTag(const Item *Stop) const
462 {
463    if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
464       return Tag;
465    return Parent->FullTag(Stop) + "::" + Tag;
466 }
467                                                                         /*}}}*/
468
469 // ReadConfigFile - Read a configuration file                           /*{{{*/
470 // ---------------------------------------------------------------------
471 /* The configuration format is very much like the named.conf format
472    used in bind8, in fact this routine can parse most named.conf files. 
473    Sectional config files are like bind's named.conf where there are 
474    sections like 'zone "foo.org" { .. };' This causes each section to be
475    added in with a tag like "zone::foo.org" instead of being split 
476    tag/value. AsSectional enables Sectional parsing.*/
477 bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
478                     unsigned Depth)
479 {   
480    // Open the stream for reading
481    ifstream F(FName.c_str(),ios::in); 
482    if (!F != 0)
483       return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
484    
485    char Buffer[300];
486    string LineBuffer;
487    string Stack[100];
488    unsigned int StackPos = 0;
489    
490    // Parser state
491    string ParentTag;
492    
493    int CurLine = 0;
494    bool InComment = false;
495    while (F.eof() == false)
496    {
497       F.getline(Buffer,sizeof(Buffer));
498       CurLine++;
499       _strtabexpand(Buffer,sizeof(Buffer));
500       _strstrip(Buffer);
501
502       // Multi line comment
503       if (InComment == true)
504       {
505          for (const char *I = Buffer; *I != 0; I++)
506          {
507             if (*I == '*' && I[1] == '/')
508             {
509                memmove(Buffer,I+2,strlen(I+2) + 1);
510                InComment = false;
511                break;
512             }       
513          }
514          if (InComment == true)
515             continue;
516       }
517       
518       // Discard single line comments
519       bool InQuote = false;
520       for (char *I = Buffer; *I != 0; I++)
521       {
522          if (*I == '"')
523             InQuote = !InQuote;
524          if (InQuote == true)
525             continue;
526          
527          if (*I == '/' && I[1] == '/')
528          {
529             *I = 0;
530             break;
531          }
532       }
533
534       // Look for multi line comments
535       InQuote = false;
536       for (char *I = Buffer; *I != 0; I++)
537       {
538          if (*I == '"')
539             InQuote = !InQuote;
540          if (InQuote == true)
541             continue;
542          
543          if (*I == '/' && I[1] == '*')
544          {
545             InComment = true;
546             for (char *J = Buffer; *J != 0; J++)
547             {
548                if (*J == '*' && J[1] == '/')
549                {
550                   memmove(I,J+2,strlen(J+2) + 1);
551                   InComment = false;
552                   break;
553                }               
554             }
555             
556             if (InComment == true)
557             {
558                *I = 0;
559                break;
560             }       
561          }
562       }
563       
564       // Blank
565       if (Buffer[0] == 0)
566          continue;
567       
568       // We now have a valid line fragment
569       InQuote = false;
570       for (char *I = Buffer; *I != 0;)
571       {
572          if (*I == '"')
573             InQuote = !InQuote;
574          
575          if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
576          {
577             // Put the last fragment into the buffer
578             char *Start = Buffer;
579             char *Stop = I;
580             for (; Start != I && isspace(*Start) != 0; Start++);
581             for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
582             if (LineBuffer.empty() == false && Stop - Start != 0)
583                LineBuffer += ' ';
584             LineBuffer += string(Start,Stop - Start);
585             
586             // Remove the fragment
587             char TermChar = *I;
588             memmove(Buffer,I + 1,strlen(I + 1) + 1);
589             I = Buffer;
590             
591             // Syntax Error
592             if (TermChar == '{' && LineBuffer.empty() == true)
593                return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
594             
595             // No string on this line
596             if (LineBuffer.empty() == true)
597             {
598                if (TermChar == '}')
599                {
600                   if (StackPos == 0)
601                      ParentTag = string();
602                   else
603                      ParentTag = Stack[--StackPos];
604                }
605                continue;
606             }
607             
608             // Parse off the tag
609             string Tag;
610             const char *Pos = LineBuffer.c_str();
611             if (ParseQuoteWord(Pos,Tag) == false)
612                return _error->Error(_("Syntax error %s:%u: Malformed Tag"),FName.c_str(),CurLine);
613
614             // Parse off the word
615             string Word;
616             bool NoWord = false;
617             if (ParseCWord(Pos,Word) == false &&
618                 ParseQuoteWord(Pos,Word) == false)
619             {
620                if (TermChar != '{')
621                {
622                   Word = Tag;
623                   Tag = "";
624                }
625                else
626                   NoWord = true;
627             }
628             if (strlen(Pos) != 0)
629                return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
630
631             // Go down a level
632             if (TermChar == '{')
633             {
634                if (StackPos <= 100)
635                   Stack[StackPos++] = ParentTag;
636                
637                /* Make sectional tags incorperate the section into the
638                   tag string */
639                if (AsSectional == true && Word.empty() == false)
640                {
641                   Tag += "::" ;
642                   Tag += Word;
643                   Word = "";
644                }
645                
646                if (ParentTag.empty() == true)
647                   ParentTag = Tag;
648                else
649                   ParentTag += string("::") + Tag;
650                Tag = string();
651             }
652             
653             // Generate the item name
654             string Item;
655             if (ParentTag.empty() == true)
656                Item = Tag;
657             else
658             {
659                if (TermChar != '{' || Tag.empty() == false)
660                   Item = ParentTag + "::" + Tag;
661                else
662                   Item = ParentTag;
663             }
664             
665             // Specials
666             if (Tag.length() >= 1 && Tag[0] == '#')
667             {
668                if (ParentTag.empty() == false)
669                   return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
670                Tag.erase(Tag.begin());
671                if (Tag == "clear")
672                   Conf.Clear(Word);
673                else if (Tag == "include")
674                {
675                   if (Depth > 10)
676                      return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
677                   if (Word.length() > 2 && Word.end()[-1] == '/')
678                   {
679                      if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
680                         return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
681                   }
682                   else
683                   {
684                      if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
685                         return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
686                   }               
687                }
688                else
689                   return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
690             }
691             else
692             {
693                // Set the item in the configuration class
694                if (NoWord == false)
695                   Conf.Set(Item,Word);
696             }
697             
698             // Empty the buffer
699             LineBuffer = string();
700             
701             // Move up a tag, but only if there is no bit to parse
702             if (TermChar == '}')
703             {
704                if (StackPos == 0)
705                   ParentTag = string();
706                else
707                   ParentTag = Stack[--StackPos];
708             }
709             
710          }
711          else
712             I++;
713       }
714
715       // Store the fragment
716       const char *Stripd = _strstrip(Buffer);
717       if (*Stripd != 0 && LineBuffer.empty() == false)
718          LineBuffer += " ";
719       LineBuffer += Stripd;
720    }
721
722    if (LineBuffer.empty() == false)
723       return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
724    return true;
725 }
726                                                                         /*}}}*/
727 // ReadConfigDir - Read a directory of config files                     /*{{{*/
728 // ---------------------------------------------------------------------
729 /* */
730 bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
731                     unsigned Depth)
732 {   
733    DIR *D = opendir(Dir.c_str());
734    if (D == 0)
735       return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
736
737    vector<string> List;
738    
739    for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
740    {
741       if (Ent->d_name[0] == '.')
742          continue;
743
744       // CNC:2003-12-02 Only accept .list & .conf files as valid config parts
745       if ((flExtension(Ent->d_name) != "list") && 
746           (flExtension(Ent->d_name) != "conf"))
747          continue;
748       
749       // Skip bad file names ala run-parts
750       const char *C = Ent->d_name;
751       for (; *C != 0; C++)
752          // CNC:2002-11-25
753          if (isalpha(*C) == 0 && isdigit(*C) == 0
754              && *C != '_' && *C != '-' && *C != '.')
755             break;
756       if (*C != 0)
757          continue;
758       
759       // Make sure it is a file and not something else
760       string File = flCombine(Dir,Ent->d_name);
761       struct stat St;
762       if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
763          continue;
764       
765       List.push_back(File);      
766    }   
767    closedir(D);
768    
769    sort(List.begin(),List.end());
770
771    // Read the files
772    for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
773       if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
774          return false;
775    return true;
776 }
777                                                                         /*}}}*/
778
779 // vim:sts=3:sw=3