]> git.saurik.com Git - apt.git/blame - apt-pkg/contrib/configuration.cc
Restored GCC 2.95 compilability
[apt.git] / apt-pkg / contrib / configuration.cc
CommitLineData
6c139d6e
AL
1// -*- mode: cpp; mode: fold -*-
2// Description /*{{{*/
851a45a8 3// $Id: configuration.cc,v 1.22 2001/05/14 05:47:30 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>
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;
9c14e3d6 110 I->Tag = string(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)
161 return string();
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{
b2e465d6 176 const Item *Itm = Lookup(Name);
9c14e3d6
AL
177 if (Itm == 0 || Itm->Value.empty() == true)
178 {
179 if (Default == 0)
180 return string();
181 else
182 return Default;
183 }
184
b2e465d6
AL
185 string val = Itm->Value;
186 while (Itm->Parent != 0 && Itm->Parent->Value.empty() == false)
187 {
188 // Absolute
189 if (val.length() >= 1 && val[0] == '/')
190 break;
191
192 // ~/foo or ./foo
193 if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
194 break;
195
196 // ../foo
197 if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
198 break;
199
200 if (Itm->Parent->Value.end()[-1] != '/')
201 val.insert(0, "/");
202
203 val.insert(0, Itm->Parent->Value);
204 Itm = Itm->Parent;
205 }
206
207 return val;
9c14e3d6
AL
208}
209 /*}}}*/
3b5421b4
AL
210// Configuration::FindDir - Find a directory name /*{{{*/
211// ---------------------------------------------------------------------
212/* This is like findfile execept the result is terminated in a / */
b2e465d6 213string Configuration::FindDir(const char *Name,const char *Default) const
3b5421b4
AL
214{
215 string Res = FindFile(Name,Default);
216 if (Res.end()[-1] != '/')
217 return Res + '/';
218 return Res;
219}
220 /*}}}*/
6c139d6e
AL
221// Configuration::FindI - Find an integer value /*{{{*/
222// ---------------------------------------------------------------------
223/* */
b2e465d6 224int Configuration::FindI(const char *Name,int Default) const
6c139d6e 225{
b2e465d6 226 const Item *Itm = Lookup(Name);
6c139d6e
AL
227 if (Itm == 0 || Itm->Value.empty() == true)
228 return Default;
229
230 char *End;
231 int Res = strtol(Itm->Value.c_str(),&End,0);
232 if (End == Itm->Value.c_str())
233 return Default;
234
08e8f724
AL
235 return Res;
236}
237 /*}}}*/
238// Configuration::FindB - Find a boolean type /*{{{*/
239// ---------------------------------------------------------------------
240/* */
b2e465d6 241bool Configuration::FindB(const char *Name,bool Default) const
08e8f724 242{
b2e465d6 243 const Item *Itm = Lookup(Name);
08e8f724
AL
244 if (Itm == 0 || Itm->Value.empty() == true)
245 return Default;
246
3b5421b4 247 return StringToBool(Itm->Value,Default);
6c139d6e
AL
248}
249 /*}}}*/
b2e465d6
AL
250// Configuration::FindAny - Find an arbitrary type /*{{{*/
251// ---------------------------------------------------------------------
252/* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
253string Configuration::FindAny(const char *Name,const char *Default) const
254{
255 string key = Name;
256 char type = 0;
257
258 if (key.size() > 2 && key.end()[-2] == '/')
259 {
260 type = key.end()[-1];
261 key.resize(key.size() - 2);
262 }
263
264 switch (type)
265 {
266 // file
267 case 'f':
268 return FindFile(key.c_str(), Default);
269
270 // directory
271 case 'd':
272 return FindDir(key.c_str(), Default);
273
274 // bool
275 case 'b':
276 return FindB(key, Default) ? "true" : "false";
277
278 // int
279 case 'i':
280 {
281 char buf[16];
500827ed 282 snprintf(buf, sizeof(buf)-1, "%d", FindI(key, atoi(Default)));
b2e465d6
AL
283 return buf;
284 }
285 }
286
287 // fallback
288 return Find(Name, Default);
289}
290 /*}}}*/
291// Configuration::CndSet - Conditinal Set a value /*{{{*/
292// ---------------------------------------------------------------------
293/* This will not overwrite */
294void Configuration::CndSet(const char *Name,string Value)
295{
296 Item *Itm = Lookup(Name,true);
297 if (Itm == 0)
298 return;
299 if (Itm->Value.empty() == true)
300 Itm->Value = Value;
301}
302 /*}}}*/
6c139d6e
AL
303// Configuration::Set - Set a value /*{{{*/
304// ---------------------------------------------------------------------
305/* */
306void Configuration::Set(const char *Name,string Value)
307{
308 Item *Itm = Lookup(Name,true);
309 if (Itm == 0)
310 return;
311 Itm->Value = Value;
312}
313 /*}}}*/
314// Configuration::Set - Set an integer value /*{{{*/
315// ---------------------------------------------------------------------
316/* */
317void Configuration::Set(const char *Name,int Value)
318{
319 Item *Itm = Lookup(Name,true);
320 if (Itm == 0)
321 return;
322 char S[300];
323 snprintf(S,sizeof(S),"%i",Value);
324 Itm->Value = S;
325}
326 /*}}}*/
b2e465d6
AL
327// Configuration::Clear - Clear an entire tree /*{{{*/
328// ---------------------------------------------------------------------
329/* */
330void Configuration::Clear(string Name)
331{
332 Item *Top = Lookup(Name.c_str(),false);
333 if (Top == 0)
334 return;
335
336 Top->Value = string();
337 Item *Stop = Top;
338 Top = Top->Child;
339 Stop->Child = 0;
340 for (; Top != 0;)
341 {
342 if (Top->Child != 0)
343 {
344 Top = Top->Child;
345 continue;
346 }
347
348 while (Top != 0 && Top->Next == 0)
349 {
350 Item *Tmp = Top;
351 Top = Top->Parent;
352 delete Tmp;
353
354 if (Top == Stop)
355 return;
356 }
357
358 Item *Tmp = Top;
359 if (Top != 0)
360 Top = Top->Next;
361 delete Tmp;
362 }
363}
364 /*}}}*/
08e8f724
AL
365// Configuration::Exists - Returns true if the Name exists /*{{{*/
366// ---------------------------------------------------------------------
367/* */
b2e465d6 368bool Configuration::Exists(const char *Name) const
08e8f724 369{
b2e465d6 370 const Item *Itm = Lookup(Name);
08e8f724
AL
371 if (Itm == 0)
372 return false;
373 return true;
374}
375 /*}}}*/
b2e465d6
AL
376// Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
377// ---------------------------------------------------------------------
378/* qualified by /[fdbi] exists */
379bool Configuration::ExistsAny(const char *Name) const
380{
381 string key = Name;
382
383 if (key.size() > 2 && key.end()[-2] == '/' &&
384 key.find_first_of("fdbi",key.size()-1) < key.size())
385 {
386 key.resize(key.size() - 2);
387 if (Exists(key.c_str()))
388 return true;
389 }
390
391 return Exists(Name);
392}
393 /*}}}*/
93bf083d
AL
394// Configuration::Dump - Dump the config /*{{{*/
395// ---------------------------------------------------------------------
396/* Dump the entire configuration space */
397void Configuration::Dump()
398{
399 /* Write out all of the configuration directives by walking the
400 configuration tree */
b2e465d6 401 const Configuration::Item *Top = Tree(0);
93bf083d
AL
402 for (; Top != 0;)
403 {
404 clog << Top->FullTag() << " \"" << Top->Value << "\";" << endl;
405
406 if (Top->Child != 0)
407 {
408 Top = Top->Child;
409 continue;
410 }
411
412 while (Top != 0 && Top->Next == 0)
413 Top = Top->Parent;
414 if (Top != 0)
415 Top = Top->Next;
b2e465d6 416 }
93bf083d
AL
417}
418 /*}}}*/
08e8f724 419
0a8a80e5
AL
420// Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
421// ---------------------------------------------------------------------
b2e465d6
AL
422/* Stop sets an optional max recursion depth if this item is being viewed as
423 part of a sub tree. */
424string Configuration::Item::FullTag(const Item *Stop) const
0a8a80e5 425{
b2e465d6 426 if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
0a8a80e5 427 return Tag;
b2e465d6 428 return Parent->FullTag(Stop) + "::" + Tag;
0a8a80e5
AL
429}
430 /*}}}*/
431
08e8f724
AL
432// ReadConfigFile - Read a configuration file /*{{{*/
433// ---------------------------------------------------------------------
434/* The configuration format is very much like the named.conf format
b2e465d6
AL
435 used in bind8, in fact this routine can parse most named.conf files.
436 Sectional config files are like bind's named.conf where there are
437 sections like 'zone "foo.org" { .. };' This causes each section to be
438 added in with a tag like "zone::foo.org" instead of being split
6df63aa6 439 tag/value. AsSectional enables Sectional parsing.*/
b2e465d6
AL
440bool ReadConfigFile(Configuration &Conf,string FName,bool AsSectional,
441 unsigned Depth)
442{
08e8f724 443 // Open the stream for reading
851a45a8 444 ifstream F(FName.c_str(),ios::in);
08e8f724 445 if (!F != 0)
b2e465d6 446 return _error->Errno("ifstream::ifstream",_("Opening configuration file %s"),FName.c_str());
08e8f724
AL
447
448 char Buffer[300];
449 string LineBuffer;
a4e87467
AL
450 string Stack[100];
451 unsigned int StackPos = 0;
08e8f724
AL
452
453 // Parser state
454 string ParentTag;
455
456 int CurLine = 0;
457 bool InComment = false;
458 while (F.eof() == false)
459 {
460 F.getline(Buffer,sizeof(Buffer));
461 CurLine++;
462 _strtabexpand(Buffer,sizeof(Buffer));
463 _strstrip(Buffer);
464
465 // Multi line comment
466 if (InComment == true)
467 {
468 for (const char *I = Buffer; *I != 0; I++)
469 {
470 if (*I == '*' && I[1] == '/')
471 {
472 memmove(Buffer,I+2,strlen(I+2) + 1);
473 InComment = false;
474 break;
475 }
476 }
477 if (InComment == true)
478 continue;
479 }
480
481 // Discard single line comments
bfd22fc0 482 bool InQuote = false;
08e8f724
AL
483 for (char *I = Buffer; *I != 0; I++)
484 {
bfd22fc0
AL
485 if (*I == '"')
486 InQuote = !InQuote;
487 if (InQuote == true)
488 continue;
489
08e8f724
AL
490 if (*I == '/' && I[1] == '/')
491 {
492 *I = 0;
493 break;
494 }
495 }
7834cb57 496
08e8f724 497 // Look for multi line comments
7834cb57 498 InQuote = false;
08e8f724
AL
499 for (char *I = Buffer; *I != 0; I++)
500 {
bfd22fc0
AL
501 if (*I == '"')
502 InQuote = !InQuote;
503 if (InQuote == true)
504 continue;
505
08e8f724
AL
506 if (*I == '/' && I[1] == '*')
507 {
508 InComment = true;
509 for (char *J = Buffer; *J != 0; J++)
510 {
511 if (*J == '*' && J[1] == '/')
512 {
513 memmove(I,J+2,strlen(J+2) + 1);
514 InComment = false;
515 break;
516 }
517 }
518
519 if (InComment == true)
520 {
521 *I = 0;
522 break;
523 }
524 }
525 }
526
527 // Blank
528 if (Buffer[0] == 0)
529 continue;
530
531 // We now have a valid line fragment
7834cb57 532 InQuote = false;
08e8f724
AL
533 for (char *I = Buffer; *I != 0;)
534 {
7834cb57
AL
535 if (*I == '"')
536 InQuote = !InQuote;
537
538 if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
08e8f724 539 {
b2e465d6 540 // Put the last fragment into the buffer
08e8f724
AL
541 char *Start = Buffer;
542 char *Stop = I;
543 for (; Start != I && isspace(*Start) != 0; Start++);
544 for (; Stop != Start && isspace(Stop[-1]) != 0; Stop--);
545 if (LineBuffer.empty() == false && Stop - Start != 0)
546 LineBuffer += ' ';
547 LineBuffer += string(Start,Stop - Start);
548
549 // Remove the fragment
550 char TermChar = *I;
551 memmove(Buffer,I + 1,strlen(I + 1) + 1);
552 I = Buffer;
08e8f724
AL
553
554 // Syntax Error
555 if (TermChar == '{' && LineBuffer.empty() == true)
b2e465d6 556 return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
08e8f724 557
b2e465d6 558 // No string on this line
08e8f724 559 if (LineBuffer.empty() == true)
b2e465d6
AL
560 {
561 if (TermChar == '}')
562 {
563 if (StackPos == 0)
564 ParentTag = string();
565 else
566 ParentTag = Stack[--StackPos];
567 }
08e8f724 568 continue;
b2e465d6
AL
569 }
570
08e8f724 571 // Parse off the tag
7f25bdff
AL
572 string Tag;
573 const char *Pos = LineBuffer.c_str();
574 if (ParseQuoteWord(Pos,Tag) == false)
b2e465d6
AL
575 return _error->Error(_("Syntax error %s:%u: Malformed Tag"),FName.c_str(),CurLine);
576
577 // Parse off the word
578 string Word;
bcae6dd4 579 bool NoWord = false;
b2e465d6
AL
580 if (ParseCWord(Pos,Word) == false &&
581 ParseQuoteWord(Pos,Word) == false)
582 {
583 if (TermChar != '{')
584 {
585 Word = Tag;
586 Tag = "";
bcae6dd4 587 }
4ae405e9
AL
588 else
589 NoWord = true;
b2e465d6
AL
590 }
591 if (strlen(Pos) != 0)
592 return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
593
08e8f724
AL
594 // Go down a level
595 if (TermChar == '{')
596 {
a4e87467
AL
597 if (StackPos <= 100)
598 Stack[StackPos++] = ParentTag;
b2e465d6
AL
599
600 /* Make sectional tags incorperate the section into the
601 tag string */
602 if (AsSectional == true && Word.empty() == false)
603 {
604 Tag += "::" ;
605 Tag += Word;
606 Word = "";
607 }
608
08e8f724
AL
609 if (ParentTag.empty() == true)
610 ParentTag = Tag;
611 else
612 ParentTag += string("::") + Tag;
613 Tag = string();
614 }
bfd22fc0 615
08e8f724
AL
616 // Generate the item name
617 string Item;
618 if (ParentTag.empty() == true)
619 Item = Tag;
620 else
621 {
7f25bdff 622 if (TermChar != '{' || Tag.empty() == false)
08e8f724 623 Item = ParentTag + "::" + Tag;
7f25bdff
AL
624 else
625 Item = ParentTag;
08e8f724
AL
626 }
627
b2e465d6
AL
628 // Specials
629 if (Tag.length() >= 1 && Tag[0] == '#')
630 {
631 if (ParentTag.empty() == false)
632 return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
633 Tag.erase(Tag.begin());
634 if (Tag == "clear")
635 Conf.Clear(Word);
636 else if (Tag == "include")
637 {
638 if (Depth > 10)
639 return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
640 if (Word.length() > 2 && Word.end()[-1] == '/')
641 {
642 if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
643 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
644 }
645 else
646 {
647 if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
648 return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
649 }
650 }
651 else
652 return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
653 }
654 else
655 {
656 // Set the item in the configuration class
bcae6dd4
AL
657 if (NoWord == false)
658 Conf.Set(Item,Word);
b2e465d6
AL
659 }
660
08e8f724
AL
661 // Empty the buffer
662 LineBuffer = string();
b2e465d6
AL
663
664 // Move up a tag, but only if there is no bit to parse
665 if (TermChar == '}')
666 {
667 if (StackPos == 0)
668 ParentTag = string();
669 else
670 ParentTag = Stack[--StackPos];
671 }
672
08e8f724
AL
673 }
674 else
675 I++;
676 }
677
678 // Store the fragment
679 const char *Stripd = _strstrip(Buffer);
680 if (*Stripd != 0 && LineBuffer.empty() == false)
681 LineBuffer += " ";
682 LineBuffer += Stripd;
683 }
b2e465d6
AL
684
685 if (LineBuffer.empty() == false)
686 return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
687 return true;
688}
689 /*}}}*/
690// ReadConfigDir - Read a directory of config files /*{{{*/
691// ---------------------------------------------------------------------
692/* */
693bool ReadConfigDir(Configuration &Conf,string Dir,bool AsSectional,
694 unsigned Depth)
695{
b2e465d6
AL
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 {
721e08d6 704 if (Ent->d_name[0] == '.')
b2e465d6
AL
705 continue;
706
bb3a5465
AL
707 // Skip bad file names ala run-parts
708 const char *C = Ent->d_name;
709 for (; *C != 0; C++)
710 if (isalpha(*C) == 0 && isdigit(*C) == 0 && *C != '_' && *C != '-')
b2e465d6 711 break;
bb3a5465 712 if (*C != 0)
b2e465d6
AL
713 continue;
714
715 // Make sure it is a file and not something else
716 string File = flCombine(Dir,Ent->d_name);
717 struct stat St;
718 if (stat(File.c_str(),&St) != 0 || S_ISREG(St.st_mode) == 0)
719 continue;
720
721 List.push_back(File);
722 }
723 closedir(D);
08e8f724 724
b2e465d6
AL
725 sort(List.begin(),List.end());
726
727 // Read the files
728 for (vector<string>::const_iterator I = List.begin(); I != List.end(); I++)
729 if (ReadConfigFile(Conf,*I,AsSectional,Depth) == false)
730 return false;
08e8f724
AL
731 return true;
732}
733 /*}}}*/