]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
Fixed the resetting of Dir with "dir {};". Closes: #87323
[apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
bcae6dd4 3// $Id: configuration.cc,v 1.17 2001/03/04 00:44:35 jgg Exp $
6c139d6e
AL
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__
094a497d 16#pragma implementation "apt-pkg/configuration.h"
6c139d6e 17#endif
094a497d 18#include <apt-pkg/configuration.h>
08e8f724 19#include <apt-pkg/error.h>
cdcc6d34 20#include <apt-pkg/strutl.h>
b2e465d6
AL
21#include <apt-pkg/fileutl.h>
22#include <apti18n.h>
6c139d6e 23
b2e465d6
AL
24#include <vector>
25#include <algorithm>
26#include <fstream>
27
6c139d6e 28#include <stdio.h>
b2e465d6
AL
29#include <dirent.h>
30#include <sys/stat.h>
31#include <unistd.h>
6c139d6e 32 /*}}}*/
9c14e3d6
AL
33
34Configuration *_config = new Configuration;
6c139d6e
AL
35
36// Configuration::Configuration - Constructor /*{{{*/
37// ---------------------------------------------------------------------
38/* */
b2e465d6 39Configuration::Configuration() : ToFree(true)
6c139d6e
AL
40{
41 Root = new Item;
b2e465d6
AL
42}
43Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
44{
45};
46
47 /*}}}*/
48// Configuration::~Configuration - Destructor /*{{{*/
49// ---------------------------------------------------------------------
50/* */
51Configuration::~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 }
6c139d6e
AL
78}
79 /*}}}*/
0a8a80e5 80// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
81// ---------------------------------------------------------------------
82/* This will lookup a single item by name below another item. It is a
83 helper function for the main lookup function */
84Configuration::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;
9c14e3d6 90
7f25bdff
AL
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
6c139d6e
AL
101 if (Res == 0)
102 return I;
103 if (Create == false)
104 return 0;
105
106 I = new Item;
9c14e3d6 107 I->Tag = string(S,Len);
6c139d6e 108 I->Next = *Last;
9c14e3d6 109 I->Parent = Head;
6c139d6e
AL
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 */
118Configuration::Item *Configuration::Lookup(const char *Name,bool Create)
119{
0a8a80e5
AL
120 if (Name == 0)
121 return Root->Child;
122
6c139d6e
AL
123 const char *Start = Name;
124 const char *End = Start + strlen(Name);
125 const char *TagEnd = Name;
126 Item *Itm = Root;
7f25bdff 127 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
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
7f25bdff
AL
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
6c139d6e 145 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
146 return Itm;
147}
148 /*}}}*/
149// Configuration::Find - Find a value /*{{{*/
150// ---------------------------------------------------------------------
151/* */
b2e465d6 152string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 153{
b2e465d6 154 const Item *Itm = Lookup(Name);
6c139d6e 155 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
156 {
157 if (Default == 0)
158 return string();
159 else
160 return Default;
161 }
162
6c139d6e
AL
163 return Itm->Value;
164}
165 /*}}}*/
3b5421b4 166// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
167// ---------------------------------------------------------------------
168/* Directories are stored as the base dir in the Parent node and the
3b5421b4 169 sub directory in sub nodes with the final node being the end filename
9c14e3d6 170 */
b2e465d6 171string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 172{
b2e465d6 173 const Item *Itm = Lookup(Name);
9c14e3d6
AL
174 if (Itm == 0 || Itm->Value.empty() == true)
175 {
176 if (Default == 0)
177 return string();
178 else
179 return Default;
180 }
181
b2e465d6
AL
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;
9c14e3d6
AL
205}
206 /*}}}*/
3b5421b4
AL
207// Configuration::FindDir - Find a directory name /*{{{*/
208// ---------------------------------------------------------------------
209/* This is like findfile execept the result is terminated in a / */
b2e465d6 210string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
211{
212 string Res = FindFile(Name,Default);
213 if (Res.end()[-1] != '/')
214 return Res + '/';
215 return Res;
216}
217 /*}}}*/
6c139d6e
AL
218// Configuration::FindI - Find an integer value /*{{{*/
219// ---------------------------------------------------------------------
220/* */
b2e465d6 221int Configuration::FindI(const char *Name,int Default) const
6c139d6e 222{
b2e465d6 223 const Item *Itm = Lookup(Name);
6c139d6e
AL
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
08e8f724
AL
232 return Res;
233}
234 /*}}}*/
235// Configuration::FindB - Find a boolean type /*{{{*/
236// ---------------------------------------------------------------------
237/* */
b2e465d6 238bool Configuration::FindB(const char *Name,bool Default) const
08e8f724 239{
b2e465d6 240 const Item *Itm = Lookup(Name);
08e8f724
AL
241 if (Itm == 0 || Itm->Value.empty() == true)
242 return Default;
243
3b5421b4 244 return StringToBool(Itm->Value,Default);
6c139d6e
AL
245}
246 /*}}}*/
b2e465d6
AL
247// Configuration::FindAny - Find an arbitrary type /*{{{*/
248// ---------------------------------------------------------------------
249/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
250string 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 */
291void 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 /*}}}*/
6c139d6e
AL
300// Configuration::Set - Set a value /*{{{*/
301// ---------------------------------------------------------------------
302/* */
303void 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/* */
314void 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 /*}}}*/
b2e465d6
AL
324// Configuration::Clear - Clear an entire tree /*{{{*/
325// ---------------------------------------------------------------------
326/* */
327void 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 /*}}}*/
08e8f724
AL
362// Configuration::Exists - Returns true if the Name exists /*{{{*/
363// ---------------------------------------------------------------------
364/* */
b2e465d6 365bool Configuration::Exists(const char *Name) const
08e8f724 366{
b2e465d6 367 const Item *Itm = Lookup(Name);
08e8f724
AL
368 if (Itm == 0)
369 return false;
370 return true;
371}
372 /*}}}*/
b2e465d6
AL
373// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
374// ---------------------------------------------------------------------
375/* qualified by /[fdbi] exists */
376bool 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 /*}}}*/
93bf083d
AL
391// Configuration::Dump - Dump the config /*{{{*/
392// ---------------------------------------------------------------------
393/* Dump the entire configuration space */
394void Configuration::Dump()
395{
396 /* Write out all of the configuration directives by walking the
397 configuration tree */
b2e465d6 398 const Configuration::Item *Top = Tree(0);
93bf083d
AL
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;
b2e465d6 413 }
93bf083d
AL
414}
415 /*}}}*/
08e8f724 416
0a8a80e5
AL
417// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
418// ---------------------------------------------------------------------
b2e465d6
AL
419/* Stop sets an optional max recursion depth if this item is being viewed as
420 part of a sub tree. */
421string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 422{
b2e465d6 423 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 424 return Tag;
b2e465d6 425 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
426}
427 /*}}}*/
428
08e8f724
AL
429// ReadConfigFile - Read a configuration file /*{{{*/
430// ---------------------------------------------------------------------
431/* The configuration format is very much like the named.conf format
b2e465d6
AL
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. */
437bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
438 unsigned Depth)
439{
08e8f724
AL
440 // Open the stream for reading
441 ifstream F(FName.c_str(),ios::in | ios::nocreate);
442 if (!F != 0)
b2e465d6 443 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
08e8f724
AL
444
445 char Buffer[300];
446 string LineBuffer;
a4e87467
AL
447 string Stack[100];
448 unsigned int StackPos = 0;
08e8f724
AL
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
bfd22fc0 479 bool InQuote = false;
08e8f724
AL
480 for (char *I = Buffer; *I != 0; I++)
481 {
bfd22fc0
AL
482 if (*I == '"')
483 InQuote = !InQuote;
484 if (InQuote == true)
485 continue;
486
08e8f724
AL
487 if (*I == '/' && I[1] == '/')
488 {
489 *I = 0;
490 break;
491 }
492 }
7834cb57 493
08e8f724 494 // Look for multi line comments
7834cb57 495 InQuote = false;
08e8f724
AL
496 for (char *I = Buffer; *I != 0; I++)
497 {
bfd22fc0
AL
498 if (*I == '"')
499 InQuote = !InQuote;
500 if (InQuote == true)
501 continue;
502
08e8f724
AL
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
7834cb57 529 InQuote = false;
08e8f724
AL
530 for (char *I = Buffer; *I != 0;)
531 {
7834cb57
AL
532 if (*I == '"')
533 InQuote = !InQuote;
534
535 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 536 {
b2e465d6 537 // Put the last fragment into the buffer
08e8f724
AL
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;
08e8f724
AL
550
551 // Syntax Error
552 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 553 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 554
b2e465d6 555 // No string on this line
08e8f724 556 if (LineBuffer.empty() == true)
b2e465d6
AL
557 {
558 if (TermChar == '}')
559 {
560 if (StackPos == 0)
561 ParentTag = string();
562 else
563 ParentTag = Stack[--StackPos];
564 }
08e8f724 565 continue;
b2e465d6
AL
566 }
567
08e8f724 568 // Parse off the tag
7f25bdff
AL
569 string Tag;
570 const char *Pos = LineBuffer.c_str();
571 if (ParseQuoteWord(Pos,Tag) == false)
b2e465d6
AL
572 return _error->Error(_("Syntax error %s:%u: Malformed Tag"),FName.c_str(),CurLine);
573
574 // Parse off the word
575 string Word;
bcae6dd4 576 bool NoWord = false;
b2e465d6
AL
577 if (ParseCWord(Pos,Word) == false &&
578 ParseQuoteWord(Pos,Word) == false)
579 {
580 if (TermChar != '{')
581 {
582 Word = Tag;
583 Tag = "";
bcae6dd4
AL
584 }
585 NoWord = true;
b2e465d6
AL
586 }
587 if (strlen(Pos) != 0)
588 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
589
08e8f724
AL
590 // Go down a level
591 if (TermChar == '{')
592 {
a4e87467
AL
593 if (StackPos <= 100)
594 Stack[StackPos++] = ParentTag;
b2e465d6
AL
595
596 /* Make sectional tags incorperate the section into the
597 tag string */
598 if (AsSectional == true && Word.empty() == false)
599 {
600 Tag += "::" ;
601 Tag += Word;
602 Word = "";
603 }
604
08e8f724
AL
605 if (ParentTag.empty() == true)
606 ParentTag = Tag;
607 else
608 ParentTag += string("::") + Tag;
609 Tag = string();
610 }
bfd22fc0 611
08e8f724
AL
612 // Generate the item name
613 string Item;
614 if (ParentTag.empty() == true)
615 Item = Tag;
616 else
617 {
7f25bdff 618 if (TermChar != '{' || Tag.empty() == false)
08e8f724 619 Item = ParentTag + "::" + Tag;
7f25bdff
AL
620 else
621 Item = ParentTag;
08e8f724
AL
622 }
623
b2e465d6
AL
624 // Specials
625 if (Tag.length() >= 1 && Tag[0] == '#')
626 {
627 if (ParentTag.empty() == false)
628 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
629 Tag.erase(Tag.begin());
630 if (Tag == "clear")
631 Conf.Clear(Word);
632 else if (Tag == "include")
633 {
634 if (Depth > 10)
635 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
636 if (Word.length() > 2 && Word.end()[-1] == '/')
637 {
638 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
639 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
640 }
641 else
642 {
643 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
644 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
645 }
646 }
647 else
648 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
649 }
650 else
651 {
652 // Set the item in the configuration class
bcae6dd4
AL
653 if (NoWord == false)
654 Conf.Set(Item,Word);
b2e465d6
AL
655 }
656
08e8f724
AL
657 // Empty the buffer
658 LineBuffer = string();
b2e465d6
AL
659
660 // Move up a tag, but only if there is no bit to parse
661 if (TermChar == '}')
662 {
663 if (StackPos == 0)
664 ParentTag = string();
665 else
666 ParentTag = Stack[--StackPos];
667 }
668
08e8f724
AL
669 }
670 else
671 I++;
672 }
673
674 // Store the fragment
675 const char *Stripd = _strstrip(Buffer);
676 if (*Stripd != 0 && LineBuffer.empty() == false)
677 LineBuffer += " ";
678 LineBuffer += Stripd;
679 }
b2e465d6
AL
680
681 if (LineBuffer.empty() == false)
682 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
683 return true;
684}
685 /*}}}*/
686// ReadConfigDir - Read a directory of config files /*{{{*/
687// ---------------------------------------------------------------------
688/* */
689bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
690 unsigned Depth)
691{
692 static const char *BadExts[] = {".disabled",".dpkg-old",".dpkg-dist",
693 ".rpmsave",".rpmorig","~",",v",0};
694
695 DIR *D = opendir(Dir.c_str());
696 if (D == 0)
697 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
698
699 vector<string> List;
700
701 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
702 {
721e08d6 703 if (Ent->d_name[0] == '.')
b2e465d6
AL
704 continue;
705
706 // Skip bad extensions
707 const char **I;
708 for (I = BadExts; *I != 0; I++)
709 {
710 if (strcmp(Ent->d_name + strlen(Ent->d_name) - strlen(*I),*I) == 0)
711 break;
712 }
713
714 if (*I != 0)
715 continue;
716
717 // Make sure it is a file and not something else
718 string File = flCombine(Dir,Ent->d_name);
719 struct stat St;
720 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
721 continue;
722
723 List.push_back(File);
724 }
725 closedir(D);
08e8f724 726
b2e465d6
AL
727 sort(List.begin(),List.end());
728
729 // Read the files
730 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
731 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
732 return false;
08e8f724
AL
733 return true;
734}
735 /*}}}*/