a b/src/conftree.cpp
1
/* Copyright (C) 2003 J.F.Dockes 
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 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 General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
#ifndef TEST_CONFTREE
18
19
#include "conftree.h"
20
21
#include <fnmatch.h>                    // for fnmatch
22
#include <stdlib.h>                     // for abort
23
#include <sys/stat.h>                   // for stat, st_mtime
24
#include <unistd.h>                     // for access
25
#include <pwd.h>                        // for getpwnam, getpwuid, passwd
26
27
#include <algorithm>                    // for find
28
#include <cstring>                      // for strlen
29
#include <fstream>                      // for ifstream, ofstream
30
#include <iostream>                     // for cerr, cout
31
#include <sstream>                      // for stringstream
32
#include <utility>                      // for pair
33
34
using namespace std;
35
36
#undef DEBUG
37
#ifdef DEBUG
38
#define LOGDEB(X) fprintf X
39
#else
40
#define LOGDEB(X)
41
#endif
42
43
#ifndef MIN 
44
#define MIN(A,B) ((A) < (B) ? (A) : (B))
45
#endif
46
47
#define LL 2048
48
49
string ConfNull::path_home()
50
{
51
    uid_t uid = getuid();
52
53
    struct passwd *entry = getpwuid(uid);
54
    if (entry == 0) {
55
  const char *cp = getenv("HOME");
56
  if (cp)
57
      return cp;
58
  else 
59
  return "/";
60
    }
61
62
    string homedir = entry->pw_dir;
63
    path_catslash(homedir);
64
    return homedir;
65
}
66
67
void ConfNull::path_catslash(string &s) {
68
    if (s.empty() || s[s.length() - 1] != '/')
69
  s += '/';
70
}
71
72
string ConfNull::path_cat(const string &s1, const string &s2) {
73
    string res = s1;
74
    path_catslash(res);
75
    res +=  s2;
76
    return res;
77
}
78
79
string ConfNull::path_tildexpand(const string &s) 
80
{
81
    if (s.empty() || s[0] != '~')
82
  return s;
83
    string o = s;
84
    if (s.length() == 1) {
85
  o.replace(0, 1, path_home());
86
    } else if  (s[1] == '/') {
87
  o.replace(0, 2, path_home());
88
    } else {
89
  string::size_type pos = s.find('/');
90
  int l = (pos == string::npos) ? s.length() - 1 : pos - 1;
91
  struct passwd *entry = getpwnam(s.substr(1, l).c_str());
92
  if (entry)
93
      o.replace(0, l+1, entry->pw_dir);
94
    }
95
    return o;
96
}
97
void ConfNull::trimstring(string &s, const char *ws)
98
{
99
    string::size_type pos = s.find_first_not_of(ws);
100
    if (pos == string::npos) {
101
  s.clear();
102
  return;
103
    }
104
    s.replace(0, pos, string());
105
106
    pos = s.find_last_not_of(ws);
107
    if (pos != string::npos && pos != s.length()-1)
108
  s.replace(pos+1, string::npos, string());
109
}
110
111
void ConfSimple::parseinput(istream &input)
112
{
113
    string submapkey;
114
    char cline[LL];
115
    bool appending = false;
116
    string line;
117
    bool eof = false;
118
119
    for (;;) {
120
        cline[0] = 0;
121
  input.getline(cline, LL-1);
122
  LOGDEB((stderr, "Parse:line: [%s] status %d\n", cline, int(status)));
123
  if (!input.good()) {
124
      if (input.bad()) {
125
                LOGDEB((stderr, "Parse: input.bad()\n"));
126
      status = STATUS_ERROR;
127
      return;
128
      }
129
            LOGDEB((stderr, "Parse: eof\n"));
130
      // Must be eof ? But maybe we have a partial line which
131
      // must be processed. This happens if the last line before
132
      // eof ends with a backslash, or there is no final \n
133
            eof = true;
134
  }
135
136
        {
137
            int ll = strlen(cline);
138
            while (ll > 0 && (cline[ll-1] == '\n' || cline[ll-1] == '\r')) {
139
                cline[ll-1] = 0;
140
                ll--;
141
            }
142
        }
143
144
  if (appending)
145
      line += cline;
146
  else
147
      line = cline;
148
149
  // Note that we trim whitespace before checking for backslash-eol
150
  // This avoids invisible whitespace problems.
151
  trimstring(line);
152
  if (line.empty() || line.at(0) == '#') {
153
            if (eof)
154
                break;
155
      m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line));
156
      continue;
157
  }
158
  if (line[line.length() - 1] == '\\') {
159
      line.erase(line.length() - 1);
160
      appending = true;
161
      continue;
162
  }
163
  appending = false;
164
165
  if (line[0] == '[') {
166
      trimstring(line, "[]");
167
      if (dotildexpand)
168
      submapkey = path_tildexpand(line);
169
      else 
170
      submapkey = line;
171
      // No need for adding sk to order, will be done with first
172
      // variable insert. Also means that empty section are
173
      // expandable (won't be output when rewriting)
174
      // Another option would be to add the subsec to m_order here
175
      // and not do it inside i_set() if init is true
176
      continue;
177
  }
178
179
  // Look for first equal sign
180
  string::size_type eqpos = line.find("=");
181
  if (eqpos == string::npos) {
182
      m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line));
183
      continue;
184
  }
185
186
  // Compute name and value, trim white space
187
  string nm, val;
188
  nm = line.substr(0, eqpos);
189
  trimstring(nm);
190
  val = line.substr(eqpos+1, string::npos);
191
  trimstring(val);
192
  
193
  if (nm.length() == 0) {
194
      m_order.push_back(ConfLine(ConfLine::CFL_COMMENT, line));
195
      continue;
196
  }
197
  i_set(nm, val, submapkey, true);
198
        if (eof)
199
            break;
200
    }
201
}
202
203
204
ConfSimple::ConfSimple(int readonly, bool tildexp)
205
    : dotildexpand(tildexp), m_fmtime(0), m_holdWrites(false)
206
{
207
    status = readonly ? STATUS_RO : STATUS_RW;
208
}
209
210
void ConfSimple::reparse(const string& d)
211
{
212
    clear();
213
    stringstream input(d, ios::in);
214
    parseinput(input);
215
}
216
217
ConfSimple::ConfSimple(const string& d, int readonly, bool tildexp)
218
    : dotildexpand(tildexp), m_fmtime(0), m_holdWrites(false)
219
{
220
    status = readonly ? STATUS_RO : STATUS_RW;
221
222
    stringstream input(d, ios::in);
223
    parseinput(input);
224
}
225
226
ConfSimple::ConfSimple(const char *fname, int readonly, bool tildexp)
227
    : dotildexpand(tildexp), m_filename(fname), m_fmtime(0), m_holdWrites(false)
228
{
229
    status = readonly ? STATUS_RO : STATUS_RW;
230
231
    ifstream input;
232
    if (readonly) {
233
  input.open(fname, ios::in);
234
    } else {
235
  ios::openmode mode = ios::in|ios::out;
236
  // It seems that there is no separate 'create if not exists' 
237
  // open flag. Have to truncate to create, but dont want to do 
238
  // this to an existing file !
239
  if (access(fname, 0) < 0) {
240
      mode |= ios::trunc;
241
  }
242
  input.open(fname, mode);
243
  if (input.is_open()) {
244
      status = STATUS_RW;
245
  } else {
246
      input.clear();
247
      input.open(fname, ios::in);
248
      if (input.is_open()) {
249
      status = STATUS_RO;
250
      }
251
  }
252
    }
253
254
    if (!input.is_open()) {
255
  status = STATUS_ERROR;
256
  return;
257
    }     
258
259
    parseinput(input);
260
    i_changed(true);
261
}
262
263
ConfSimple::StatusCode ConfSimple::getStatus() const
264
{
265
    switch (status) {
266
    case STATUS_RO: return STATUS_RO;
267
    case STATUS_RW: return STATUS_RW;
268
    default: return STATUS_ERROR;
269
    }
270
}
271
272
bool ConfSimple::sourceChanged() const
273
{
274
    if (!m_filename.empty()) {
275
  struct stat st;
276
  if (stat(m_filename.c_str(), &st) == 0) {
277
      if (m_fmtime != st.st_mtime) {
278
      return true;
279
      }
280
  }
281
    }
282
    return false;
283
}
284
285
bool ConfSimple::i_changed(bool upd)
286
{
287
    if (!m_filename.empty()) {
288
  struct stat st;
289
  if (stat(m_filename.c_str(), &st) == 0) {
290
      if (m_fmtime != st.st_mtime) {
291
      if (upd)
292
          m_fmtime = st.st_mtime;
293
      return true;
294
      }
295
  }
296
    }
297
    return false;
298
}
299
300
int ConfSimple::get(const string &nm, string &value, const string &sk) const
301
{
302
    if (!ok())
303
  return 0;
304
305
    // Find submap
306
    map<string, map<string, string> >::const_iterator ss;
307
    if ((ss = m_submaps.find(sk)) == m_submaps.end()) 
308
  return 0;
309
310
    // Find named value
311
    map<string, string>::const_iterator s;
312
    if ((s = ss->second.find(nm)) == ss->second.end()) 
313
  return 0;
314
    value = s->second;
315
    return 1;
316
}
317
318
// Appropriately output a subkey (nm=="") or variable line.
319
// Splits long lines
320
static ConfSimple::WalkerCode varprinter(void *f, const string &nm, 
321
                   const string &value)
322
{
323
    ostream *output = (ostream *)f;
324
    if (nm.empty()) {
325
  *output << "\n[" << value << "]\n";
326
    } else {
327
  string value1;
328
  if (value.length() < 60) {
329
      value1 = value;
330
  } else {
331
      string::size_type pos = 0;
332
      while (pos < value.length()) {
333
      string::size_type len = MIN(60, value.length() - pos);
334
      value1 += value.substr(pos, len);
335
      pos += len;
336
      if (pos < value.length())
337
          value1 += "\\\n";
338
      }
339
  }
340
  *output << nm << " = " << value1 << "\n";
341
    }
342
    return ConfSimple::WALK_CONTINUE;
343
}
344
345
// Set variable and rewrite data
346
int ConfSimple::set(const std::string &nm, const std::string &value, 
347
          const string &sk)
348
{
349
    if (status  != STATUS_RW)
350
  return 0;
351
    LOGDEB((stderr, "ConfSimple::set [%s]:[%s] -> [%s]\n", sk.c_str(),
352
       nm.c_str(), value.c_str()));
353
    if (!i_set(nm, value, sk))
354
  return 0;
355
    return write();
356
}
357
358
// Internal set variable: no rw checking or file rewriting. If init is
359
// set, we're doing initial parsing, else we are changing a parsed
360
// tree (changes the way we update the order data)
361
int ConfSimple::i_set(const std::string &nm, const std::string &value, 
362
            const string &sk, bool init)
363
{
364
    LOGDEB((stderr, "ConfSimple::i_set: nm[%s] val[%s] key[%s], init %d\n",
365
      nm.c_str(), value.c_str(), sk.c_str(), init));
366
    // Values must not have embedded newlines
367
    if (value.find_first_of("\n\r") != string::npos) {
368
  LOGDEB((stderr, "ConfSimple::i_set: LF in value\n"));
369
  return 0;
370
    }
371
    bool existing = false;
372
    map<string, map<string, string> >::iterator ss;
373
    // Test if submap already exists, else create it, and insert variable:
374
    if ((ss = m_submaps.find(sk)) == m_submaps.end()) {
375
  LOGDEB((stderr, "ConfSimple::i_set: new submap\n"));
376
  map<string, string> submap;
377
  submap[nm] = value;
378
  m_submaps[sk] = submap;
379
380
  // Maybe add sk entry to m_order data:
381
  if (!sk.empty()) {
382
      ConfLine nl(ConfLine::CFL_SK, sk);
383
      // Append SK entry only if it's not already there (erase
384
      // does not remove entries from the order data, adn it may
385
      // be being recreated after deletion)
386
      if (find(m_order.begin(), m_order.end(), nl) == m_order.end()) {
387
      m_order.push_back(nl);
388
      }
389
  }
390
    } else {
391
  // Insert or update variable in existing map.
392
  map<string, string>::iterator it;
393
  it = ss->second.find(nm);
394
  if (it == ss->second.end()) {
395
      ss->second.insert(pair<string,string>(nm, value));
396
  } else {
397
      it->second = value;
398
      existing = true;
399
  }
400
    }
401
402
    // If the variable already existed, no need to change the m_order data
403
    if (existing) {
404
  LOGDEB((stderr, "ConfSimple::i_set: existing var: no order update\n"));
405
  return 1;
406
    }
407
408
    // Add the new variable at the end of its submap in the order data.
409
410
    if (init) {
411
  // During the initial construction, just append:
412
  LOGDEB((stderr, "ConfSimple::i_set: init true: append\n"));
413
  m_order.push_back(ConfLine(ConfLine::CFL_VAR, nm));
414
  return 1;
415
    } 
416
417
    // Look for the start and end of the subkey zone. Start is either
418
    // at begin() for a null subkey, or just behind the subkey
419
    // entry. End is either the next subkey entry, or the end of
420
    // list. We insert the new entry just before end.
421
    vector<ConfLine>::iterator start, fin;
422
    if (sk.empty()) {
423
  start = m_order.begin();
424
  LOGDEB((stderr,"ConfSimple::i_set: null sk, start at top of order\n"));
425
    } else {
426
  start = find(m_order.begin(), m_order.end(), 
427
           ConfLine(ConfLine::CFL_SK, sk));
428
  if (start == m_order.end()) {
429
      // This is not logically possible. The subkey must
430
      // exist. We're doomed
431
      std::cerr << "Logical failure during configuration variable " 
432
      "insertion" << endl;
433
      abort();
434
  }
435
    }
436
437
    fin = m_order.end();
438
    if (start != m_order.end()) {
439
  // The null subkey has no entry (maybe it should)
440
  if (!sk.empty())
441
      start++;
442
  for (vector<ConfLine>::iterator it = start; it != m_order.end(); it++) {
443
      if (it->m_kind == ConfLine::CFL_SK) {
444
      fin = it;
445
      break;
446
      }
447
  }
448
    }
449
450
    // It may happen that the order entry already exists because erase doesnt
451
    // update m_order
452
    if (find(start, fin, ConfLine(ConfLine::CFL_VAR, nm)) == fin) {
453
  m_order.insert(fin, ConfLine(ConfLine::CFL_VAR, nm));
454
    }
455
    return 1;
456
}
457
458
int ConfSimple::erase(const string &nm, const string &sk)
459
{
460
    if (status  != STATUS_RW)
461
  return 0;
462
463
    map<string, map<string, string> >::iterator ss;
464
    if ((ss = m_submaps.find(sk)) == m_submaps.end()) {
465
  return 0;
466
    }
467
    
468
    ss->second.erase(nm);
469
    if (ss->second.empty()) {
470
  m_submaps.erase(ss);
471
    }
472
    return write();
473
}
474
475
int ConfSimple::eraseKey(const string &sk)
476
{
477
    vector<string> nms = getNames(sk);
478
    for (vector<string>::iterator it = nms.begin(); it != nms.end(); it++) {
479
  erase(*it, sk);
480
    }
481
    return write();
482
}
483
484
// Walk the tree, calling user function at each node
485
ConfSimple::WalkerCode 
486
ConfSimple::sortwalk(WalkerCode (*walker)(void *,const string&,const string&),
487
           void *clidata) const
488
{
489
    if (!ok())
490
  return WALK_STOP;
491
    // For all submaps:
492
    for (map<string, map<string, string> >::const_iterator sit = 
493
       m_submaps.begin();
494
   sit != m_submaps.end(); sit++) {
495
496
  // Possibly emit submap name:
497
  if (!sit->first.empty() && walker(clidata, string(), sit->first.c_str())
498
      == WALK_STOP)
499
      return WALK_STOP;
500
501
  // Walk submap
502
  const map<string, string> &sm = sit->second;
503
  for (map<string, string>::const_iterator it = sm.begin();it != sm.end();
504
       it++) {
505
      if (walker(clidata, it->first, it->second) == WALK_STOP)
506
      return WALK_STOP;
507
  }
508
    }
509
    return WALK_CONTINUE;
510
}
511
512
// Write to default output. This currently only does something if output is
513
// a file
514
bool ConfSimple::write()
515
{
516
    if (!ok())
517
  return false;
518
    if (m_holdWrites)
519
  return true;
520
    if (m_filename.length()) {
521
  ofstream output(m_filename.c_str(), ios::out|ios::trunc);
522
  if (!output.is_open())
523
      return 0;
524
  return write(output);
525
    } else {
526
  // No backing store, no writing. Maybe one day we'll need it with
527
        // some kind of output string. This can't be the original string which
528
        // is currently readonly.
529
  //ostringstream output(m_ostring, ios::out | ios::trunc);
530
  return 1;
531
    }
532
}
533
534
// Write out the tree in configuration file format:
535
// This does not check holdWrites, this is done by write(void), which
536
// lets ie: showall work even when holdWrites is set
537
bool ConfSimple::write(ostream& out) const
538
{
539
    if (!ok())
540
  return false;
541
    string sk;
542
    for (vector<ConfLine>::const_iterator it = m_order.begin(); 
543
   it != m_order.end(); it++) {
544
  switch(it->m_kind) {
545
  case ConfLine::CFL_COMMENT: 
546
      out << it->m_data << endl; 
547
      if (!out.good()) 
548
      return false;
549
      break;
550
  case ConfLine::CFL_SK:      
551
      sk = it->m_data;
552
      LOGDEB((stderr, "ConfSimple::write: SK [%s]\n", sk.c_str()));
553
      // Check that the submap still exists, and only output it if it
554
      // does
555
      if (m_submaps.find(sk) != m_submaps.end()) {
556
      out << "[" << it->m_data << "]" << endl;
557
      if (!out.good()) 
558
          return false;
559
      }
560
      break;
561
  case ConfLine::CFL_VAR:
562
      string nm = it->m_data;
563
      LOGDEB((stderr, "ConfSimple::write: VAR [%s], sk [%s]\n",
564
          nm.c_str(), sk.c_str()));
565
      // As erase() doesnt update m_order we can find unexisting
566
      // variables, and must not output anything for them. Have
567
      // to use a ConfSimple::get() to check here, because
568
      // ConfTree's could retrieve from an ancestor even if the
569
      // local var is gone.
570
      string value;
571
      if (ConfSimple::get(nm, value, sk)) {
572
          varprinter(&out, nm, value);
573
          if (!out.good()) 
574
          return false;
575
          break;
576
      }
577
      LOGDEB((stderr, "ConfSimple::write: no value: nm[%s] sk[%s]\n",
578
          nm.c_str(), sk.c_str()));
579
      break;
580
  }
581
    }
582
    return true;
583
}
584
585
void ConfSimple::showall() const
586
{
587
    if (!ok())
588
  return;
589
    write(std::cout);
590
}
591
592
vector<string> ConfSimple::getNames(const string &sk, const char *pattern) const
593
{
594
    vector<string> mylist;
595
    if (!ok())
596
  return mylist;
597
    map<string, map<string, string> >::const_iterator ss;
598
    if ((ss = m_submaps.find(sk)) == m_submaps.end()) {
599
  return mylist;
600
    }
601
    mylist.reserve(ss->second.size());
602
    map<string, string>::const_iterator it;
603
    for (it = ss->second.begin(); it != ss->second.end(); it++) {
604
        if (pattern && 0 != fnmatch(pattern, it->first.c_str(), 0))
605
            continue;
606
  mylist.push_back(it->first);
607
    }
608
    return mylist;
609
}
610
611
vector<string> ConfSimple::getSubKeys() const
612
{
613
    vector<string> mylist;
614
    if (!ok())
615
  return mylist;
616
    mylist.reserve(m_submaps.size());
617
    map<string, map<string, string> >::const_iterator ss;
618
    for (ss = m_submaps.begin(); ss != m_submaps.end(); ss++) {
619
  mylist.push_back(ss->first);
620
    }
621
    return mylist;
622
}
623
624
bool ConfSimple::hasNameAnywhere(const string& nm) const
625
{
626
    vector<string>keys = getSubKeys();
627
    for (vector<string>::const_iterator it = keys.begin(); 
628
         it != keys.end(); it++) {
629
        string val;
630
        if (get(nm, val, *it))
631
            return true;
632
    }
633
    return false;
634
}
635
636
// //////////////////////////////////////////////////////////////////////////
637
// ConfTree Methods: conftree interpret keys like a hierarchical file tree
638
// //////////////////////////////////////////////////////////////////////////
639
640
int ConfTree::get(const std::string &name, string &value, const string &sk)
641
    const
642
{
643
    if (sk.empty() || sk[0] != '/') {
644
  //  LOGDEB((stderr, "ConfTree::get: looking in global space\n"));
645
  return ConfSimple::get(name, value, sk);
646
    }
647
648
    // Get writable copy of subkey path
649
    string msk = sk;
650
651
    // Handle the case where the config file path has an ending / and not
652
    // the input sk
653
    path_catslash(msk);
654
655
    // Look in subkey and up its parents until root ('')
656
    for (;;) {
657
  //  LOGDEB((stderr,"ConfTree::get: looking for '%s' in '%s'\n",
658
  //      name.c_str(), msk.c_str()));
659
  if (ConfSimple::get(name, value, msk))
660
      return 1;
661
  string::size_type pos = msk.rfind("/");
662
  if (pos != string::npos) {
663
      msk.replace(pos, string::npos, string());
664
  } else
665
      break;
666
    }
667
    return 0;
668
}
669
670
#else // TEST_CONFTREE
671
672
#include <stdio.h>
673
#include <unistd.h>
674
#include <fcntl.h>
675
#include <errno.h>
676
#include <string.h>
677
#include <sstream>
678
#include <iostream>
679
#include <vector>
680
681
#include "conftree.h"
682
#include "smallut.h"
683
#include "readfile.h"
684
685
using namespace std;
686
687
static char *thisprog;
688
689
bool complex_updates(const string& fn)
690
{
691
    int fd;
692
    if ((fd = open(fn.c_str(), O_RDWR|O_TRUNC|O_CREAT, 0666)) < 0) {
693
  perror("open/create");
694
  return false;
695
    }
696
    close(fd);
697
698
    ConfTree conf(fn.c_str());
699
    if (!conf.ok()) {
700
  cerr << "Config init failed" << endl;
701
  return false;
702
    }
703
704
    conf.set("nm-1", "val-1", "");
705
    conf.set("nm-2", "val-2", "");
706
707
    conf.set("nm-1", "val1-1", "/dir1");
708
    conf.set("nm-2", "val1-2", "/dir1");
709
710
    conf.set("nm-1", "val2-1", "/dir2");
711
    conf.set("nm-2", "val2-2", "/dir2");
712
713
    conf.set("nm-1", "val11-1", "/dir1/dir1");
714
    conf.set("nm-2", "val11-2", "/dir1/dir1");
715
716
    conf.eraseKey("/dir2");
717
    conf.set("nm-1", "val2-1", "/dir2");
718
    conf.set("nm-2", "val2-2", "/dir2");
719
720
    conf.erase("nm-1", "");
721
    conf.erase("nm-2", "");
722
    conf.eraseKey("/dir1");
723
    conf.eraseKey("/dir2");
724
    conf.eraseKey("/dir1/dir1");
725
726
    conf.set("nm-1", "val1-1", "/dir1");
727
    conf.set("nm-2", "val1-2", "/dir1");
728
    conf.set("nm-1", "val-1", "");
729
730
    conf.set("nm-1", "val2-1", "/dir2");
731
    conf.set("nm-2", "val2-2", "/dir2");
732
733
    conf.set("nm-1", "val11-1", "/dir1/dir1");
734
    conf.set("nm-2", "val11-2", "/dir1/dir1");
735
736
    conf.erase("nm-1", "/dir2");
737
    conf.erase("nm-2", "/dir2");
738
    conf.erase("nm-1", "/dir1/dir1");
739
    conf.erase("nm-2", "/dir1/dir1");
740
741
    string data;
742
    file_to_string(fn, data, 0);
743
    const string ref =
744
  "nm-1 = val-1\n"
745
  "[/dir1]\n"
746
  "nm-1 = val1-1\n"
747
  "nm-2 = val1-2\n"
748
  ;
749
    if (data.compare(ref)) {
750
  cerr << "Final file:" << endl << data << endl << "Differs from ref:" <<
751
      endl << ref << endl;
752
  return false;
753
    } else {
754
  cout << "Updates test Ok" << endl;
755
    }
756
    return true;
757
}
758
759
ConfSimple::WalkerCode mywalker(void *, const string &nm, const string &value)
760
{
761
    if (nm.empty())
762
  printf("\n[%s]\n", value.c_str());
763
    else 
764
  printf("'%s' -> '%s'\n", nm.c_str(), value.c_str());
765
    return ConfSimple::WALK_CONTINUE;
766
}
767
768
const char *longvalue = 
769
"Donnees012345678901234567890123456789012345678901234567890123456789AA"
770
"0123456789012345678901234567890123456789012345678901234567890123456789FIN"
771
    ;
772
773
void memtest(ConfSimple &c) 
774
{
775
    cout << "Initial:" << endl;
776
    c.showall();
777
    if (c.set("nom", "avec nl \n 2eme ligne", "")) {
778
  fprintf(stderr, "set with embedded nl succeeded !\n");
779
  exit(1);
780
    }
781
    if (!c.set(string("parm1"), string("1"), string("subkey1"))) {
782
  fprintf(stderr, "Set error");
783
  exit(1);
784
    }
785
    if (!c.set("sparm", "Parametre \"string\" bla", "s2")) {
786
  fprintf(stderr, "Set error");
787
  exit(1);
788
    }
789
    if (!c.set("long", longvalue, "")) {
790
  fprintf(stderr, "Set error");
791
  exit(1);
792
    }
793
794
    cout << "Final:" << endl;
795
    c.showall();
796
}
797
798
bool readwrite(ConfNull *conf)
799
{
800
    if (conf->ok()) {
801
  // It's ok for the file to not exist here
802
  string value;
803
      
804
  if (conf->get("mypid", value)) {
805
      cout << "Value for mypid is [" << value << "]" << endl;
806
  } else {
807
      cout << "mypid not set" << endl;
808
  }
809
      
810
  if (conf->get("unstring", value)) {
811
      cout << "Value for unstring is ["<< value << "]" << endl;
812
  } else {
813
      cout << "unstring not set" << endl;
814
  }
815
    }
816
    char spid[100];
817
    sprintf(spid, "%d", getpid());
818
    if (!conf->set("mypid", spid)) {
819
  cerr << "Set mypid failed" << endl;
820
    }
821
822
    ostringstream ost;
823
    ost << "mypid" << getpid();
824
    if (!conf->set(ost.str(), spid, "")) {
825
  cerr << "Set mypid failed (2)" << endl;
826
    }
827
    if (!conf->set("unstring", "Une jolie phrase pour essayer")) {
828
  cerr << "Set unstring failed" << endl;
829
    }
830
    return true;
831
}
832
833
bool query(ConfNull *conf, const string& nm, const string& sub)
834
{
835
    if (!conf->ok()) {
836
  cerr <<  "Error opening or parsing file\n" << endl;
837
  return false;
838
    }
839
    string value;
840
    if (!conf->get(nm, value, sub)) {
841
  cerr << "name [" << nm << "] not found in [" << sub << "]" << endl;
842
  return false;
843
    }
844
    cout << "[" << sub << "] " << nm << " " << value << endl;
845
    return true;
846
}
847
848
bool erase(ConfNull *conf, const string& nm, const string& sub)
849
{
850
    if (!conf->ok()) {
851
  cerr <<  "Error opening or parsing file\n" << endl;
852
  return false;
853
    }
854
855
    if (!conf->erase(nm, sub)) {
856
  cerr <<  "delete name [" << nm <<  "] in ["<< sub << "] failed" << endl;
857
  return false;
858
    }
859
    return true;
860
}
861
862
bool eraseKey(ConfNull *conf, const string& sub)
863
{
864
    if (!conf->ok()) {
865
  cerr <<  "Error opening or parsing file\n" << endl;
866
  return false;
867
    }
868
869
    if (!conf->eraseKey(sub)) {
870
  cerr <<  "delete key [" << sub <<  "] failed" << endl;
871
  return false;
872
    }
873
    return true;
874
}
875
876
bool setvar(ConfNull *conf, const string& nm, const string& value, 
877
      const string& sub)
878
{
879
    if (!conf->ok()) {
880
  cerr <<  "Error opening or parsing file\n" << endl;
881
  return false;
882
    }
883
    if (!conf->set(nm, value, sub)) {
884
  cerr <<  "Set error\n" << endl;
885
  return false;
886
    }
887
    return true;
888
}
889
890
static char usage [] =
891
    "testconftree [opts] filename\n"
892
    "[-w]  : read/write test.\n"
893
    "[-s]  : string parsing test. Filename must hold parm 'strings'\n"
894
    "-a nm value sect : add/set nm,value in 'sect' which can be ''\n"
895
    "-q nm sect : subsection test: look for nm in 'sect' which can be ''\n"
896
    "-d nm sect : delete nm in 'sect' which can be ''\n"
897
    "-E sect : erase key (and all its names)\n"
898
    "-S : string io test. No filename in this case\n"
899
    "-V : volatile config test. No filename in this case\n"
900
    "-U : complex update test. Will erase the named file parameter\n"  
901
    ;
902
903
void Usage() {
904
    fprintf(stderr, "%s:%s\n", thisprog, usage);
905
    exit(1);
906
}
907
static int     op_flags;
908
#define OPT_MOINS 0x1
909
#define OPT_w   0x2 
910
#define OPT_q     0x4
911
#define OPT_s     0x8
912
#define OPT_S     0x10
913
#define OPT_d     0x20
914
#define OPT_V     0x40
915
#define OPT_a     0x80
916
#define OPT_k     0x100
917
#define OPT_E     0x200
918
#define OPT_U      0x400
919
920
int main(int argc, char **argv)
921
{
922
    const char *nm = 0;
923
    const char *sub = 0;
924
    const char *value = 0;
925
926
    thisprog = argv[0];
927
    argc--; argv++;
928
929
    while (argc > 0 && **argv == '-') {
930
  (*argv)++;
931
  if (!(**argv))
932
      /* Cas du "adb - core" */
