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