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