933
      Usage();
934
  while (**argv)
935
      switch (*(*argv)++) {
936
      case 'a':
937
      op_flags |= OPT_a;
938
      if (argc < 4)  
939
          Usage();
940
      nm = *(++argv);argc--;
941
      value = *(++argv);argc--;
942
      sub = *(++argv);argc--;       
943
      goto b1;
944
      case 'd':
945
      op_flags |= OPT_d;
946
      if (argc < 3)  
947
          Usage();
948
      nm = *(++argv);argc--;
949
      sub = *(++argv);argc--;       
950
      goto b1;
951
      case 'E':   
952
      op_flags |= OPT_E; 
953
      if (argc < 2)
954
          Usage();
955
      sub = *(++argv);argc--;       
956
      goto b1;
957
      case 'k':   op_flags |= OPT_k; break;
958
      case 'q':
959
      op_flags |= OPT_q;
960
      if (argc < 3)  
961
          Usage();
962
      nm = *(++argv);argc--;
963
      sub = *(++argv);argc--;       
964
      goto b1;
965
      case 's':   op_flags |= OPT_s; break;
966
      case 'S':   op_flags |= OPT_S; break;
967
      case 'V':   op_flags |= OPT_V; break;
968
      case 'U':   op_flags |= OPT_U; break;
969
      case 'w':   op_flags |= OPT_w; break;
970
971
      default: Usage();   break;
972
      }
