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