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