973
    b1: argc--; argv++;
974
    }
975
976
    if ((op_flags & OPT_S)) {
977
  // String storage test
978
  if (argc != 0)
979
      Usage();
980
  string s;
981
  ConfSimple c(s);
982
  memtest(c);
983
  exit(0);
984
    } else if  ((op_flags & OPT_V)) {
985
  // No storage test
986
  if (argc != 0)
987
      Usage();
988
  ConfSimple c;
989
  memtest(c);
990
  exit(0);
991
    } 
992
993
    // Other tests use file(s) as backing store
994
    if (argc < 1)
995
  Usage();
996
997
    if (op_flags & OPT_U) {
998
  exit(!complex_updates(argv[0]));
999
    }
1000
    vector<string> flist;
1001
    while (argc--) {
1002
  flist.push_back(*argv++);
1003
    }
1004
    bool ro = !(op_flags & (OPT_w|OPT_a|OPT_d|OPT_E));
1005
    ConfNull *conf = 0;
1006
    switch (flist.size()) {
1007
    case 0:
1008
  Usage();
1009
  break;
1010
    case 1:
1011
  conf = new ConfTree(flist.front().c_str(), ro);
1012
  break;
1013
    default:
1014
  conf = new ConfStack<ConfTree>(flist, ro);
1015
  break;
1016
    }
