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