]> git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
4b2c0fbb51977e53bdd21eb4f9ddd01e6c9ab03b
[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 = string(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 string();
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 string();
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,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,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(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(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 cout << "config item: " << Name << " not found" << endl;
379 return;
380 }
381 Top->Value = string();
382 Item *Stop = Top;
383 Top = Top->Child;
384 Stop->Child = 0;
385 for (; Top != 0;)
386 {
387 if (Top->Child != 0)
388 {
389 Top = Top->Child;
390 continue;
391 }
392
393 while (Top != 0 && Top->Next == 0)
394 {
395 Item *Tmp = Top;
396 Top = Top->Parent;
397 delete Tmp;
398
399 if (Top == Stop)
400 return;
401 }
402
403 Item *Tmp = Top;
404 if (Top != 0)
405 Top = Top->Next;
406 delete Tmp;
407 }
408 }
409 /*}}}*/
410 // Configuration::Exists - Returns true if the Name exists /*{{{*/
411 // ---------------------------------------------------------------------
412 /* */
413 bool Configuration::Exists(const char *Name) const
414 {
415 const Item *Itm = Lookup(Name);
416 if (Itm == 0)
417 return false;
418 return true;
419 }
420 /*}}}*/
421 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
422 // ---------------------------------------------------------------------
423 /* qualified by /[fdbi] exists */
424 bool Configuration::ExistsAny(const char *Name) const
425 {
426 string key = Name;
427
428 if (key.size() > 2 && key.end()[-2] == '/')
429 if (key.find_first_of("fdbi",key.size()-1) < key.size())
430 {
431 key.resize(key.size() - 2);
432 if (Exists(key.c_str()))
433 return true;
434 }
435 else
436 {
437 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
438 }
439
440 return Exists(Name);
441 }
442 /*}}}*/
443 // Configuration::Dump - Dump the config /*{{{*/
444 // ---------------------------------------------------------------------
445 /* Dump the entire configuration space */
446 void Configuration::Dump(ostream& str)
447 {
448 /* Write out all of the configuration directives by walking the
449 configuration tree */
450 const Configuration::Item *Top = Tree(0);
451 for (; Top != 0;)
452 {
453 str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
454
455 if (Top->Child != 0)
456 {
457 Top = Top->Child;
458 continue;
459 }
460
461 while (Top != 0 && Top->Next == 0)
462 Top = Top->Parent;
463 if (Top != 0)
464 Top = Top->Next;
465 }
466 }
467 /*}}}*/
468
469 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
470 // ---------------------------------------------------------------------
471 /* Stop sets an optional max recursion depth if this item is being viewed as
472 part of a sub tree. */
473 string Configuration::Item::FullTag(const Item *Stop) const
474 {
475 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
476 return Tag;
477 return Parent->FullTag(Stop) + "::" + Tag;
478 }
479 /*}}}*/
480
481 // ReadConfigFile - Read a configuration file /*{{{*/
482 // ---------------------------------------------------------------------
483 /* The configuration format is very much like the named.conf format
484 used in bind8, in fact this routine can parse most named.conf files.
485 Sectional config files are like bind's named.conf where there are
486 sections like 'zone "foo.org" { .. };' This causes each section to be
487 added in with a tag like "zone::foo.org" instead of being split
488 tag/value. AsSectional enables Sectional parsing.*/
489 bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
490 unsigned Depth)
491 {
492 // Open the stream for reading
493 ifstream F(FName.c_str(),ios::in);
494 if (!F != 0)
495 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
496
497 char Buffer[1024];
498 string LineBuffer;
499 string Stack[100];
500 unsigned int StackPos = 0;
501
502 // Parser state
503 string ParentTag;
504
505 int CurLine = 0;
506 bool InComment = false;
507 while (F.eof() == false)
508 {
509 F.getline(Buffer,sizeof(Buffer));
510 CurLine++;
511 // This should be made to work instead, but this is better than looping
512 if (F.fail() && !F.eof())
513 return _error->Error(_("Line %d too long (max %d)"), CurLine, sizeof(Buffer));
514
515 _strtabexpand(Buffer,sizeof(Buffer));
516 _strstrip(Buffer);
517
518 // Multi line comment
519 if (InComment == true)
520 {
521 for (const char *I = Buffer; *I != 0; I++)
522 {
523 if (*I == '*' && I[1] == '/')
524 {
525 memmove(Buffer,I+2,strlen(I+2) + 1);
526 InComment = false;
527 break;
528 }
529 }
530 if (InComment == true)
531 continue;
532 }
533
534 // Discard single line comments
535 bool 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 *I = 0;
546 break;
547 }
548 }
549
550 // Look for multi line comments
551 InQuote = false;
552 for (char *I = Buffer; *I != 0; I++)
553 {
554 if (*I == '"')
555 InQuote = !InQuote;
556 if (InQuote == true)
557 continue;
558
559 if (*I == '/' && I[1] == '*')
560 {
561 InComment = true;
562 for (char *J = Buffer; *J != 0; J++)
563 {
564 if (*J == '*' && J[1] == '/')
565 {
566 memmove(I,J+2,strlen(J+2) + 1);
567 InComment = false;
568 break;
569 }
570 }
571
572 if (InComment == true)
573 {
574 *I = 0;
575 break;
576 }
577 }
578 }
579
580 // Blank
581 if (Buffer[0] == 0)
582 continue;
583
584 // We now have a valid line fragment
585 InQuote = false;
586 for (char *I = Buffer; *I != 0;)
587 {
588 if (*I == '"')
589 InQuote = !InQuote;
590
591 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
592 {
593 // Put the last fragment into the buffer
594 char *Start = Buffer;
595 char *Stop = I;
596 for (; Start != I && isspace(*Start) != 0; Start++);
597 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
598 if (LineBuffer.empty() == false && Stop - Start != 0)
599 LineBuffer += ' ';
600 LineBuffer += string(Start,Stop - Start);
601
602 // Remove the fragment
603 char TermChar = *I;
604 memmove(Buffer,I + 1,strlen(I + 1) + 1);
605 I = Buffer;
606
607 // Syntax Error
608 if (TermChar == '{' && LineBuffer.empty() == true)
609 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
610
611 // No string on this line
612 if (LineBuffer.empty() == true)
613 {
614 if (TermChar == '}')
615 {
616 if (StackPos == 0)
617 ParentTag = string();
618 else
619 ParentTag = Stack[--StackPos];
620 }
621 continue;
622 }
623
624 // Parse off the tag
625 string Tag;
626 const char *Pos = LineBuffer.c_str();
627 if (ParseQuoteWord(Pos,Tag) == false)
628 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
629
630 // Parse off the word
631 string Word;
632 bool NoWord = false;
633 if (ParseCWord(Pos,Word) == false &&
634 ParseQuoteWord(Pos,Word) == false)
635 {
636 if (TermChar != '{')
637 {
638 Word = Tag;
639 Tag = "";
640 }
641 else
642 NoWord = true;
643 }
644 if (strlen(Pos) != 0)
645 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
646
647 // Go down a level
648 if (TermChar == '{')
649 {
650 if (StackPos <= 100)
651 Stack[StackPos++] = ParentTag;
652
653 /* Make sectional tags incorperate the section into the
654 tag string */
655 if (AsSectional == true && Word.empty() == false)
656 {
657 Tag += "::" ;
658 Tag += Word;
659 Word = "";
660 }
661
662 if (ParentTag.empty() == true)
663 ParentTag = Tag;
664 else
665 ParentTag += string("::") + Tag;
666 Tag = string();
667 }
668
669 // Generate the item name
670 string Item;
671 if (ParentTag.empty() == true)
672 Item = Tag;
673 else
674 {
675 if (TermChar != '{' || Tag.empty() == false)
676 Item = ParentTag + "::" + Tag;
677 else
678 Item = ParentTag;
679 }
680
681 // Specials
682 if (Tag.length() >= 1 && Tag[0] == '#')
683 {
684 if (ParentTag.empty() == false)
685 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
686 Tag.erase(Tag.begin());
687 if (Tag == "clear")
688 Conf.Clear(Word);
689 else if (Tag == "include")
690 {
691 if (Depth > 10)
692 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
693 if (Word.length() > 2 && Word.end()[-1] == '/')
694 {
695 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
696 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
697 }
698 else
699 {
700 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
701 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
702 }
703 }
704 else
705 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
706 }
707 else
708 {
709 // Set the item in the configuration class
710 if (NoWord == false)
711 Conf.Set(Item,Word);
712 }
713
714 // Empty the buffer
715 LineBuffer = string();
716
717 // Move up a tag, but only if there is no bit to parse
718 if (TermChar == '}')
719 {
720 if (StackPos == 0)
721 ParentTag = string();
722 else
723 ParentTag = Stack[--StackPos];
724 }
725
726 }
727 else
728 I++;
729 }
730
731 // Store the fragment
732 const char *Stripd = _strstrip(Buffer);
733 if (*Stripd != 0 && LineBuffer.empty() == false)
734 LineBuffer += " ";
735 LineBuffer += Stripd;
736 }
737
738 if (LineBuffer.empty() == false)
739 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
740 return true;
741 }
742 /*}}}*/
743 // ReadConfigDir - Read a directory of config files /*{{{*/
744 // ---------------------------------------------------------------------
745 /* */
746 bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
747 unsigned Depth)
748 {
749 DIR *D = opendir(Dir.c_str());
750 if (D == 0)
751 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
752
753 vector<string> List;
754
755 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
756 {
757 if (Ent->d_name[0] == '.')
758 continue;
759
760 // Skip bad file names ala run-parts
761 const char *C = Ent->d_name;
762 for (; *C != 0; C++)
763 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
764 break;
765 if (*C != 0)
766 continue;
767
768 // Make sure it is a file and not something else
769 string File = flCombine(Dir,Ent->d_name);
770 struct stat St;
771 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
772 continue;
773
774 List.push_back(File);
775 }
776 closedir(D);
777
778 sort(List.begin(),List.end());
779
780 // Read the files
781 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
782 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
783 return false;
784 return true;
785 }
786 /*}}}*/