apt  2.2.4
About: Apt (Advanced Package Tool) is a management system for software packages (Debian/Ubuntu). Release series 2.2.
  Fossies Dox: apt-2.2.4.tar.gz  ("unofficial" and yet experimental doxygen-generated source code documentation)  

configuration.cc
Go to the documentation of this file.
1 // -*- mode: cpp; mode: fold -*-
2 // Description /*{{{*/
3 /* ######################################################################
4 
5  Configuration Class
6 
7  This class provides a configuration file and command line parser
8  for a tree-oriented configuration environment. All runtime configuration
9  is stored in here.
10 
11  This source is placed in the Public Domain, do with it what you will
12  It was originally written by Jason Gunthorpe <jgg@debian.org>.
13 
14  ##################################################################### */
15  /*}}}*/
16 // Include files /*{{{*/
17 #include <config.h>
18 
19 #include <apt-pkg/configuration.h>
20 #include <apt-pkg/error.h>
21 #include <apt-pkg/fileutl.h>
22 #include <apt-pkg/macros.h>
23 #include <apt-pkg/strutl.h>
24 #include <apt-pkg/string_view.h>
25 
26 #include <ctype.h>
27 #include <regex.h>
28 #include <stddef.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 
33 #include <algorithm>
34 #include <array>
35 #include <fstream>
36 #include <iterator>
37 #include <numeric>
38 #include <sstream>
39 #include <stack>
40 #include <string>
41 #include <unordered_map>
42 #include <vector>
43 
44 #include <apti18n.h>
45 
46 using namespace std;
47  /*}}}*/
48 
50 
51 /* TODO: This config verification shouldn't be using a static variable
52  but a Cnf-member – but that would need ABI breaks and stuff and for now
53  that really is an apt-dev-only tool, so it isn't that bad that it is
54  unusable and allaround a bit strange */
56 APT_HIDDEN std::unordered_map<std::string, ConfigType> apt_known_config {};
57 static std::string getConfigTypeString(ConfigType const type) /*{{{*/
58 {
59  switch (type)
60  {
61  case ConfigType::UNDEFINED: return "UNDEFINED";
62  case ConfigType::INT: return "INT";
63  case ConfigType::BOOL: return "BOOL";
64  case ConfigType::STRING: return "STRING";
65  case ConfigType::STRING_OR_BOOL: return "STRING_OR_BOOL";
66  case ConfigType::FILE: return "FILE";
67  case ConfigType::DIR: return "DIR";
68  case ConfigType::LIST: return "LIST";
69  case ConfigType::STRING_OR_LIST: return "STRING_OR_LIST";
70  }
71  return "UNKNOWN";
72 }
73  /*}}}*/
74 static ConfigType getConfigType(std::string const &type) /*{{{*/
75 {
76  if (type == "<INT>")
77  return ConfigType::INT;
78  else if (type == "<BOOL>")
79  return ConfigType::BOOL;
80  else if (type == "<STRING>")
81  return ConfigType::STRING;
82  else if (type == "<STRING_OR_BOOL>")
84  else if (type == "<FILE>")
85  return ConfigType::FILE;
86  else if (type == "<DIR>")
87  return ConfigType::DIR;
88  else if (type == "<LIST>")
89  return ConfigType::LIST;
90  else if (type == "<STRING_OR_LIST>")
92  else if (type == "<PROGRAM_PATH>")
93  return ConfigType::PROGRAM_PATH;
94  return ConfigType::UNDEFINED;
95 }
96  /*}}}*/
97 // checkFindConfigOptionType - workhorse of option checking /*{{{*/
98 static void checkFindConfigOptionTypeInternal(std::string name, ConfigType const type)
99 {
100  std::transform(name.begin(), name.end(), name.begin(), ::tolower);
101  auto known = apt_known_config.find(name);
102  if (known == apt_known_config.cend())
103  {
104  auto const rcolon = name.rfind(':');
105  if (rcolon != std::string::npos)
106  {
107  known = apt_known_config.find(name.substr(0, rcolon) + ":*");
108  if (known == apt_known_config.cend())
109  {
110  auto const parts = StringSplit(name, "::");
111  size_t psize = parts.size();
112  if (psize > 1)
113  {
114  for (size_t max = psize; max != 1; --max)
115  {
116  std::ostringstream os;
117  std::copy(parts.begin(), parts.begin() + max, std::ostream_iterator<std::string>(os, "::"));
118  os << "**";
119  known = apt_known_config.find(os.str());
120  if (known != apt_known_config.cend() && known->second == ConfigType::UNDEFINED)
121  return;
122  }
123  for (size_t max = psize - 1; max != 1; --max)
124  {
125  std::ostringstream os;
126  std::copy(parts.begin(), parts.begin() + max - 1, std::ostream_iterator<std::string>(os, "::"));
127  os << "*::";
128  std::copy(parts.begin() + max + 1, parts.end() - 1, std::ostream_iterator<std::string>(os, "::"));
129  os << *(parts.end() - 1);
130  known = apt_known_config.find(os.str());
131  if (known != apt_known_config.cend())
132  break;
133  }
134  }
135  }
136  }
137  }
138  if (known == apt_known_config.cend())
139  _error->Warning("Using unknown config option »%s« of type %s",
140  name.c_str(), getConfigTypeString(type).c_str());
141  else if (known->second != type)
142  {
143  if (known->second == ConfigType::DIR && type == ConfigType::FILE)
144  ; // implementation detail
145  else if (type == ConfigType::STRING && (known->second == ConfigType::FILE || known->second == ConfigType::DIR))
146  ; // TODO: that might be an error or not, we will figure this out later
147  else if (known->second == ConfigType::STRING_OR_BOOL && (type == ConfigType::BOOL || type == ConfigType::STRING))
148  ;
149  else if (known->second == ConfigType::STRING_OR_LIST && (type == ConfigType::LIST || type == ConfigType::STRING))
150  ;
151  else
152  _error->Warning("Using config option »%s« of type %s as a type %s",
153  name.c_str(), getConfigTypeString(known->second).c_str(), getConfigTypeString(type).c_str());
154  }
155 }
156 static void checkFindConfigOptionType(char const * const name, ConfigType const type)
157 {
158  if (apt_known_config.empty())
159  return;
161 }
162  /*}}}*/
163 static bool LoadConfigurationIndex(std::string const &filename) /*{{{*/
164 {
165  apt_known_config.clear();
166  if (filename.empty())
167  return true;
168  Configuration Idx;
169  if (ReadConfigFile(Idx, filename) == false)
170  return false;
171 
172  Configuration::Item const * Top = Idx.Tree(nullptr);
173  if (unlikely(Top == nullptr))
174  return false;
175 
176  do {
177  if (Top->Value.empty() == false)
178  {
179  std::string fulltag = Top->FullTag();
180  std::transform(fulltag.begin(), fulltag.end(), fulltag.begin(), ::tolower);
181  apt_known_config.emplace(std::move(fulltag), getConfigType(Top->Value));
182  }
183 
184  if (Top->Child != nullptr)
185  {
186  Top = Top->Child;
187  continue;
188  }
189 
190  while (Top != nullptr && Top->Next == nullptr)
191  Top = Top->Parent;
192  if (Top != nullptr)
193  Top = Top->Next;
194  } while (Top != nullptr);
195 
196  return true;
197 }
198  /*}}}*/
199 
200 // Configuration::Configuration - Constructor /*{{{*/
201 // ---------------------------------------------------------------------
202 /* */
204 {
205  Root = new Item;
206 }
207 Configuration::Configuration(const Item *Root) : Root((Item *)Root), ToFree(false)
208 {
209 }
210  /*}}}*/
211 // Configuration::~Configuration - Destructor /*{{{*/
212 // ---------------------------------------------------------------------
213 /* */
215 {
216  if (ToFree == false)
217  return;
218 
219  Item *Top = Root;
220  for (; Top != 0;)
221  {
222  if (Top->Child != 0)
223  {
224  Top = Top->Child;
225  continue;
226  }
227 
228  while (Top != 0 && Top->Next == 0)
229  {
230  Item *Parent = Top->Parent;
231  delete Top;
232  Top = Parent;
233  }
234  if (Top != 0)
235  {
236  Item *Next = Top->Next;
237  delete Top;
238  Top = Next;
239  }
240  }
241 }
242  /*}}}*/
243 // Configuration::Lookup - Lookup a single item /*{{{*/
244 // ---------------------------------------------------------------------
245 /* This will lookup a single item by name below another item. It is a
246  helper function for the main lookup function */
248  unsigned long const &Len,bool const &Create)
249 {
250  int Res = 1;
251  Item *I = Head->Child;
252  Item **Last = &Head->Child;
253 
254  // Empty strings match nothing. They are used for lists.
255  if (Len != 0)
256  {
257  for (; I != 0; Last = &I->Next, I = I->Next)
258  if (Len == I->Tag.length() && (Res = stringcasecmp(I->Tag,S,S + Len)) == 0)
259  break;
260  }
261  else
262  for (; I != 0; Last = &I->Next, I = I->Next);
263 
264  if (Res == 0)
265  return I;
266  if (Create == false)
267  return 0;
268 
269  I = new Item;
270  I->Tag.assign(S,Len);
271  I->Next = *Last;
272  I->Parent = Head;
273  *Last = I;
274  return I;
275 }
276  /*}}}*/
277 // Configuration::Lookup - Lookup a fully scoped item /*{{{*/
278 // ---------------------------------------------------------------------
279 /* This performs a fully scoped lookup of a given name, possibly creating
280  new items */
281 Configuration::Item *Configuration::Lookup(const char *Name,bool const &Create)
282 {
283  if (Name == 0)
284  return Root->Child;
285 
286  const char *Start = Name;
287  const char *End = Start + strlen(Name);
288  const char *TagEnd = Name;
289  Item *Itm = Root;
290  for (; End - TagEnd >= 2; TagEnd++)
291  {
292  if (TagEnd[0] == ':' && TagEnd[1] == ':')
293  {
294  Itm = Lookup(Itm,Start,TagEnd - Start,Create);
295  if (Itm == 0)
296  return 0;
297  TagEnd = Start = TagEnd + 2;
298  }
299  }
300 
301  // This must be a trailing ::, we create unique items in a list
302  if (End - Start == 0)
303  {
304  if (Create == false)
305  return 0;
306  }
307 
308  Itm = Lookup(Itm,Start,End - Start,Create);
309  return Itm;
310 }
311  /*}}}*/
312 // Configuration::Find - Find a value /*{{{*/
313 // ---------------------------------------------------------------------
314 /* */
315 string Configuration::Find(const char *Name,const char *Default) const
316 {
318  const Item *Itm = Lookup(Name);
319  if (Itm == 0 || Itm->Value.empty() == true)
320  {
321  if (Default == 0)
322  return "";
323  else
324  return Default;
325  }
326 
327  return Itm->Value;
328 }
329  /*}}}*/
330 // Configuration::FindFile - Find a Filename /*{{{*/
331 // ---------------------------------------------------------------------
332 /* Directories are stored as the base dir in the Parent node and the
333  sub directory in sub nodes with the final node being the end filename
334  */
335 string Configuration::FindFile(const char *Name,const char *Default) const
336 {
338  const Item *RootItem = Lookup("RootDir");
339  std::string result = (RootItem == 0) ? "" : RootItem->Value;
340  if(result.empty() == false && result[result.size() - 1] != '/')
341  result.push_back('/');
342 
343  const Item *Itm = Lookup(Name);
344  if (Itm == 0 || Itm->Value.empty() == true)
345  {
346  if (Default != 0)
347  result.append(Default);
348  }
349  else
350  {
351  string val = Itm->Value;
352  while (Itm->Parent != 0)
353  {
354  if (Itm->Parent->Value.empty() == true)
355  {
356  Itm = Itm->Parent;
357  continue;
358  }
359 
360  // Absolute
361  if (val.length() >= 1 && val[0] == '/')
362  {
363  if (val.compare(0, 9, "/dev/null") == 0)
364  val.erase(9);
365  break;
366  }
367 
368  // ~/foo or ./foo
369  if (val.length() >= 2 && (val[0] == '~' || val[0] == '.') && val[1] == '/')
370  break;
371 
372  // ../foo
373  if (val.length() >= 3 && val[0] == '.' && val[1] == '.' && val[2] == '/')
374  break;
375 
376  if (Itm->Parent->Value.end()[-1] != '/')
377  val.insert(0, "/");
378 
379  val.insert(0, Itm->Parent->Value);
380  Itm = Itm->Parent;
381  }
382  result.append(val);
383  }
384  return flNormalize(result);
385 }
386  /*}}}*/
387 // Configuration::FindDir - Find a directory name /*{{{*/
388 // ---------------------------------------------------------------------
389 /* This is like findfile execept the result is terminated in a / */
390 string Configuration::FindDir(const char *Name,const char *Default) const
391 {
393  string Res = FindFile(Name,Default);
394  if (Res.end()[-1] != '/')
395  {
396  size_t const found = Res.rfind("/dev/null");
397  if (found != string::npos && found == Res.size() - 9)
398  return Res; // /dev/null returning
399  return Res + '/';
400  }
401  return Res;
402 }
403  /*}}}*/
404 // Configuration::FindVector - Find a vector of values /*{{{*/
405 // ---------------------------------------------------------------------
406 /* Returns a vector of config values under the given item */
407 vector<string> Configuration::FindVector(const char *Name, std::string const &Default, bool const Keys) const
408 {
410  vector<string> Vec;
411  const Item *Top = Lookup(Name);
412  if (Top == NULL)
413  return VectorizeString(Default, ',');
414 
415  if (Top->Value.empty() == false)
416  return VectorizeString(Top->Value, ',');
417 
418  Item *I = Top->Child;
419  while(I != NULL)
420  {
421  Vec.push_back(Keys ? I->Tag : I->Value);
422  I = I->Next;
423  }
424  if (Vec.empty() == true)
425  return VectorizeString(Default, ',');
426 
427  return Vec;
428 }
429  /*}}}*/
430 // Configuration::FindI - Find an integer value /*{{{*/
431 // ---------------------------------------------------------------------
432 /* */
433 int Configuration::FindI(const char *Name,int const &Default) const
434 {
436  const Item *Itm = Lookup(Name);
437  if (Itm == 0 || Itm->Value.empty() == true)
438  return Default;
439 
440  char *End;
441  int Res = strtol(Itm->Value.c_str(),&End,0);
442  if (End == Itm->Value.c_str())
443  return Default;
444 
445  return Res;
446 }
447  /*}}}*/
448 // Configuration::FindB - Find a boolean type /*{{{*/
449 // ---------------------------------------------------------------------
450 /* */
451 bool Configuration::FindB(const char *Name,bool const &Default) const
452 {
454  const Item *Itm = Lookup(Name);
455  if (Itm == 0 || Itm->Value.empty() == true)
456  return Default;
457 
458  return StringToBool(Itm->Value,Default);
459 }
460  /*}}}*/
461 // Configuration::FindAny - Find an arbitrary type /*{{{*/
462 // ---------------------------------------------------------------------
463 /* a key suffix of /f, /d, /b or /i calls Find{File,Dir,B,I} */
464 string Configuration::FindAny(const char *Name,const char *Default) const
465 {
466  string key = Name;
467  char type = 0;
468 
469  if (key.size() > 2 && key.end()[-2] == '/')
470  {
471  type = key.end()[-1];
472  key.resize(key.size() - 2);
473  }
474 
475  switch (type)
476  {
477  // file
478  case 'f':
479  return FindFile(key.c_str(), Default);
480 
481  // directory
482  case 'd':
483  return FindDir(key.c_str(), Default);
484 
485  // bool
486  case 'b':
487  return FindB(key, Default) ? "true" : "false";
488 
489  // int
490  case 'i':
491  {
492  char buf[16];
493  snprintf(buf, sizeof(buf)-1, "%d", FindI(key, Default ? atoi(Default) : 0 ));
494  return buf;
495  }
496  }
497 
498  // fallback
499  return Find(Name, Default);
500 }
501  /*}}}*/
502 // Configuration::CndSet - Conditinal Set a value /*{{{*/
503 // ---------------------------------------------------------------------
504 /* This will not overwrite */
505 void Configuration::CndSet(const char *Name,const string &Value)
506 {
507  Item *Itm = Lookup(Name,true);
508  if (Itm == 0)
509  return;
510  if (Itm->Value.empty() == true)
511  Itm->Value = Value;
512 }
513  /*}}}*/
514 // Configuration::Set - Set an integer value /*{{{*/
515 // ---------------------------------------------------------------------
516 /* */
517 void Configuration::CndSet(const char *Name,int const Value)
518 {
519  Item *Itm = Lookup(Name,true);
520  if (Itm == 0 || Itm->Value.empty() == false)
521  return;
522  char S[300];
523  snprintf(S,sizeof(S),"%i",Value);
524  Itm->Value = S;
525 }
526  /*}}}*/
527 // Configuration::Set - Set a value /*{{{*/
528 // ---------------------------------------------------------------------
529 /* */
530 void Configuration::Set(const char *Name,const string &Value)
531 {
532  Item *Itm = Lookup(Name,true);
533  if (Itm == 0)
534  return;
535  Itm->Value = Value;
536 }
537  /*}}}*/
538 // Configuration::Set - Set an integer value /*{{{*/
539 // ---------------------------------------------------------------------
540 /* */
541 void Configuration::Set(const char *Name,int const &Value)
542 {
543  Item *Itm = Lookup(Name,true);
544  if (Itm == 0)
545  return;
546  char S[300];
547  snprintf(S,sizeof(S),"%i",Value);
548  Itm->Value = S;
549 }
550  /*}}}*/
551 // Configuration::Clear - Clear an single value from a list /*{{{*/
552 // ---------------------------------------------------------------------
553 /* */
554 void Configuration::Clear(string const &Name, int const &Value)
555 {
556  char S[300];
557  snprintf(S,sizeof(S),"%i",Value);
558  Clear(Name, S);
559 }
560  /*}}}*/
561 // Configuration::Clear - Clear an single value from a list /*{{{*/
562 // ---------------------------------------------------------------------
563 /* */
564 void Configuration::Clear(string const &Name, string const &Value)
565 {
566  Item *Top = Lookup(Name.c_str(),false);
567  if (Top == 0 || Top->Child == 0)
568  return;
569 
570  Item *Tmp, *Prev, *I;
571  Prev = I = Top->Child;
572 
573  while(I != NULL)
574  {
575  if(I->Value == Value)
576  {
577  Tmp = I;
578  // was first element, point parent to new first element
579  if(Top->Child == Tmp)
580  Top->Child = I->Next;
581  I = I->Next;
582  Prev->Next = I;
583  delete Tmp;
584  } else {
585  Prev = I;
586  I = I->Next;
587  }
588  }
589 
590 }
591  /*}}}*/
592 // Configuration::Clear - Clear everything /*{{{*/
593 // ---------------------------------------------------------------------
595 {
596  const Configuration::Item *Top = Tree(0);
597  while( Top != 0 )
598  {
599  Clear(Top->FullTag());
600  Top = Top->Next;
601  }
602 }
603  /*}}}*/
604 // Configuration::Clear - Clear an entire tree /*{{{*/
605 // ---------------------------------------------------------------------
606 /* */
607 void Configuration::Clear(string const &Name)
608 {
609  Item *Top = Lookup(Name.c_str(),false);
610  if (Top == 0)
611  return;
612 
613  Top->Value.clear();
614  Item *Stop = Top;
615  Top = Top->Child;
616  Stop->Child = 0;
617  for (; Top != 0;)
618  {
619  if (Top->Child != 0)
620  {
621  Top = Top->Child;
622  continue;
623  }
624 
625  while (Top != 0 && Top->Next == 0)
626  {
627  Item *Tmp = Top;
628  Top = Top->Parent;
629  delete Tmp;
630 
631  if (Top == Stop)
632  return;
633  }
634 
635  Item *Tmp = Top;
636  if (Top != 0)
637  Top = Top->Next;
638  delete Tmp;
639  }
640 }
641  /*}}}*/
642 void Configuration::MoveSubTree(char const * const OldRootName, char const * const NewRootName)/*{{{*/
643 {
644  // prevent NewRoot being a subtree of OldRoot
645  if (OldRootName == nullptr)
646  return;
647  if (NewRootName != nullptr)
648  {
649  if (strcmp(OldRootName, NewRootName) == 0)
650  return;
651  std::string const oldroot = std::string(OldRootName) + "::";
652  if (strcasestr(NewRootName, oldroot.c_str()) != NULL)
653  return;
654  }
655 
656  Item * Top;
657  Item const * const OldRoot = Top = Lookup(OldRootName, false);
658  if (Top == nullptr)
659  return;
660  std::string NewRoot;
661  if (NewRootName != nullptr)
662  NewRoot.append(NewRootName).append("::");
663 
664  Top->Value.clear();
665  Item * const Stop = Top;
666  Top = Top->Child;
667  Stop->Child = 0;
668  for (; Top != 0;)
669  {
670  if (Top->Child != 0)
671  {
672  Top = Top->Child;
673  continue;
674  }
675 
676  while (Top != 0 && Top->Next == 0)
677  {
678  Set(NewRoot + Top->FullTag(OldRoot), Top->Value);
679  Item const * const Tmp = Top;
680  Top = Top->Parent;
681  delete Tmp;
682 
683  if (Top == Stop)
684  return;
685  }
686 
687  Set(NewRoot + Top->FullTag(OldRoot), Top->Value);
688  Item const * const Tmp = Top;
689  if (Top != 0)
690  Top = Top->Next;
691  delete Tmp;
692  }
693 }
694  /*}}}*/
695 // Configuration::Exists - Returns true if the Name exists /*{{{*/
696 // ---------------------------------------------------------------------
697 /* */
698 bool Configuration::Exists(const char *Name) const
699 {
700  const Item *Itm = Lookup(Name);
701  if (Itm == 0)
702  return false;
703  return true;
704 }
705  /*}}}*/
706 // Configuration::ExistsAny - Returns true if the Name, possibly /*{{{*/
707 // ---------------------------------------------------------------------
708 /* qualified by /[fdbi] exists */
709 bool Configuration::ExistsAny(const char *Name) const
710 {
711  string key = Name;
712 
713  if (key.size() > 2 && key.end()[-2] == '/')
714  {
715  if (key.find_first_of("fdbi",key.size()-1) < key.size())
716  {
717  key.resize(key.size() - 2);
718  if (Exists(key.c_str()))
719  return true;
720  }
721  else
722  {
723  _error->Warning(_("Unrecognized type abbreviation: '%c'"), key.end()[-3]);
724  }
725  }
726  return Exists(Name);
727 }
728  /*}}}*/
729 // Configuration::Dump - Dump the config /*{{{*/
730 // ---------------------------------------------------------------------
731 /* Dump the entire configuration space */
732 void Configuration::Dump(ostream& str)
733 {
734  Dump(str, NULL, "%F \"%v\";\n", true);
735 }
736 void Configuration::Dump(ostream& str, char const * const root,
737  char const * const formatstr, bool const emptyValue)
738 {
739  const Configuration::Item* Top = Tree(root);
740  if (Top == 0)
741  return;
742  const Configuration::Item* const Root = (root == NULL) ? NULL : Top;
743  std::vector<std::string> const format = VectorizeString(formatstr, '%');
744 
745  /* Write out all of the configuration directives by walking the
746  configuration tree */
747  do {
748  if (emptyValue == true || Top->Value.empty() == emptyValue)
749  {
750  std::vector<std::string>::const_iterator f = format.begin();
751  str << *f;
752  for (++f; f != format.end(); ++f)
753  {
754  if (f->empty() == true)
755  {
756  ++f;
757  str << '%' << *f;
758  continue;
759  }
760  char const type = (*f)[0];
761  if (type == 'f')
762  str << Top->FullTag();
763  else if (type == 't')
764  str << Top->Tag;
765  else if (type == 'v')
766  str << Top->Value;
767  else if (type == 'F')
768  str << QuoteString(Top->FullTag(), "=\"\n");
769  else if (type == 'T')
770  str << QuoteString(Top->Tag, "=\"\n");
771  else if (type == 'V')
772  str << QuoteString(Top->Value, "=\"\n");
773  else if (type == 'n')
774  str << "\n";
775  else if (type == 'N')
776  str << "\t";
777  else
778  str << '%' << type;
779  str << f->c_str() + 1;
780  }
781  }
782 
783  if (Top->Child != 0)
784  {
785  Top = Top->Child;
786  continue;
787  }
788 
789  while (Top != 0 && Top->Next == 0)
790  Top = Top->Parent;
791  if (Top != 0)
792  Top = Top->Next;
793 
794  if (Root != NULL)
795  {
796  const Configuration::Item* I = Top;
797  while(I != 0)
798  {
799  if (I == Root)
800  break;
801  else
802  I = I->Parent;
803  }
804  if (I == 0)
805  break;
806  }
807  } while (Top != 0);
808 }
809  /*}}}*/
810 
811 // Configuration::Item::FullTag - Return the fully scoped tag /*{{{*/
812 // ---------------------------------------------------------------------
813 /* Stop sets an optional max recursion depth if this item is being viewed as
814  part of a sub tree. */
815 string Configuration::Item::FullTag(const Item *Stop) const
816 {
817  if (Parent == 0 || Parent->Parent == 0 || Parent == Stop)
818  return Tag;
819  return Parent->FullTag(Stop) + "::" + Tag;
820 }
821  /*}}}*/
822 
823 // ReadConfigFile - Read a configuration file /*{{{*/
824 // ---------------------------------------------------------------------
825 /* The configuration format is very much like the named.conf format
826  used in bind8, in fact this routine can parse most named.conf files.
827  Sectional config files are like bind's named.conf where there are
828  sections like 'zone "foo.org" { .. };' This causes each section to be
829  added in with a tag like "zone::foo.org" instead of being split
830  tag/value. AsSectional enables Sectional parsing.*/
831 static void leaveCurrentScope(std::stack<std::string> &Stack, std::string &ParentTag)
832 {
833  if (Stack.empty())
834  ParentTag.clear();
835  else
836  {
837  ParentTag = Stack.top();
838  Stack.pop();
839  }
840 }
841 bool ReadConfigFile(Configuration &Conf,const string &FName,bool const &AsSectional,
842  unsigned const &Depth)
843 {
844  // Open the stream for reading
845  FileFd F;
846  if (OpenConfigurationFileFd(FName, F) == false)
847  return false;
848 
849  string LineBuffer;
850  std::stack<std::string> Stack;
851 
852  // Parser state
853  string ParentTag;
854 
855  int CurLine = 0;
856  bool InComment = false;
857  while (F.Eof() == false)
858  {
859  // The raw input line.
860  std::string Input;
861  if (F.ReadLine(Input) == false)
862  Input.clear();
863  // The input line with comments stripped.
864  std::string Fragment;
865 
866  // Expand tabs in the input line and remove leading and trailing whitespace.
867  Input = APT::String::Strip(SubstVar(Input, "\t", " "));
868  CurLine++;
869 
870  // Now strip comments; if the whole line is contained in a
871  // comment, skip this line.
872  APT::StringView Line{Input.data(), Input.size()};
873 
874  // continued Multi line comment
875  if (InComment)
876  {
877  size_t end = Line.find("*/");
878  if (end != APT::StringView::npos)
879  {
880  Line.remove_prefix(end + 2);
881  InComment = false;
882  }
883  else
884  continue;
885  }
886 
887  // Discard single line comments
888  {
889  size_t start = 0;
890  while ((start = Line.find("//", start)) != APT::StringView::npos)
891  {
892  if (std::count(Line.begin(), Line.begin() + start, '"') % 2 != 0)
893  {
894  ++start;
895  continue;
896  }
897  Line.remove_suffix(Line.length() - start);
898  break;
899  }
900  using APT::operator""_sv;
901  constexpr std::array<APT::StringView, 3> magicComments { "clear"_sv, "include"_sv, "x-apt-configure-index"_sv };
902  start = 0;
903  while ((start = Line.find('#', start)) != APT::StringView::npos)
904  {
905  if (std::count(Line.begin(), Line.begin() + start, '"') % 2 != 0 ||
906  std::any_of(magicComments.begin(), magicComments.end(), [&](auto const m) { return Line.compare(start+1, m.length(), m) == 0; }))
907  {
908  ++start;
909  continue;
910  }
911  Line.remove_suffix(Line.length() - start);
912  break;
913  }
914  }
915 
916  // Look for multi line comments and build up the
917  // fragment.
918  Fragment.reserve(Line.length());
919  {
920  size_t start = 0;
921  while ((start = Line.find("/*", start)) != APT::StringView::npos)
922  {
923  if (std::count(Line.begin(), Line.begin() + start, '"') % 2 != 0)
924  {
925  start += 2;
926  continue;
927  }
928  Fragment.append(Line.data(), start);
929  auto const end = Line.find("*/", start + 2);
930  if (end == APT::StringView::npos)
931  {
932  Line.clear();
933  InComment = true;
934  break;
935  }
936  else
937  Line.remove_prefix(end + 2);
938  start = 0;
939  }
940  if (not Line.empty())
941  Fragment.append(Line.data(), Line.length());
942  }
943 
944  // Skip blank lines.
945  if (Fragment.empty())
946  continue;
947 
948  // The line has actual content; interpret what it means.
949  bool InQuote = false;
950  auto Start = Fragment.cbegin();
951  auto End = Fragment.cend();
952  for (std::string::const_iterator I = Start;
953  I != End; ++I)
954  {
955  if (*I == '"')
956  InQuote = !InQuote;
957 
958  if (InQuote == false && (*I == '{' || *I == ';' || *I == '}'))
959  {
960  // Put the last fragment into the buffer
961  std::string::const_iterator NonWhitespaceStart = Start;
962  std::string::const_iterator NonWhitespaceStop = I;
963  for (; NonWhitespaceStart != I && isspace(*NonWhitespaceStart) != 0; ++NonWhitespaceStart)
964  ;
965  for (; NonWhitespaceStop != NonWhitespaceStart && isspace(NonWhitespaceStop[-1]) != 0; --NonWhitespaceStop)
966  ;
967  if (LineBuffer.empty() == false && NonWhitespaceStop - NonWhitespaceStart != 0)
968  LineBuffer += ' ';
969  LineBuffer += string(NonWhitespaceStart, NonWhitespaceStop);
970 
971  // Drop this from the input string, saving the character
972  // that terminated the construct we just closed. (i.e., a
973  // brace or a semicolon)
974  char TermChar = *I;
975  Start = I + 1;
976 
977  // Syntax Error
978  if (TermChar == '{' && LineBuffer.empty() == true)
979  return _error->Error(_("Syntax error %s:%u: Block starts with no name."),FName.c_str(),CurLine);
980 
981  // No string on this line
982  if (LineBuffer.empty() == true)
983  {
984  if (TermChar == '}')
985  leaveCurrentScope(Stack, ParentTag);
986  continue;
987  }
988 
989  // Parse off the tag
990  string Tag;
991  const char *Pos = LineBuffer.c_str();
992  if (ParseQuoteWord(Pos,Tag) == false)
993  return _error->Error(_("Syntax error %s:%u: Malformed tag"),FName.c_str(),CurLine);
994 
995  // Parse off the word
996  string Word;
997  bool NoWord = false;
998  if (ParseCWord(Pos,Word) == false &&
999  ParseQuoteWord(Pos,Word) == false)
1000  {
1001  if (TermChar != '{')
1002  {
1003  Word = Tag;
1004  Tag = "";
1005  }
1006  else
1007  NoWord = true;
1008  }
1009  if (strlen(Pos) != 0)
1010  return _error->Error(_("Syntax error %s:%u: Extra junk after value"),FName.c_str(),CurLine);
1011 
1012  // Go down a level
1013  if (TermChar == '{')
1014  {
1015  Stack.push(ParentTag);
1016 
1017  /* Make sectional tags incorporate the section into the
1018  tag string */
1019  if (AsSectional == true && Word.empty() == false)
1020  {
1021  Tag.append("::").append(Word);
1022  Word.clear();
1023  }
1024 
1025  if (ParentTag.empty() == true)
1026  ParentTag = Tag;
1027  else
1028  ParentTag.append("::").append(Tag);
1029  Tag.clear();
1030  }
1031 
1032  // Generate the item name
1033  string Item;
1034  if (ParentTag.empty() == true)
1035  Item = Tag;
1036  else
1037  {
1038  if (TermChar != '{' || Tag.empty() == false)
1039  Item = ParentTag + "::" + Tag;
1040  else
1041  Item = ParentTag;
1042  }
1043 
1044  // Specials
1045  if (Tag.length() >= 1 && Tag[0] == '#')
1046  {
1047  if (ParentTag.empty() == false)
1048  return _error->Error(_("Syntax error %s:%u: Directives can only be done at the top level"),FName.c_str(),CurLine);
1049  Tag.erase(Tag.begin());
1050  if (Tag == "clear")
1051  Conf.Clear(Word);
1052  else if (Tag == "include")
1053  {
1054  if (Depth > 10)
1055  return _error->Error(_("Syntax error %s:%u: Too many nested includes"),FName.c_str(),CurLine);
1056  if (Word.length() > 2 && Word.end()[-1] == '/')
1057  {
1058  if (ReadConfigDir(Conf,Word,AsSectional,Depth+1) == false)
1059  return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
1060  }
1061  else
1062  {
1063  if (ReadConfigFile(Conf,Word,AsSectional,Depth+1) == false)
1064  return _error->Error(_("Syntax error %s:%u: Included from here"),FName.c_str(),CurLine);
1065  }
1066  }
1067  else if (Tag == "x-apt-configure-index")
1068  {
1069  if (LoadConfigurationIndex(Word) == false)
1070  return _error->Warning("Loading the configure index %s in file %s:%u failed!", Word.c_str(), FName.c_str(), CurLine);
1071  }
1072  else
1073  return _error->Error(_("Syntax error %s:%u: Unsupported directive '%s'"),FName.c_str(),CurLine,Tag.c_str());
1074  }
1075  else if (Tag.empty() == true && NoWord == false && Word == "#clear")
1076  return _error->Error(_("Syntax error %s:%u: clear directive requires an option tree as argument"),FName.c_str(),CurLine);
1077  else
1078  {
1079  // Set the item in the configuration class
1080  if (NoWord == false)
1081  Conf.Set(Item,Word);
1082  }
1083 
1084  // Empty the buffer
1085  LineBuffer.clear();
1086 
1087  // Move up a tag, but only if there is no bit to parse
1088  if (TermChar == '}')
1089  leaveCurrentScope(Stack, ParentTag);
1090  }
1091  }
1092 
1093  // Store the remaining text, if any, in the current line buffer.
1094 
1095  // NB: could change this to use string-based operations; I'm
1096  // using strstrip now to ensure backwards compatibility.
1097  // -- dburrows 2008-04-01
1098  {
1099  char *Buffer = new char[End - Start + 1];
1100  try
1101  {
1102  std::copy(Start, End, Buffer);
1103  Buffer[End - Start] = '\0';
1104 
1105  const char *Stripd = _strstrip(Buffer);
1106  if (*Stripd != 0 && LineBuffer.empty() == false)
1107  LineBuffer += " ";
1108  LineBuffer += Stripd;
1109  }
1110  catch(...)
1111  {
1112  delete[] Buffer;
1113  throw;
1114  }
1115  delete[] Buffer;
1116  }
1117  }
1118 
1119  if (LineBuffer.empty() == false)
1120  return _error->Error(_("Syntax error %s:%u: Extra junk at end of file"),FName.c_str(),CurLine);
1121  return true;
1122 }
1123  /*}}}*/
1124 // ReadConfigDir - Read a directory of config files /*{{{*/
1125 // ---------------------------------------------------------------------
1126 /* */
1127 bool ReadConfigDir(Configuration &Conf,const string &Dir,
1128  bool const &AsSectional, unsigned const &Depth)
1129 {
1130  _error->PushToStack();
1131  auto const files = GetListOfFilesInDir(Dir, "conf", true, true);
1132  auto const successfulList = not _error->PendingError();
1133  _error->MergeWithStack();
1134  return std::accumulate(files.cbegin(), files.cend(), true, [&](bool good, auto const &file) {
1135  return ReadConfigFile(Conf, file, AsSectional, Depth) && good;
1136  }) && successfulList;
1137 }
1138  /*}}}*/
1139 // MatchAgainstConfig Constructor /*{{{*/
1141 {
1142  std::vector<std::string> const strings = _config->FindVector(Config);
1143  for (std::vector<std::string>::const_iterator s = strings.begin();
1144  s != strings.end(); ++s)
1145  {
1146  regex_t *p = new regex_t;
1147  if (regcomp(p, s->c_str(), REG_EXTENDED | REG_ICASE | REG_NOSUB) == 0)
1148  patterns.push_back(p);
1149  else
1150  {
1151  regfree(p);
1152  delete p;
1153  _error->Warning("Invalid regular expression '%s' in configuration "
1154  "option '%s' will be ignored.",
1155  s->c_str(), Config);
1156  continue;
1157  }
1158  }
1159  if (strings.empty() == true)
1160  patterns.push_back(NULL);
1161 }
1162  /*}}}*/
1163 // MatchAgainstConfig Destructor /*{{{*/
1165 {
1166  clearPatterns();
1167 }
1169 {
1170  for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1171  p != patterns.end(); ++p)
1172  {
1173  if (*p == NULL) continue;
1174  regfree(*p);
1175  delete *p;
1176  }
1177  patterns.clear();
1178 }
1179  /*}}}*/
1180 // MatchAgainstConfig::Match - returns true if a pattern matches /*{{{*/
1181 bool Configuration::MatchAgainstConfig::Match(char const * str) const
1182 {
1183  for(std::vector<regex_t *>::const_iterator p = patterns.begin();
1184  p != patterns.end(); ++p)
1185  if (*p != NULL && regexec(*p, str, 0, 0, 0) == 0)
1186  return true;
1187 
1188  return false;
1189 }
1190  /*}}}*/
static bool std::string const metaIndex const *const pkgAcqMetaClearSig *const pkgAcquire::Item *const I
return false
static bool Dump(CommandLine &)
Definition: apt-cache.cc:344
Provide access methods to various configuration settings.
Simple subset of std::string_view from C++17.
Definition: string_view.h:27
static constexpr size_t npos
Definition: string_view.h:32
constexpr const char * data() const
Definition: string_view.h:133
bool Match(char const *str) const
Returns true for a string matching one of the patterns.
MatchAgainstConfig(char const *Config)
bool Exists(const std::string &Name) const
Definition: configuration.h:98
void MoveSubTree(char const *const OldRoot, char const *const NewRoot)
int FindI(const char *Name, int const &Default=0) const
void Set(const std::string &Name, const std::string &Value)
Definition: configuration.h:92
std::vector< std::string > FindVector(const char *Name, std::string const &Default="", bool const Keys=false) const
bool ExistsAny(const char *Name) const
Item * Lookup(Item *Head, const char *S, unsigned long const &Len, bool const &Create)
std::string Find(const char *Name, const char *Default=0) const
std::string FindAny(const char *Name, const char *Default=0) const
bool FindB(const char *Name, bool const &Default=false) const
std::string FindDir(const char *Name, const char *Default=0) const
void CndSet(const char *Name, const std::string &Value)
std::string FindFile(const char *Name, const char *Default=0) const
Definition: fileutl.h:39
char * ReadLine(char *To, unsigned long long const Size)
Definition: fileutl.cc:2776
bool Eof()
Definition: fileutl.h:154
bool empty() const noexcept
Definition: gpgv.cc:56
static bool LoadConfigurationIndex(std::string const &filename)
STRING_OR_BOOL
BOOL
bool ReadConfigFile(Configuration &Conf, const string &FName, bool const &AsSectional, unsigned const &Depth)
FILE
DIR
INT
static void checkFindConfigOptionTypeInternal(std::string name, ConfigType const type)
enum APT_HIDDEN ConfigType
static void leaveCurrentScope(std::stack< std::string > &Stack, std::string &ParentTag)
bool ReadConfigDir(Configuration &Conf, const string &Dir, bool const &AsSectional, unsigned const &Depth)
STRING_OR_LIST
UNDEFINED
APT_HIDDEN std::unordered_map< std::string, ConfigType > apt_known_config
static std::string getConfigTypeString(ConfigType const type)
static ConfigType getConfigType(std::string const &type)
Configuration * _config
STRING
static void checkFindConfigOptionType(char const *const name, ConfigType const type)
LIST
std::string flNormalize(std::string file)
removes superfluous /./ and // from path
Definition: fileutl.cc:770
bool OpenConfigurationFileFd(std::string const &File, FileFd &Fd)
Definition: fileutl.cc:3419
std::vector< string > GetListOfFilesInDir(string const &Dir, string const &Ext, bool const &SortList, bool const &AllowNoExt)
Definition: fileutl.cc:421
#define APT_HIDDEN
Definition: macros.h:78
std::string Strip(const std::string &str)
Definition: strutl.cc:55
std::string FullTag(const Item *Stop=0) const
char * _strstrip(char *String)
Definition: strutl.cc:226
int stringcasecmp(const char *A, const char *AEnd, const char *B, const char *BEnd)
Definition: strutl.cc:685
int StringToBool(const string &Text, int Default)
Definition: strutl.cc:820
string SubstVar(const string &Str, const string &Subst, const string &Contents)
Definition: strutl.cc:502
vector< string > StringSplit(std::string const &s, std::string const &sep, unsigned int maxsplit)
Definition: strutl.cc:1327
vector< string > VectorizeString(string const &haystack, char const &split)
Definition: strutl.cc:1308
bool ParseQuoteWord(const char *&String, string &Res)
Definition: strutl.cc:288
string QuoteString(const string &Str, const char *Bad)
Definition: strutl.cc:384
bool ParseCWord(const char *&String, string &Res)
Definition: strutl.cc:347