]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
Fix ftp(archive(1) documentation of -o
[apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
5e14c452 3// $Id: configuration.cc,v 1.28 2004/04/30 04:00:15 mdz 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.
7da2b375
AL
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>.
6c139d6e
AL
14
15 ##################################################################### */
16 /*}}}*/
17// Include files /*{{{*/
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>
851a45a8 27#include <iostream>
b2e465d6 28
6c139d6e 29#include <stdio.h>
b2e465d6
AL
30#include <dirent.h>
31#include <sys/stat.h>
32#include <unistd.h>
851a45a8
AL
33
34using namespace std;
6c139d6e 35 /*}}}*/
9c14e3d6
AL
36
37Configuration *_config = new Configuration;
6c139d6e
AL
38
39// Configuration::Configuration - Constructor /*{{{*/
40// ---------------------------------------------------------------------
41/* */
b2e465d6 42Configuration::Configuration() : ToFree(true)
6c139d6e
AL
43{
44 Root = new Item;
b2e465d6
AL
45}
46Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
47{
48};
49
50 /*}}}*/
51// Configuration::~Configuration - Destructor /*{{{*/
52// ---------------------------------------------------------------------
53/* */
54Configuration::~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 }
6c139d6e
AL
81}
82 /*}}}*/
0a8a80e5 83// Configuration::Lookup - Lookup a single item /*{{{*/
6c139d6e
AL
84// ---------------------------------------------------------------------
85/* This will lookup a single item by name below another item. It is a
86 helper function for the main lookup function */
87Configuration::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;
9c14e3d6 93
7f25bdff
AL
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)
851a45a8 98 if ((Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
7f25bdff
AL
99 break;
100 }
101 else
102 for (; I != 0; Last = &I->Next, I = I->Next);
103
6c139d6e
AL
104 if (Res == 0)
105 return I;
106 if (Create == false)
107 return 0;
108
109 I = new Item;
171c75f1 110 I->Tag.assign(S,Len);
6c139d6e 111 I->Next = *Last;
9c14e3d6 112 I->Parent = Head;
6c139d6e
AL
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 */
121Configuration::Item *Configuration::Lookup(const char *Name,bool Create)
122{
0a8a80e5
AL
123 if (Name == 0)
124 return Root->Child;
125
6c139d6e
AL
126 const char *Start = Name;
127 const char *End = Start + strlen(Name);
128 const char *TagEnd = Name;
129 Item *Itm = Root;
7f25bdff 130 for (; End - TagEnd >= 2; TagEnd++)
6c139d6e
AL
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
7f25bdff
AL
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
6c139d6e 148 Itm = Lookup(Itm,Start,End - Start,Create);
6c139d6e
AL
149 return Itm;
150}
151 /*}}}*/
152// Configuration::Find - Find a value /*{{{*/
153// ---------------------------------------------------------------------
154/* */
b2e465d6 155string Configuration::Find(const char *Name,const char *Default) const
6c139d6e 156{
b2e465d6 157 const Item *Itm = Lookup(Name);
6c139d6e 158 if (Itm == 0 || Itm->Value.empty() == true)
9c14e3d6
AL
159 {
160 if (Default == 0)
171c75f1 161 return "";
9c14e3d6
AL
162 else
163 return Default;
164 }
165
6c139d6e
AL
166 return Itm->Value;
167}
168 /*}}}*/
3b5421b4 169// Configuration::FindFile - Find a Filename /*{{{*/
9c14e3d6
AL
170// ---------------------------------------------------------------------
171/* Directories are stored as the base dir in the Parent node and the
3b5421b4 172 sub directory in sub nodes with the final node being the end filename
9c14e3d6 173 */
b2e465d6 174string Configuration::FindFile(const char *Name,const char *Default) const
9c14e3d6 175{
db2cca11
OS
176 const Item *RootItem = Lookup("RootDir");
177 std::string rootDir = (RootItem == 0) ? "" : RootItem->Value;
178 if(rootDir.size() > 0 && rootDir[rootDir.size() - 1] != '/')
179 rootDir.push_back('/');
180
b2e465d6 181 const Item *Itm = Lookup(Name);
9c14e3d6
AL
182 if (Itm == 0 || Itm->Value.empty() == true)
183 {
184 if (Default == 0)
17b3813c 185 return rootDir;
9c14e3d6 186 else
17b3813c 187 return rootDir + Default;
9c14e3d6
AL
188 }
189
b2e465d6
AL
190 string val = Itm->Value;
191 while (Itm->Parent != 0 && Itm->Parent->Value.empty() == false)
192 {
193 // Absolute
194 if (val.length() >= 1 && val[0] == '/')
195 break;
196
197 // ~/foo or ./foo
198 if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
199 break;
200
201 // ../foo
202 if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
203 break;
204
205 if (Itm->Parent->Value.end()[-1] != '/')
206 val.insert(0, "/");
207
208 val.insert(0, Itm->Parent->Value);
209 Itm = Itm->Parent;
210 }
211
db2cca11 212 return rootDir + val;
9c14e3d6
AL
213}
214 /*}}}*/
3b5421b4
AL
215// Configuration::FindDir - Find a directory name /*{{{*/
216// ---------------------------------------------------------------------
217/* This is like findfile execept the result is terminated in a / */
b2e465d6 218string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
219{
220 string Res = FindFile(Name,Default);
221 if (Res.end()[-1] != '/')
222 return Res + '/';
223 return Res;
224}
225 /*}}}*/
50c409c4
DK
226// Configuration::FindVector - Find a vector of values /*{{{*/
227// ---------------------------------------------------------------------
228/* Returns a vector of config values under the given item */
229vector<string> Configuration::FindVector(const char *Name) const
230{
231 vector<string> Vec;
232 const Item *Top = Lookup(Name);
233 if (Top == NULL)
234 return Vec;
235
236 Item *I = Top->Child;
237 while(I != NULL)
238 {
239 Vec.push_back(I->Value);
240 I = I->Next;
241 }
242 return Vec;
243}
244 /*}}}*/
6c139d6e
AL
245// Configuration::FindI - Find an integer value /*{{{*/
246// ---------------------------------------------------------------------
247/* */
b2e465d6 248int Configuration::FindI(const char *Name,int Default) const
6c139d6e 249{
b2e465d6 250 const Item *Itm = Lookup(Name);
6c139d6e
AL
251 if (Itm == 0 || Itm->Value.empty() == true)
252 return Default;
253
254 char *End;
255 int Res = strtol(Itm->Value.c_str(),&End,0);
256 if (End == Itm->Value.c_str())
257 return Default;
258
08e8f724
AL
259 return Res;
260}
261 /*}}}*/
262// Configuration::FindB - Find a boolean type /*{{{*/
263// ---------------------------------------------------------------------
264/* */
b2e465d6 265bool Configuration::FindB(const char *Name,bool Default) const
08e8f724 266{
b2e465d6 267 const Item *Itm = Lookup(Name);
08e8f724
AL
268 if (Itm == 0 || Itm->Value.empty() == true)
269 return Default;
270
3b5421b4 271 return StringToBool(Itm->Value,Default);
6c139d6e
AL
272}
273 /*}}}*/
b2e465d6
AL
274// Configuration::FindAny - Find an arbitrary type /*{{{*/
275// ---------------------------------------------------------------------
276/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
277string Configuration::FindAny(const char *Name,const char *Default) const
278{
279 string key = Name;
280 char type = 0;
281
282 if (key.size() > 2 && key.end()[-2] == '/')
283 {
284 type = key.end()[-1];
285 key.resize(key.size() - 2);
286 }
287
288 switch (type)
289 {
290 // file
291 case 'f':
292 return FindFile(key.c_str(), Default);
293
294 // directory
295 case 'd':
296 return FindDir(key.c_str(), Default);
297
298 // bool
299 case 'b':
300 return FindB(key, Default) ? "true" : "false";
301
302 // int
303 case 'i':
304 {
305 char buf[16];
cc99a102 306 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
b2e465d6
AL
307 return buf;
308 }
309 }
310
311 // fallback
312 return Find(Name, Default);
313}
314 /*}}}*/
315// Configuration::CndSet - Conditinal Set a value /*{{{*/
316// ---------------------------------------------------------------------
317/* This will not overwrite */
171c75f1 318void Configuration::CndSet(const char *Name,const string &Value)
b2e465d6
AL
319{
320 Item *Itm = Lookup(Name,true);
321 if (Itm == 0)
322 return;
323 if (Itm->Value.empty() == true)
324 Itm->Value = Value;
325}
326 /*}}}*/
6c139d6e
AL
327// Configuration::Set - Set a value /*{{{*/
328// ---------------------------------------------------------------------
329/* */
171c75f1 330void Configuration::Set(const char *Name,const string &Value)
6c139d6e
AL
331{
332 Item *Itm = Lookup(Name,true);
333 if (Itm == 0)
334 return;
335 Itm->Value = Value;
336}
337 /*}}}*/
338// Configuration::Set - Set an integer value /*{{{*/
339// ---------------------------------------------------------------------
340/* */
341void Configuration::Set(const char *Name,int Value)
342{
343 Item *Itm = Lookup(Name,true);
344 if (Itm == 0)
345 return;
346 char S[300];
347 snprintf(S,sizeof(S),"%i",Value);
348 Itm->Value = S;
75ef8f14
MV
349}
350 /*}}}*/
351// Configuration::Clear - Clear an single value from a list /*{{{*/
352// ---------------------------------------------------------------------
353/* */
171c75f1 354void Configuration::Clear(const string Name, int Value)
75ef8f14
MV
355{
356 char S[300];
357 snprintf(S,sizeof(S),"%i",Value);
358 Clear(Name, S);
359}
360 /*}}}*/
361// Configuration::Clear - Clear an single value from a list /*{{{*/
362// ---------------------------------------------------------------------
363/* */
171c75f1 364void Configuration::Clear(const string Name, string Value)
75ef8f14
MV
365{
366 Item *Top = Lookup(Name.c_str(),false);
367 if (Top == 0 || Top->Child == 0)
368 return;
369
370 Item *Tmp, *Prev, *I;
371 Prev = I = Top->Child;
372
373 while(I != NULL)
374 {
375 if(I->Value == Value)
376 {
377 Tmp = I;
378 // was first element, point parent to new first element
379 if(Top->Child == Tmp)
380 Top->Child = I->Next;
381 I = I->Next;
382 Prev->Next = I;
383 delete Tmp;
384 } else {
385 Prev = I;
386 I = I->Next;
387 }
388 }
389
6c139d6e
AL
390}
391 /*}}}*/
b2e465d6
AL
392// Configuration::Clear - Clear an entire tree /*{{{*/
393// ---------------------------------------------------------------------
394/* */
395void Configuration::Clear(string Name)
396{
397 Item *Top = Lookup(Name.c_str(),false);
75ef8f14 398 if (Top == 0)
b2e465d6 399 return;
75ef8f14 400
171c75f1 401 Top->Value.clear();
b2e465d6
AL
402 Item *Stop = Top;
403 Top = Top->Child;
404 Stop->Child = 0;
405 for (; Top != 0;)
406 {
407 if (Top->Child != 0)
408 {
409 Top = Top->Child;
410 continue;
411 }
412
413 while (Top != 0 && Top->Next == 0)
414 {
415 Item *Tmp = Top;
416 Top = Top->Parent;
417 delete Tmp;
418
419 if (Top == Stop)
420 return;
421 }
422
423 Item *Tmp = Top;
424 if (Top != 0)
425 Top = Top->Next;
426 delete Tmp;
427 }
428}
429 /*}}}*/
08e8f724
AL
430// Configuration::Exists - Returns true if the Name exists /*{{{*/
431// ---------------------------------------------------------------------
432/* */
b2e465d6 433bool Configuration::Exists(const char *Name) const
08e8f724 434{
b2e465d6 435 const Item *Itm = Lookup(Name);
08e8f724
AL
436 if (Itm == 0)
437 return false;
438 return true;
439}
440 /*}}}*/
b2e465d6
AL
441// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
442// ---------------------------------------------------------------------
443/* qualified by /[fdbi] exists */
444bool Configuration::ExistsAny(const char *Name) const
445{
446 string key = Name;
447
8d998026 448 if (key.size() > 2 && key.end()[-2] == '/')
ff6bf1be 449 {
8d998026
AL
450 if (key.find_first_of("fdbi",key.size()-1) < key.size())
451 {
452 key.resize(key.size() - 2);
453 if (Exists(key.c_str()))
454 return true;
455 }
456 else
457 {
69a63027 458 _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
8d998026 459 }
ff6bf1be 460 }
b2e465d6
AL
461 return Exists(Name);
462}
463 /*}}}*/
93bf083d
AL
464// Configuration::Dump - Dump the config /*{{{*/
465// ---------------------------------------------------------------------
466/* Dump the entire configuration space */
ff2a211a 467void Configuration::Dump(ostream& str)
93bf083d
AL
468{
469 /* Write out all of the configuration directives by walking the
470 configuration tree */
b2e465d6 471 const Configuration::Item *Top = Tree(0);
93bf083d
AL
472 for (; Top != 0;)
473 {
ff2a211a 474 str << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
93bf083d
AL
475
476 if (Top->Child != 0)
477 {
478 Top = Top->Child;
479 continue;
480 }
481
482 while (Top != 0 && Top->Next == 0)
483 Top = Top->Parent;
484 if (Top != 0)
485 Top = Top->Next;
b2e465d6 486 }
93bf083d
AL
487}
488 /*}}}*/
08e8f724 489
0a8a80e5
AL
490// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
491// ---------------------------------------------------------------------
b2e465d6
AL
492/* Stop sets an optional max recursion depth if this item is being viewed as
493 part of a sub tree. */
494string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 495{
b2e465d6 496 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 497 return Tag;
b2e465d6 498 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
499}
500 /*}}}*/
501
08e8f724
AL
502// ReadConfigFile - Read a configuration file /*{{{*/
503// ---------------------------------------------------------------------
504/* The configuration format is very much like the named.conf format
b2e465d6
AL
505 used in bind8, in fact this routine can parse most named.conf files.
506 Sectional config files are like bind's named.conf where there are
507 sections like 'zone "foo.org" { .. };' This causes each section to be
508 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 509 tag/value. AsSectional enables Sectional parsing.*/
171c75f1 510bool ReadConfigFile(Configuration &Conf,const string &FName,bool AsSectional,
b2e465d6
AL
511 unsigned Depth)
512{
08e8f724 513 // Open the stream for reading
851a45a8 514 ifstream F(FName.c_str(),ios::in);
08e8f724 515 if (!F != 0)
b2e465d6 516 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
3f807f6c 517
08e8f724 518 string LineBuffer;
a4e87467
AL
519 string Stack[100];
520 unsigned int StackPos = 0;
08e8f724
AL
521
522 // Parser state
523 string ParentTag;
524
525 int CurLine = 0;
526 bool InComment = false;
527 while (F.eof() == false)
528 {
3f807f6c
DB
529 // The raw input line.
530 std::string Input;
531 // The input line with comments stripped.
532 std::string Fragment;
8a27f5da
DB
533
534 // Grab the next line of F and place it in Input.
3f807f6c
DB
535 do
536 {
537 char *Buffer = new char[1024];
538
539 F.clear();
540 F.getline(Buffer,sizeof(Buffer) / 2);
541
542 Input += Buffer;
8aea8c3f 543 delete[] Buffer;
3f807f6c
DB
544 }
545 while (F.fail() && !F.eof());
546
8a27f5da
DB
547 // Expand tabs in the input line and remove leading and trailing
548 // whitespace.
3f807f6c 549 {
3f807f6c
DB
550 const int BufferSize = Input.size() * 8 + 1;
551 char *Buffer = new char[BufferSize];
552 try
553 {
554 memcpy(Buffer, Input.c_str(), Input.size() + 1);
555
556 _strtabexpand(Buffer, BufferSize);
557 _strstrip(Buffer);
558 Input = Buffer;
559 }
560 catch(...)
561 {
562 delete[] Buffer;
563 throw;
564 }
565 delete[] Buffer;
566 }
08e8f724 567 CurLine++;
5e14c452 568
8a27f5da
DB
569 // Now strip comments; if the whole line is contained in a
570 // comment, skip this line.
571
3f807f6c
DB
572 // The first meaningful character in the current fragment; will
573 // be adjusted below as we remove bytes from the front.
574 std::string::const_iterator Start = Input.begin();
575 // The last meaningful character in the current fragment.
576 std::string::const_iterator End = Input.end();
08e8f724
AL
577
578 // Multi line comment
579 if (InComment == true)
580 {
3f807f6c
DB
581 for (std::string::const_iterator I = Start;
582 I != End; ++I)
08e8f724 583 {
3f807f6c 584 if (*I == '*' && I + 1 != End && I[1] == '/')
08e8f724 585 {
3f807f6c 586 Start = I + 2;
08e8f724
AL
587 InComment = false;
588 break;
589 }
590 }
591 if (InComment == true)
592 continue;
593 }
594
595 // Discard single line comments
bfd22fc0 596 bool InQuote = false;
3f807f6c
DB
597 for (std::string::const_iterator I = Start;
598 I != End; ++I)
08e8f724 599 {
bfd22fc0
AL
600 if (*I == '"')
601 InQuote = !InQuote;
602 if (InQuote == true)
603 continue;
c914647f
DK
604
605 if ((*I == '/' && I + 1 != End && I[1] == '/') ||
606 (*I == '#' && strcmp(string(I,I+6).c_str(),"#clear") != 0 &&
607 strcmp(string(I,I+8).c_str(),"#include") != 0))
608 {
3f807f6c 609 End = I;
08e8f724
AL
610 break;
611 }
612 }
7834cb57 613
3f807f6c
DB
614 // Look for multi line comments and build up the
615 // fragment.
616 Fragment.reserve(End - Start);
7834cb57 617 InQuote = false;
3f807f6c
DB
618 for (std::string::const_iterator I = Start;
619 I != End; ++I)
08e8f724 620 {
bfd22fc0
AL
621 if (*I == '"')
622 InQuote = !InQuote;
623 if (InQuote == true)
3f807f6c
DB
624 Fragment.push_back(*I);
625 else if (*I == '/' && I + 1 != End && I[1] == '*')
08e8f724
AL
626 {
627 InComment = true;
3f807f6c
DB
628 for (std::string::const_iterator J = I;
629 J != End; ++J)
08e8f724 630 {
3f807f6c 631 if (*J == '*' && J + 1 != End && J[1] == '/')
08e8f724 632 {
3f807f6c
DB
633 // Pretend we just finished walking over the
634 // comment, and don't add anything to the output
635 // fragment.
636 I = J + 1;
08e8f724
AL
637 InComment = false;
638 break;
639 }
640 }
641
642 if (InComment == true)
3f807f6c 643 break;
08e8f724 644 }
3f807f6c
DB
645 else
646 Fragment.push_back(*I);
08e8f724 647 }
3f807f6c
DB
648
649 // Skip blank lines.
650 if (Fragment.empty())
08e8f724
AL
651 continue;
652
8a27f5da 653 // The line has actual content; interpret what it means.
7834cb57 654 InQuote = false;
3f807f6c
DB
655 Start = Fragment.begin();
656 End = Fragment.end();
657 for (std::string::const_iterator I = Start;
658 I != End; ++I)
08e8f724 659 {
7834cb57
AL
660 if (*I == '"')
661 InQuote = !InQuote;
662
663 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 664 {
b2e465d6 665 // Put the last fragment into the buffer
a4dcbf1c
DB
666 std::string::const_iterator NonWhitespaceStart = Start;
667 std::string::const_iterator NonWhitespaceStop = I;
668 for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; NonWhitespaceStart++)
3f807f6c 669 ;
a4dcbf1c 670 for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; NonWhitespaceStop--)
3f807f6c 671 ;
a4dcbf1c 672 if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
08e8f724 673 LineBuffer += ' ';
a4dcbf1c 674 LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
3f807f6c
DB
675
676 // Drop this from the input string, saving the character
677 // that terminated the construct we just closed. (i.e., a
678 // brace or a semicolon)
08e8f724 679 char TermChar = *I;
3f807f6c 680 Start = I + 1;
08e8f724
AL
681
682 // Syntax Error
683 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 684 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 685
b2e465d6 686 // No string on this line
08e8f724 687 if (LineBuffer.empty() == true)
b2e465d6
AL
688 {
689 if (TermChar == '}')
690 {
691 if (StackPos == 0)
692 ParentTag = string();
693 else
694 ParentTag = Stack[--StackPos];
695 }
08e8f724 696 continue;
b2e465d6
AL
697 }
698
08e8f724 699 // Parse off the tag
7f25bdff
AL
700 string Tag;
701 const char *Pos = LineBuffer.c_str();
702 if (ParseQuoteWord(Pos,Tag) == false)
db0db9fe 703 return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
b2e465d6
AL
704
705 // Parse off the word
706 string Word;
bcae6dd4 707 bool NoWord = false;
b2e465d6
AL
708 if (ParseCWord(Pos,Word) == false &&
709 ParseQuoteWord(Pos,Word) == false)
710 {
711 if (TermChar != '{')
712 {
713 Word = Tag;
714 Tag = "";
bcae6dd4 715 }
4ae405e9
AL
716 else
717 NoWord = true;
b2e465d6
AL
718 }
719 if (strlen(Pos) != 0)
720 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
721
08e8f724
AL
722 // Go down a level
723 if (TermChar == '{')
724 {
a4e87467
AL
725 if (StackPos <= 100)
726 Stack[StackPos++] = ParentTag;
b2e465d6
AL
727
728 /* Make sectional tags incorperate the section into the
729 tag string */
730 if (AsSectional == true && Word.empty() == false)
731 {
732 Tag += "::" ;
733 Tag += Word;
734 Word = "";
735 }
736
08e8f724
AL
737 if (ParentTag.empty() == true)
738 ParentTag = Tag;
739 else
740 ParentTag += string("::") + Tag;
741 Tag = string();
742 }
bfd22fc0 743
08e8f724
AL
744 // Generate the item name
745 string Item;
746 if (ParentTag.empty() == true)
747 Item = Tag;
748 else
749 {
7f25bdff 750 if (TermChar != '{' || Tag.empty() == false)
08e8f724 751 Item = ParentTag + "::" + Tag;
7f25bdff
AL
752 else
753 Item = ParentTag;
08e8f724
AL
754 }
755
b2e465d6
AL
756 // Specials
757 if (Tag.length() >= 1 && Tag[0] == '#')
758 {
759 if (ParentTag.empty() == false)
760 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
761 Tag.erase(Tag.begin());
762 if (Tag == "clear")
763 Conf.Clear(Word);
764 else if (Tag == "include")
765 {
766 if (Depth > 10)
767 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
768 if (Word.length() > 2 && Word.end()[-1] == '/')
769 {
770 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
771 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
772 }
773 else
774 {
775 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
776 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
777 }
778 }
779 else
780 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
781 }
782 else
783 {
784 // Set the item in the configuration class
bcae6dd4
AL
785 if (NoWord == false)
786 Conf.Set(Item,Word);
b2e465d6
AL
787 }
788
08e8f724 789 // Empty the buffer
171c75f1 790 LineBuffer.clear();
b2e465d6
AL
791
792 // Move up a tag, but only if there is no bit to parse
793 if (TermChar == '}')
794 {
795 if (StackPos == 0)
171c75f1 796 ParentTag.clear();
b2e465d6
AL
797 else
798 ParentTag = Stack[--StackPos];
799 }
800
08e8f724 801 }
08e8f724
AL
802 }
803
3f807f6c
DB
804 // Store the remaining text, if any, in the current line buffer.
805
806 // NB: could change this to use string-based operations; I'm
807 // using strstrip now to ensure backwards compatibility.
808 // -- dburrows 2008-04-01
809 {
810 char *Buffer = new char[End - Start + 1];
811 try
812 {
813 std::copy(Start, End, Buffer);
814 Buffer[End - Start] = '\0';
815
816 const char *Stripd = _strstrip(Buffer);
817 if (*Stripd != 0 && LineBuffer.empty() == false)
818 LineBuffer += " ";
819 LineBuffer += Stripd;
820 }
821 catch(...)
822 {
823 delete[] Buffer;
824 throw;
825 }
826 delete[] Buffer;
827 }
08e8f724 828 }
b2e465d6
AL
829
830 if (LineBuffer.empty() == false)
831 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
832 return true;
833}
834 /*}}}*/
835// ReadConfigDir - Read a directory of config files /*{{{*/
836// ---------------------------------------------------------------------
837/* */
171c75f1
MV
838bool ReadConfigDir(Configuration &Conf,const string &Dir,bool AsSectional,
839 unsigned Depth)
b2e465d6 840{
b2e465d6
AL
841 DIR *D = opendir(Dir.c_str());
842 if (D == 0)
843 return _error->Errno("opendir",_("Unable to read %s"),Dir.c_str());
844
845 vector<string> List;
846
847 for (struct dirent *Ent = readdir(D); Ent != 0; Ent = readdir(D))
848 {
721e08d6 849 if (Ent->d_name[0] == '.')
b2e465d6
AL
850 continue;
851
bb3a5465
AL
852 // Skip bad file names ala run-parts
853 const char *C = Ent->d_name;
854 for (; *C != 0; C++)
855 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 856 break;
bb3a5465 857 if (*C != 0)
b2e465d6
AL
858 continue;
859
860 // Make sure it is a file and not something else
861 string File = flCombine(Dir,Ent->d_name);
862 struct stat St;
863 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
864 continue;
865
866 List.push_back(File);
867 }
868 closedir(D);
08e8f724 869
b2e465d6
AL
870 sort(List.begin(),List.end());
871
872 // Read the files
873 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
874 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
875 return false;
08e8f724
AL
876 return true;
877}
878 /*}}}*/