]> git.saurik.com Git - apt.git/blob - apt-pkg/contrib/configuration.cc
Restored GCC 2.95 compilability
[apt.git] / apt-pkg / contrib / configuration.cc
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 // $Id: configuration.cc,v 1.22 2001/05/14 05:47:30 jgg 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 ##################################################################### */
13 /*}}}*/
14 // Include files /*{{{*/
15 #ifdef __GNUG__
16 #pragma implementation "apt-pkg/configuration.h"
17 #endif
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 = string(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 string();
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 string();
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, atoi(Default)));
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,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,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 entire tree /*{{{*/
328 // ---------------------------------------------------------------------
329 /* */
330 void Configuration::Clear(string Name)
331 {
332 Item *Top = Lookup(Name.c_str(),false);
333 if (Top == 0)
334 return;
335
336 Top->Value = string();
337 Item *Stop = Top;
338 Top = Top->Child;
339 Stop->Child = 0;
340 for (; Top != 0;)
341 {
342 if (Top->Child != 0)
343 {
344 Top = Top->Child;
345 continue;
346 }
347
348 while (Top != 0 && Top->Next == 0)
349 {
350 Item *Tmp = Top;
351 Top = Top->Parent;
352 delete Tmp;
353
354 if (Top == Stop)
355 return;
356 }
357
358 Item *Tmp = Top;
359 if (Top != 0)
360 Top = Top->Next;
361 delete Tmp;
362 }
363 }
364 /*}}}*/
365 // Configuration::Exists - Returns true if the Name exists /*{{{*/
366 // ---------------------------------------------------------------------
367 /* */
368 bool Configuration::Exists(const char *Name) const
369 {
370 const Item *Itm = Lookup(Name);
371 if (Itm == 0)
372 return false;
373 return true;
374 }
375 /*}}}*/
376 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
377 // ---------------------------------------------------------------------
378 /* qualified by /[fdbi] exists */
379 bool Configuration::ExistsAny(const char *Name) const
380 {
381 string key = Name;
382
383 if (key.size() > 2 && key.end()[-2] == '/' &&
384 key.find_first_of("fdbi",key.size()-1) < key.size())
385 {
386 key.resize(key.size() - 2);
387 if (Exists(key.c_str()))
388 return true;
389 }
390
391 return Exists(Name);
392 }
393 /*}}}*/
394 // Configuration::Dump - Dump the config /*{{{*/
395 // ---------------------------------------------------------------------
396 /* Dump the entire configuration space */
397 void Configuration::Dump()
398 {
399 /* Write out all of the configuration directives by walking the
400 configuration tree */
401 const Configuration::Item *Top = Tree(0);
402 for (; Top != 0;)
403 {
404 clog << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
405
406 if (Top->Child != 0)
407 {
408 Top = Top->Child;
409 continue;
410 }
411
412 while (Top != 0 && Top->Next == 0)
413 Top = Top->Parent;
414 if (Top != 0)
415 Top = Top->Next;
416 }
417 }
418 /*}}}*/
419
420 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
421 // ---------------------------------------------------------------------
422 /* Stop sets an optional max recursion depth if this item is being viewed as
423 part of a sub tree. */
424 string Configuration::Item::FullTag(const Item *Stop) const
425 {
426 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
427 return Tag;
428 return Parent->FullTag(Stop) + "::" + Tag;
429 }
430 /*}}}*/
431
432 // ReadConfigFile - Read a configuration file /*{{{*/
433 // ---------------------------------------------------------------------
434 /* The configuration format is very much like the named.conf format
435 used in bind8, in fact this routine can parse most named.conf files.
436 Sectional config files are like bind's named.conf where there are
437 sections like 'zone "foo.org" { .. };' This causes each section to be
438 added in with a tag like "zone::foo.org" instead of being split
439 tag/value. AsSectional enables Sectional parsing.*/
440 bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
441 unsigned Depth)
442 {
443 // Open the stream for reading
444 ifstream F(FName.c_str(),ios::in);
445 if (!F != 0)
446 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
447
448 char Buffer[300];
449 string LineBuffer;
450 string Stack[100];
451 unsigned int StackPos = 0;
452
453 // Parser state
454 string ParentTag;
455
456 int CurLine = 0;
457 bool InComment = false;
458 while (F.eof() == false)
459 {
460 F.getline(Buffer,sizeof(Buffer));
461 CurLine++;
462 _strtabexpand(Buffer,sizeof(Buffer));
463 _strstrip(Buffer);
464
465 // Multi line comment
466 if (InComment == true)
467 {
468 for (const char *I = Buffer; *I != 0; I++)
469 {
470 if (*I == '*' && I[1] == '/')
471 {
472 memmove(Buffer,I+2,strlen(I+2) + 1);
473 InComment = false;
474 break;
475 }
476 }
477 if (InComment == true)
478 continue;
479 }
480
481 // Discard single line comments
482 bool InQuote = false;
483 for (char *I = Buffer; *I != 0; I++)
484 {
485 if (*I == '"')
486 InQuote = !InQuote;
487 if (InQuote == true)
488 continue;
489
490 if (*I == '/' && I[1] == '/')
491 {
492 *I = 0;
493 break;
494 }
495 }
496
497 // Look for multi line comments
498 InQuote = false;
499 for (char *I = Buffer; *I != 0; I++)
500 {
501 if (*I == '"')
502 InQuote = !InQuote;
503 if (InQuote == true)
504 continue;
505
506 if (*I == '/' && I[1] == '*')
507 {
508 InComment = true;
509 for (char *J = Buffer; *J != 0; J++)
510 {
511 if (*J == '*' && J[1] == '/')
512 {
513 memmove(I,J+2,strlen(J+2) + 1);
514 InComment = false;
515 break;
516 }
517 }
518
519 if (InComment == true)
520 {
521 *I = 0;
522 break;
523 }
524 }
525 }
526
527 // Blank
528 if (Buffer[0] == 0)
529 continue;
530
531 // We now have a valid line fragment
532 InQuote = false;
533 for (char *I = Buffer; *I != 0;)
534 {
535 if (*I == '"')
536 InQuote = !InQuote;
537
538 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
539 {
540 // Put the last fragment into the buffer
541 char *Start = Buffer;
542 char *Stop = I;
543 for (; Start != I && isspace(*Start) != 0; Start++);
544 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
545 if (LineBuffer.empty() == false && Stop - Start != 0)
546 LineBuffer += ' ';
547 LineBuffer += string(Start,Stop - Start);
548
549 // Remove the fragment
550 char TermChar = *I;
551 memmove(Buffer,I + 1,strlen(I + 1) + 1);
552 I = Buffer;
553
554 // Syntax Error
555 if (TermChar == '{' && LineBuffer.empty() == true)
556 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
557
558 // No string on this line
559 if (LineBuffer.empty() == true)
560 {
561 if (TermChar == '}')
562 {
563 if (StackPos == 0)
564 ParentTag = string();
565 else
566 ParentTag = Stack[--StackPos];
567 }
568 continue;
569 }
570
571 // Parse off the tag
572 string Tag;
573 const char *Pos = LineBuffer.c_str();
574 if (ParseQuoteWord(Pos,Tag) == false)
575 return _error->Error(_("Syntax error %s:%u: Malformed Tag"),FName.c_str(),CurLine);
576
577 // Parse off the word
578 string Word;
579 bool NoWord = false;
580 if (ParseCWord(Pos,Word) == false &&
581 ParseQuoteWord(Pos,Word) == false)
582 {
583 if (TermChar != '{')
584 {
585 Word = Tag;
586 Tag = "";
587 }
588 else
589 NoWord = true;
590 }
591 if (strlen(Pos) != 0)
592 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
593
594 // Go down a level
595 if (TermChar == '{')
596 {
597 if (StackPos <= 100)
598 Stack[StackPos++] = ParentTag;
599
600 /* Make sectional tags incorperate the section into the
601 tag string */
602 if (AsSectional == true && Word.empty() == false)
603 {
604 Tag += "::" ;
605 Tag += Word;
606 Word = "";
607 }
608
609 if (ParentTag.empty() == true)
610 ParentTag = Tag;
611 else
612 ParentTag += string("::") + Tag;
613 Tag = string();
614 }
615
616 // Generate the item name
617 string Item;
618 if (ParentTag.empty() == true)
619 Item = Tag;
620 else
621 {
622 if (TermChar != '{' || Tag.empty() == false)
623 Item = ParentTag + "::" + Tag;
624 else
625 Item = ParentTag;
626 }
627
628 // Specials
629 if (Tag.length() >= 1 && Tag[0] == '#')
630 {
631 if (ParentTag.empty() == false)
632 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
633 Tag.erase(Tag.begin());
634 if (Tag == "clear")
635 Conf.Clear(Word);
636 else if (Tag == "include")
637 {
638 if (Depth > 10)
639 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
640 if (Word.length() > 2 && Word.end()[-1] == '/')
641 {
642 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
643 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
644 }
645 else
646 {
647 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
648 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
649 }
650 }
651 else
652 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
653 }
654 else
655 {
656 // Set the item in the configuration class
657 if (NoWord == false)
658 Conf.Set(Item,Word);
659 }
660
661 // Empty the buffer
662 LineBuffer = string();
663
664 // Move up a tag, but only if there is no bit to parse
665 if (TermChar == '}')
666 {
667 if (StackPos == 0)
668 ParentTag = string();
669 else
670 ParentTag = Stack[--StackPos];
671 }
672
673 }
674 else
675 I++;
676 }
677
678 // Store the fragment
679 const char *Stripd = _strstrip(Buffer);
680 if (*Stripd != 0 && LineBuffer.empty() == false)
681 LineBuffer += " ";
682 LineBuffer += Stripd;
683 }
684
685 if (LineBuffer.empty() == false)
686 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
687 return true;
688 }
689 /*}}}*/
690 // ReadConfigDir - Read a directory of config files /*{{{*/
691 // ---------------------------------------------------------------------
692 /* */
693 bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
694 unsigned Depth)
695 {
696 DIR *D = opendir(Dir.c_str());
697 if (D == 0)
698 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
699
700 vector<string> List;
701
702 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
703 {
704 if (Ent->d_name[0] == '.')
705 continue;
706
707 // Skip bad file names ala run-parts
708 const char *C = Ent->d_name;
709 for (; *C != 0; C++)
710 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
711 break;
712 if (*C != 0)
713 continue;
714
715 // Make sure it is a file and not something else
716 string File = flCombine(Dir,Ent->d_name);
717 struct stat St;
718 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
719 continue;
720
721 List.push_back(File);
722 }
723 closedir(D);
724
725 sort(List.begin(),List.end());
726
727 // Read the files
728 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
729 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
730 return false;
731 return true;
732 }
733 /*}}}*/