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