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