1 /* Copyright (C) 2006-2019 J.F.Dockes
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU Lesser General Public License as published by
4  *   the Free Software Foundation; either version 2.1 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU Lesser General Public License for more details.
11  *
12  *   You should have received a copy of the GNU Lesser General Public License
13  *   along with this program; if not, write to the
14  *   Free Software Foundation, Inc.,
15  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16  */
17 #ifndef _CONFTREE_H_
18 #define  _CONFTREE_H_
19 
20 /**
21  * A simple configuration file implementation.
22  *
23  * Configuration files have lines like 'name = value', and/or like '[subkey]'
24  *
25  * Lines like '[subkey]' in the file define subsections, with independant
26  * configuration namespaces. Only subsections holding at least one variable are
27  * significant (empty subsections may be deleted during an update, or not).
28  *
29  * Whitespace around name and value is ignored.
30  *
31  * The names are case-sensitive but don't depend on it, this might change.
32  *
33  * Values can be queried for, or set.
34  *
35  * Any line without a '=', or beginning with '[ \t]*#' is a comment.
36  *
37  * A configuration object can be created empty or by reading from a file or
38  * a string.
39  *
40  * All 'set' calls normally cause an immediate rewrite of the backing
41  * object if any (file or string). This can be prevented with holdWrites().
42  *
43  * The ConfTree derived class interprets the subkeys as file paths and
44  * lets subdir keys hierarchically inherit the properties from
45  * parents.
46  *
47  * The ConfStack class stacks several Con(Simple/Tree) objects so that
48  * parameters from the top of the stack override the values from lower
49  * (useful to have central/personal config files).
50  */
51 
52 #include <algorithm>
53 #include <map>
54 #include <string>
55 #include <vector>
56 
57 // rh7.3 likes iostream better...
58 #if defined(__GNUC__) && __GNUC__ < 3
59 #include <iostream>
60 #else
61 #include <istream>
62 #include <ostream>
63 #endif
64 
65 #include "pathut.h"
66 
67 /** Internal class used for storing presentation information */
68 class ConfLine {
69 public:
70     enum Kind {CFL_COMMENT, CFL_SK, CFL_VAR, CFL_VARCOMMENT};
71     Kind m_kind;
72     std::string m_data;
73     std::string m_value;
74     std::string m_aux;
75     ConfLine(Kind k, const std::string& d, std::string a = std::string())
m_kind(k)76         : m_kind(k), m_data(d), m_aux(a) {
77     }
78     bool operator==(const ConfLine& o) {
79         return o.m_kind == m_kind && o.m_data == m_data;
80     }
81 };
82 
83 /**
84  * Virtual base class used to define the interface, and a few helper methods.
85  */
86 class ConfNull {
87 public:
88     enum StatusCode {STATUS_ERROR = 0, STATUS_RO = 1, STATUS_RW = 2};
~ConfNull()89     virtual ~ConfNull() {};
90     virtual int get(const std::string& name, std::string& value,
91                     const std::string& sk = std::string()) const = 0;
92     virtual int set(const std::string& nm, const std::string& val,
93                     const std::string& sk = std::string()) = 0;
94     virtual long long getInt(const std::string& name, long long dflt,
95                              const std::string& sk = std::string());
96     virtual double getFloat(const std::string& name, double dflt,
97                             const std::string& sk = std::string());
98     virtual bool getBool(const std::string& name, bool dflt,
99                          const std::string& sk = std::string());
100     virtual bool ok() const = 0;
101     virtual std::vector<std::string> getNames(const std::string& sk,
102                                               const char* = 0) const = 0;
103     virtual bool hasNameAnywhere(const std::string& nm) const = 0;
104     virtual int erase(const std::string&, const std::string&) = 0;
105     virtual int eraseKey(const std::string&) = 0;
showall()106     virtual void showall() const {};
107     virtual std::vector<std::string> getSubKeys() const = 0;
108     virtual std::vector<std::string> getSubKeys(bool) const = 0;
109     virtual bool holdWrites(bool) = 0;
110     virtual bool sourceChanged() const = 0;
111 };
112 
113 /**
114  * Manages a simple configuration file with subsections.
115  */
116 class ConfSimple : public ConfNull {
117 public:
118 
119     /**
120      * Build the object by reading content from file.
121      * @param filename file to open
122      * @param readonly if true open readonly, else rw
123      * @param tildexp  try tilde (home dir) expansion for subkey values
124      */
125     ConfSimple(const char *fname, int readonly = 0, bool tildexp = false,
126                bool trimvalues = true);
127 
128     /**
129      * Build the object by reading content from a string
130      * @param data points to the data to parse.
131      * @param readonly if true open readonly, else rw
132      * @param tildexp  try tilde (home dir) expansion for subsection names
133      */
134     ConfSimple(const std::string& data, int readonly = 0, bool tildexp = false,
135                bool trimvalues = true);
136 
137     /**
138      * Build an empty object. This will be memory only, with no backing store.
139      * @param readonly if true open read only, else rw
140      * @param tildexp  try tilde (home dir) expansion for subsection names
141      */
142     ConfSimple(int readonly = 0, bool tildexp = false,
143                bool trimvalues = true);
144 
~ConfSimple()145     virtual ~ConfSimple() {};
146 
147     /** Origin file changed. Only makes sense if we read the data from a file */
148     virtual bool sourceChanged() const override;
149 
150     /**
151      * Decide if we actually rewrite the backing-store after modifying the
152      * tree.
153      */
holdWrites(bool on)154     virtual bool holdWrites(bool on) override {
155         m_holdWrites = on;
156         if (on == false) {
157             return write();
158         } else {
159             return true;
160         }
161     }
162 
163     /** Clear, then reparse from string */
164     void reparse(const std::string& in);
165 
166     /**
167      * Get string value for named parameter, from specified subsection (looks
168      * in global space if sk is empty).
169      * @return 0 if name not found, 1 else
170      */
171     virtual int get(const std::string& name, std::string& value,
172                     const std::string& sk = std::string()) const override;
173 
174     /**
175      * Set value for named string parameter in specified subsection (or global)
176      * @return 0 for error, 1 else
177      */
178     virtual int set(const std::string& nm, const std::string& val,
179                     const std::string& sk = std::string()) override;
180     /**
181      * Set value for named integer parameter in specified subsection (or global)
182      * @return 0 for error, 1 else
183      */
184     virtual int set(const std::string& nm, long long val,
185                     const std::string& sk = std::string());
186 
187     /**
188      * Remove name and value from config
189      */
190     virtual int erase(const std::string& name, const std::string& sk) override;
191 
192     /**
193      * Erase all names under given subkey (and subkey itself)
194      */
195     virtual int eraseKey(const std::string& sk) override;
196 
197     /** Clear all content */
198     virtual int clear();
199 
200     virtual StatusCode getStatus() const;
ok()201     virtual bool ok() const override {
202         return getStatus() != STATUS_ERROR;
203     }
204 
205     /**
206      * Walk the configuration values, calling function for each.
207      * The function is called with a null nm when changing subsections (the
208      * value is then the new subsection name)
209      * @return WALK_STOP when/if the callback returns WALK_STOP,
210      *         WALK_CONTINUE else (got to end of config)
211      */
212     enum WalkerCode {WALK_STOP, WALK_CONTINUE};
213     virtual WalkerCode sortwalk(WalkerCode
214                                 (*wlkr)(void *cldata, const std::string& nm,
215                                         const std::string& val),
216                                 void *clidata) const;
217 
218     /** Print all values to stdout */
219     virtual void showall() const override;
220 
221     /** Return all names in given submap. On win32, the pattern thing
222         only works in recoll builds */
223     virtual std::vector<std::string> getNames(
224         const std::string& sk, const char *pattern = 0) const override;
225 
226     /** Check if name is present in any submap. This is relatively expensive
227      * but useful for saving further processing sometimes */
228     virtual bool hasNameAnywhere(const std::string& nm) const override;
229 
230     /**
231      * Return all subkeys
232      */
getSubKeys(bool)233     virtual std::vector<std::string> getSubKeys(bool) const override {
234         return getSubKeys();
235     }
236     virtual std::vector<std::string> getSubKeys() const override;
237 
238     /** Return subkeys in file order. BEWARE: only for the original from the
239      * file: the data is not duplicated to further copies */
240     virtual std::vector<std::string> getSubKeys_unsorted(bool = false) const {
241         return m_subkeys_unsorted;
242     }
243 
244     /** Test for subkey existence */
hasSubKey(const std::string & sk)245     virtual bool hasSubKey(const std::string& sk) const {
246         return m_submaps.find(sk) != m_submaps.end();
247     }
248 
getFilename()249     virtual std::string getFilename() const {
250         return m_filename;
251     }
252 
253     /** Used with config files with specially formatted, xml-like comments.
254      * Extract the comments as text */
255     virtual bool commentsAsXML(std::ostream& out);
256 
257     /** !! Note that assignment and copy constructor do not copy the
258         auxiliary data (m_order and subkeys_unsorted). */
259 
260     /**
261      * Copy constructor. Expensive but less so than a full rebuild
262      */
ConfSimple(const ConfSimple & rhs)263     ConfSimple(const ConfSimple& rhs)
264         : ConfNull() {
265         if ((status = rhs.status) == STATUS_ERROR) {
266             return;
267         }
268         m_filename = rhs.m_filename;
269         m_submaps = rhs.m_submaps;
270     }
271 
272     /**
273      * Assignement. This is expensive
274      */
275     ConfSimple& operator=(const ConfSimple& rhs) {
276         if (this != &rhs && (status = rhs.status) != STATUS_ERROR) {
277             dotildexpand = rhs.dotildexpand;
278             trimvalues = rhs.trimvalues;
279             m_filename = rhs.m_filename;
280             m_submaps = rhs.m_submaps;
281         }
282         return *this;
283     }
284 
285     /**
286      * Write in file format to out
287      */
288     bool write(std::ostream& out) const;
289 
290     /** Give access to semi-parsed file contents */
getlines()291     const std::vector<ConfLine>& getlines() const {
292         return m_order;
293     }
294 
295 protected:
296     bool dotildexpand;
297     bool trimvalues;
298     StatusCode status;
299 private:
300     // Set if we're working with a file
301     std::string m_filename;
302     int64_t     m_fmtime;
303     // Configuration data submaps (one per subkey, the main data has a
304     // null subkey)
305     std::map<std::string, std::map<std::string, std::string> > m_submaps;
306     std::vector<std::string> m_subkeys_unsorted;
307     // Presentation data. We keep the comments, empty lines and
308     // variable and subkey ordering information in there (for
309     // rewriting the file while keeping hand-edited information)
310     std::vector<ConfLine>    m_order;
311     // Control if we're writing to the backing store
312     bool                     m_holdWrites;
313 
314     void parseinput(std::istream& input);
315     bool write();
316     // Internal version of set: no RW checking
317     virtual int i_set(const std::string& nm, const std::string& val,
318                       const std::string& sk, bool init = false);
319     bool i_changed(bool upd);
320 };
321 
322 /**
323  * This is a configuration class which attaches tree-like signification to the
324  * submap names.
325  *
326  * If a given variable is not found in the specified section, it will be
327  * looked up the tree of section names, and in the global space.
328  *
329  * submap names should be '/' separated paths (ie: /sub1/sub2). No checking
330  * is done, but else the class adds no functionality to ConfSimple.
331  *
332  * NOTE: contrary to common behaviour, the global or root space is NOT
333  * designated by '/' but by '' (empty subkey). A '/' subkey will not
334  * be searched at all.
335  *
336  * Note: getNames() : uses ConfSimple method, this does *not* inherit
337  *     names from englobing submaps.
338  */
339 class ConfTree : public ConfSimple {
340 
341 public:
342     /* The constructors just call ConfSimple's, asking for key tilde
343      * expansion */
344     ConfTree(const char *fname, int readonly = 0, bool trimvalues=true)
ConfSimple(fname,readonly,true,trimvalues)345         : ConfSimple(fname, readonly, true, trimvalues) {}
346     ConfTree(const std::string& data, int readonly = 0, bool trimvalues=true)
ConfSimple(data,readonly,true,trimvalues)347         : ConfSimple(data, readonly, true, trimvalues) {}
348     ConfTree(int readonly = 0, bool trimvalues=true)
ConfSimple(readonly,true,trimvalues)349         : ConfSimple(readonly, true, trimvalues) {}
~ConfTree()350     virtual ~ConfTree() {};
ConfTree(const ConfTree & r)351     ConfTree(const ConfTree& r) : ConfSimple(r) {};
352     ConfTree& operator=(const ConfTree& r) {
353         ConfSimple::operator=(r);
354         return *this;
355     }
356 
357     /**
358      * Get value for named parameter, from specified subsection, or its
359      * parents.
360      * @return 0 if name not found, 1 else
361      */
362     virtual int get(const std::string& name, std::string& value,
363                     const std::string& sk) const override;
364     using ConfSimple::get;
365 };
366 
367 /**
368  * Use several config files, trying to get values from each in order.
369  *
370  * Enables having a central/default config, with possible overrides
371  * from more specific (e.g. personal) ones.
372  *
373  * Notes: it's ok for some of the files not to exist, but the last
374  * (bottom) one must or we generate an error. We open all trees
375  * readonly, except the topmost one if requested. All writes go to the
376  * topmost file. Note that erase() won't work except for parameters
377  * only defined in the topmost file (it erases only from there).
378  */
379 template <class T> class ConfStack : public ConfNull {
380 public:
381     /// Construct from configuration file names. The earler
382     /// files in have priority when fetching values. Only the first
383     /// file will be updated if ro is false and set() is used.
384     ConfStack(const std::vector<std::string>& fns, bool ro = true) {
385         construct(fns, ro);
386     }
387     /// Construct out of single file name and multiple directories
388     ConfStack(const std::string& nm, const std::vector<std::string>& dirs,
389               bool ro = true) {
390         std::vector<std::string> fns;
391         for (const auto& dir : dirs) {
392             fns.push_back(path_cat(dir, nm));
393         }
394         ConfStack::construct(fns, ro);
395     }
396 
ConfStack(const ConfStack & rhs)397     ConfStack(const ConfStack& rhs)
398         : ConfNull() {
399         init_from(rhs);
400     }
401 
~ConfStack()402     virtual ~ConfStack() {
403         clear();
404         m_ok = false;
405     }
406 
407     ConfStack& operator=(const ConfStack& rhs) {
408         if (this != &rhs) {
409             clear();
410             m_ok = rhs.m_ok;
411             if (m_ok) {
412                 init_from(rhs);
413             }
414         }
415         return *this;
416     }
417 
sourceChanged()418     virtual bool sourceChanged() const override {
419         for (const auto& conf : m_confs) {
420             if (conf->sourceChanged()) {
421                 return true;
422             }
423         }
424         return false;
425     }
426 
get(const std::string & name,std::string & value,const std::string & sk,bool shallow)427     virtual int get(const std::string& name, std::string& value,
428                     const std::string& sk, bool shallow) const {
429         for (const auto& conf : m_confs) {
430             if (conf->get(name, value, sk)) {
431                 return true;
432             }
433             if (shallow) {
434                 break;
435             }
436         }
437         return false;
438     }
439 
get(const std::string & name,std::string & value,const std::string & sk)440     virtual int get(const std::string& name, std::string& value,
441                     const std::string& sk) const override {
442         return get(name, value, sk, false);
443     }
444 
hasNameAnywhere(const std::string & nm)445     virtual bool hasNameAnywhere(const std::string& nm) const override {
446         for (const auto& conf : m_confs) {
447             if (conf->hasNameAnywhere(nm)) {
448                 return true;
449             }
450         }
451         return false;
452     }
453 
454     virtual int set(const std::string& nm, const std::string& val,
455                     const std::string& sk = std::string()) override {
456         if (!m_ok) {
457             return 0;
458         }
459         //LOGDEB2(("ConfStack::set [%s]:[%s] -> [%s]\n", sk.c_str(),
460         //nm.c_str(), val.c_str()));
461         // Avoid adding unneeded entries: if the new value matches the
462         // one out from the deeper configs, erase or dont add it
463         // from/to the topmost file
464         auto it = m_confs.begin();
465         it++;
466         while (it != m_confs.end()) {
467             std::string value;
468             if ((*it)->get(nm, value, sk)) {
469                 // This file has value for nm/sk. If it is the same as the new
470                 // one, no need for an entry in the topmost file. Else, stop
471                 // looking and add the new entry
472                 if (value == val) {
473                     m_confs.front()->erase(nm, sk);
474                     return true;
475                 } else {
476                     break;
477                 }
478             }
479             it++;
480         }
481 
482         return m_confs.front()->set(nm, val, sk);
483     }
484 
erase(const std::string & nm,const std::string & sk)485     virtual int erase(const std::string& nm, const std::string& sk) override {
486         return m_confs.front()->erase(nm, sk);
487     }
eraseKey(const std::string & sk)488     virtual int eraseKey(const std::string& sk) override {
489         return m_confs.front()->eraseKey(sk);
490     }
holdWrites(bool on)491     virtual bool holdWrites(bool on) override {
492         return m_confs.front()->holdWrites(on);
493     }
494 
495     /** Return all names in given submap. On win32, the pattern thing
496         only works in recoll builds */
497     virtual std::vector<std::string> getNames(
498         const std::string& sk, const char *pattern = 0) const override {
499         return getNames1(sk, pattern, false);
500     }
501     virtual std::vector<std::string> getNamesShallow(const std::string& sk,
502                                                      const char *patt = 0) const {
503         return getNames1(sk, patt, true);
504     }
505 
getNames1(const std::string & sk,const char * pattern,bool shallow)506     virtual std::vector<std::string> getNames1(
507         const std::string& sk, const char *pattern, bool shallow) const {
508         std::vector<std::string> nms;
509         bool skfound = false;
510         for (const auto& conf : m_confs) {
511             if (conf->hasSubKey(sk)) {
512                 skfound = true;
513                 std::vector<std::string> lst = conf->getNames(sk, pattern);
514                 nms.insert(nms.end(), lst.begin(), lst.end());
515             }
516             if (shallow && skfound) {
517                 break;
518             }
519         }
520         sort(nms.begin(), nms.end());
521         std::vector<std::string>::iterator uit = unique(nms.begin(), nms.end());
522         nms.resize(uit - nms.begin());
523         return nms;
524     }
525 
getSubKeys()526     virtual std::vector<std::string> getSubKeys() const override {
527         return getSubKeys(false);
528     }
getSubKeys(bool shallow)529     virtual std::vector<std::string> getSubKeys(bool shallow) const override {
530         std::vector<std::string> sks;
531         for (const auto& conf : m_confs) {
532             std::vector<std::string> lst;
533             lst = conf->getSubKeys();
534             sks.insert(sks.end(), lst.begin(), lst.end());
535             if (shallow) {
536                 break;
537             }
538         }
539         sort(sks.begin(), sks.end());
540         std::vector<std::string>::iterator uit = unique(sks.begin(), sks.end());
541         sks.resize(uit - sks.begin());
542         return sks;
543     }
544 
ok()545     virtual bool ok() const override {
546         return m_ok;
547     }
548 
549 private:
550     bool     m_ok;
551     std::vector<T*> m_confs;
552 
553     /// Reset to pristine
clear()554     void clear() {
555         for (auto& conf : m_confs) {
556             delete(conf);
557         }
558         m_confs.clear();
559     }
560 
561     /// Common code to initialize from existing object
init_from(const ConfStack & rhs)562     void init_from(const ConfStack& rhs) {
563         if ((m_ok = rhs.m_ok)) {
564             for (const auto& conf : rhs.m_confs) {
565                 m_confs.push_back(new T(*conf));
566             }
567         }
568     }
569 
570     /// Common construct from file names code. We used to be ok even
571     /// if some files were not readable/parsable. Now fail if any
572     /// fails.
construct(const std::vector<std::string> & fns,bool ro)573     void construct(const std::vector<std::string>& fns, bool ro) {
574         bool ok{true};
575         bool first{true};
576         for (const auto& fn : fns) {
577             T* p = new T(fn.c_str(), ro);
578             if (p && p->ok()) {
579                 m_confs.push_back(p);
580             } else {
581                 delete p;
582                 // In ro mode, we accept a non-existing topmost file
583                 // and treat it as an empty one.
584                 if (!(ro && first && !path_exists(fn))) {
585                     ok = false;
586                 }
587             }
588             // Only the first file is opened rw
589             ro = true;
590             first = false;
591         }
592         m_ok = ok;
593     }
594 };
595 
596 #endif /*_CONFTREE_H_ */
597