1017
1018
    if (op_flags & OPT_w) {
1019
  exit(!readwrite(conf));
1020
    } else if (op_flags & OPT_q) {
1021
  exit(!query(conf, nm, sub));
1022
    } else if (op_flags & OPT_k) {
1023
  if (!conf->ok()) {
1024
      cerr << "conf init error" << endl;
1025
      exit(1);
1026
  }
1027
  vector<string>lst = conf->getSubKeys();
1028
  for (vector<string>::const_iterator it = lst.begin(); 
1029
       it != lst.end(); it++) {
1030
      cout << *it << endl;
1031
  }
1032
  exit(0);
1033
    } else if (op_flags & OPT_a) {
1034
  exit(!setvar(conf, nm, value, sub));
1035
    } else if (op_flags & OPT_d) {
1036
  exit(!erase(conf, nm, sub));
1037
    } else if (op_flags & OPT_E) {
1038
  exit(!eraseKey(conf, sub));
1039
    } else if (op_flags & OPT_s) {
1040
  if (!conf->ok()) {
1041
      cerr << "Cant open /parse conf file " << endl;
1042
      exit(1);
1043
  }
1044
      
1045
  string source;
1046
  if (!conf->get(string("strings"), source, "")) {
1047
      cerr << "Cant get param 'strings'" << endl;
1048
      exit(1);
1049
  }
1050
  cout << "source: [" << source << "]" << endl;
1051
  vector<string> strings;
1052
  if (!stringToStrings(source, strings)) {
1053
      cerr << "parse failed" << endl;
1054
      exit(1);
1055
  }
1056
      
1057
  for (vector<string>::iterator it = strings.begin(); 
1058
       it != strings.end(); it++) {
1059
      cout << "[" << *it << "]" << endl;
1060
  }
1061
       
1062
    } else {
1063
  if (!conf->ok()) {
1064
      fprintf(stderr, "Open failed\n");
1065
      exit(1);
1066
  }
1067
  printf("LIST\n");
1068
  conf->showall();
1069
  //printf("WALK\n");conf->sortwalk(mywalker, 0);
1070
  printf("\nNAMES in global space:\n");
1071
  vector<string> names = conf->getNames("");
1072
  for (vector<string>::iterator it = names.begin();
1073
             it!=names.end(); it++) 
1074
      cout << *it << " ";
1075
        cout << endl;
1076
  printf("\nNAMES in global space matching t* \n");
1077
  names = conf->getNames("", "t*");
1078
  for (vector<string>::iterator it = names.begin();
1079
             it!=names.end(); it++) 
1080
      cout << *it << " ";
1081
        cout << endl;
1082
    }
1083
}
1084
1085
#endif