WvStreams
wvconf.cc
1 /*
2  * Worldvisions Weaver Software:
3  * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4  *
5  * Implementation of the WvConfigFile class.
6  *
7  * Created: Sept 12 1997 D. Coombs
8  *
9  */
10 #include "wvconf.h"
11 #include "wvfile.h"
12 #include "wvstringtable.h"
13 #include <string.h>
14 #include <sys/stat.h>
15 
16 
17 void WvConf::setbool(void *userdata,
18  WvStringParm sect, WvStringParm ent,
19  WvStringParm oldval, WvStringParm newval)
20 {
21  if (!*(bool *)userdata)
22  {
23  WvLog log("Config Event", WvLog::Debug);
24  if(sect == "Tunnel Vision" && ent == "Magic Password")
25  log("Changed:[%s]%s\n",sect, ent);
26  else
27  log("Changed: [%s]%s = '%s' -> '%s'\n", sect, ent, oldval, newval);
28  }
29 
30  *(bool *)userdata = true;
31 }
32 
33 void WvConf::addname(void *userdata,
34  WvStringParm sect, WvStringParm ent,
35  WvStringParm oldval, WvStringParm newval)
36 {
37  (*(WvStringList *)userdata).append(new WvString(ent), true);
38 }
39 
40 
41 void WvConf::addfile(void *userdata,
42  WvStringParm sect, WvStringParm ent,
43  WvStringParm oldval, WvStringParm newval)
44 {
45  WvFile tmp(WvString("/home/%s/%s", ent, *(WvString *)userdata),
46  O_WRONLY | O_CREAT | O_TRUNC, 0600);
47  if(tmp.isok())
48  {
49  if(!!newval)
50  tmp.print("%s\n", newval);
51  else
52  tmp.print("%s\n", ent);
53  }
54 }
55 
56 WvConf::WvConf(WvStringParm _filename, int _create_mode)
57  : filename(_filename), log(filename), globalsection("")
58 {
59  create_mode = _create_mode;
60  dirty = error = loaded_once = false;
61  wvauthd = NULL;
62  load_file();
63 }
64 
65 
66 int WvConf::check_for_bool_string(const char *s)
67 {
68  if (strcasecmp(s, "off") == 0
69  || strcasecmp(s, "false") == 0
70  || strncasecmp(s, "no", 2) == 0) // also handles "none"
71  return (0);
72 
73  if (strcasecmp(s, "on") == 0
74  || strcasecmp(s, "true") == 0
75  || strcasecmp(s, "yes") == 0)
76  return (1);
77 
78  // not a special bool case, so just return the number
79  return (atoi(s));
80 }
81 
82 // parse the WvConf string "request"; pointers to the found section,
83 // entry, and value fields are stored in *section, *entry, and *value
84 // respectively, and request[] is modified.
85 //
86 // For example, the string:
87 // [silly]billy=willy
88 // is parsed into:
89 // section="silly"; entry="billy"; value="willy";
90 //
91 // Returns 0 on success, -1 if the command is missing the '[', -2 if the
92 // string is missing a ']', or -3 if the section or entry is blank. If a
93 // "value" is not found (ie. there is no equal sign outside the [] brackets)
94 // this does not qualify as an error, but *value is set to NULL.
95 //
96 int WvConf::parse_wvconf_request(char *request, char *&section,
97  char *&entry, char *&value)
98 {
99  //printf("parsing %s\n", request);
100  entry = value = NULL;
101 
102  section = strchr(request, '[');
103  if (!section)
104  return -1;
105 
106  section++;
107 
108  entry = strchr(section, ']');
109  if (!entry)
110  return -2;
111 
112  *entry++ = 0;
113 
114  value = strchr(entry, '=');
115  if (value)
116  {
117  *value++ = 0;
118  value = trim_string(value);
119  }
120 
121  //printf("section: %s\nentry: %s\n", section, entry);
122  section = trim_string(section);
123  entry = trim_string(entry);
124 
125  if (!*section)
126  return -3;
127 
128  return 0;
129 }
130 
131 
132 // This "int" version of get is smart enough to interpret words like on/off,
133 // true/false, and yes/no.
134 int WvConf::getint(WvStringParm section, WvStringParm entry, int def_val)
135 {
136  WvString def_str(def_val);
137  return check_for_bool_string(get(section, entry, def_str));
138 }
139 
140 
141 // This "int" version of fuzzy_get is smart enough to interpret words like
142 // on/off, true/false, and yes/no.
143 int WvConf::fuzzy_getint(WvStringList &section, WvStringList &entry,
144  int def_val)
145 {
146  WvString def_str(def_val);
147  return check_for_bool_string(fuzzy_get(section, entry, def_str));
148 }
149 
150 
151 // This "int" version of fuzzy_get is smart enough to interpret words like
152 // on/off, true/false, and yes/no.
153 int WvConf::fuzzy_getint(WvStringList &section, WvStringParm entry,
154  int def_val)
155 {
156  WvString def_str(def_val);
157  return check_for_bool_string(fuzzy_get(section, entry, def_str));
158 }
159 
160 
161 void WvConf::setint(WvStringParm section, WvStringParm entry, int value)
162 {
163  WvString def_str(value);
164  set(section, entry, def_str);
165 }
166 
167 
168 // only set the value if it isn't already in the config file
169 void WvConf::maybesetint(WvStringParm section, WvStringParm entry,
170  int value)
171 {
172  if (!get(section, entry, NULL))
173  setint(section, entry, value);
174 }
175 
176 
177 void WvConf::load_file(WvStringParm filename)
178 {
179  const char *p;
180  char *from_file;
181  WvConfigSection *sect = &globalsection;
182  bool quick_mode = false;
183 
184  WvFile file(filename, O_RDONLY);
185 
186  #ifdef _WIN32
187  //FIXME: Windows doesn't have a sticky bit so we can't use that to signal other processes that
188  // the file is being written to. Just be careful :).
189  #else
190  // check the sticky bit and fail if set
191  struct stat statbuf;
192  if (file.isok() && fstat(file.getrfd(), &statbuf) == -1)
193  {
194  log(WvLog::Warning, "Can't stat config file %s\n", filename);
195  file.close();
196  }
197 
198  if (file.isok() && (statbuf.st_mode & S_ISVTX))
199  {
200  file.close();
201  file.seterr(EAGAIN);
202  }
203  #endif
204 
205  if (!file.isok())
206  {
207  // Could not open for read.
208  // ...actually, this warning is mainly just annoying.
209  //log(loaded_once ? WvLog::Debug1 : WvLog::Warning,
210  // "Can't read config file %s: %s\n", filename, file.errstr());
211  if (file.geterr() != ENOENT && !loaded_once)
212  error = true;
213  return;
214  }
215 
216  while ((from_file = trim_string(file.getline())) != NULL)
217  {
218 
219  if ((p = parse_section(from_file)) != NULL)
220  {
221  quick_mode = false;
222 
223  // a new section?
224  if (!p[0]) // blank name: global section
225  sect = &globalsection;
226  else
227  {
228  sect = (*this)[p];
229  if (!sect)
230  {
231  sect = new WvConfigSection(p);
232  append(sect, true);
233  quick_mode = true;
234  }
235  }
236  }
237  else
238  {
239  // it must be an element for the current section *sect.
240  p = parse_value(from_file);
241  if (!p)
242  p = ""; // allow empty entries
243 
244  from_file = trim_string(from_file);
245  if (from_file[0]) // nonblank option name
246  {
247  if (quick_mode)
248  sect->quick_set(from_file, p);
249  else
250  sect->set(from_file, p);
251  }
252  }
253  }
254 
255  run_all_callbacks();
256 
257  loaded_once = true;
258 }
259 
260 
261 WvConf::~WvConf()
262 {
263  // We don't really have to do anything here. sections's destructor
264  // will go through and delete all its entries, so we should be fine.
265 
266  flush();
267 }
268 
269 
270 const char *WvConf::get(WvStringParm section, WvStringParm entry,
271  const char *def_val)
272 {
273  WvStringTable cache(5);
274  WvConfigSection *s;
275 
276  for(s = (*this)[section];
277  s && !cache[s->name];
278  s = (*s)["Inherits"] ? (*this)[(*s)["Inherits"]->value] : NULL)
279  {
280  const char *ret = s->get(entry);
281  if (ret) return ret;
282  cache.add(&s->name, false);
283  }
284 
285  return globalsection.get(entry, def_val);
286 }
287 
288 
289 // Gets an entry, given a string in the form [section]entry=value. Returns
290 // the value or NULL if not found. The parameter parse_error is set to the
291 // return value of parse_wvconf_request.
292 WvString WvConf::getraw(WvString wvconfstr, int &parse_error)
293 {
294  char *section, *entry, *value;
295  parse_error = parse_wvconf_request(wvconfstr.edit(),
296  section, entry, value);
297 
298  if (parse_error)
299  return WvString();
300 
301  return get(section, entry, value);
302 }
303 
304 
305 const char *WvConf::fuzzy_get(WvStringList &sections, WvStringList &entries,
306  const char *def_val)
307 {
308  WvStringList::Iter i(sections), i2(entries);
309  WvStringTable cache(5);
310  WvConfigSection *s;
311 
312  for (i.rewind(); i.next(); )
313  {
314  for (i2.rewind(); i2.next();)
315  {
316  for(s = (*this)[*i];
317  s && !cache[s->name];
318  s = (*s)["Inherits"] ? (*this)[(*s)["Inherits"]->value] : NULL)
319  {
320  const char *ret = s->get(*i2);
321  if (ret) return ret;
322  cache.add(&s->name, false);
323  }
324  }
325  }
326 
327  return def_val;
328 }
329 
330 
331 const char *WvConf::fuzzy_get(WvStringList &sections, WvStringParm entry,
332  const char *def_val)
333 {
334  WvStringList::Iter i(sections);
335  WvStringTable cache(5);
336  WvConfigSection *s;
337 
338  for (i.rewind(); i.next(); )
339  {
340  for(s = (*this)[*i];
341  s && !cache[s->name];
342  s = (*s)["Inherits"] ? (*this)[(*s)["Inherits"]->value] : NULL)
343  {
344  const char *ret = s->get(entry);
345  if (ret) return ret;
346  cache.add(&s->name, false);
347  }
348  }
349 
350  return def_val;
351 }
352 
353 
354 void WvConf::set(WvStringParm section, WvStringParm entry,
355  const char *value)
356 {
357  WvConfigSection *s = (*this)[section];
358 
359  // section does not exist yet
360  if (!s)
361  {
362  if (!value || !value[0])
363  return; // no section, no entry, no problem!
364 
365  s = new WvConfigSection(section);
366  append(s, true);
367  }
368 
369  const char *oldval = s->get(entry, "");
370  if (!value) value = "";
371  if (strcmp(oldval, value)) // case sensitive
372  {
373  run_callbacks(section, entry, oldval, value);
374 
375  /* fprintf(stderr, "cfg.set: set [%s]%s = %s\n",
376  (const char *)section, (const char *)entry,
377  (const char *)value ?: "!!!"); */
378 
379  s->set(entry, value);
380  dirty = true;
381  }
382 }
383 
384 
385 // Takes a string in the form [section]entry=value and sets it. Returns an
386 // error code as defined in parse_wvconf_request. The value parameter is
387 // also set to the value (useful in rcommand, when we display the value after
388 // it has been set).
389 void WvConf::setraw(WvString wvconfstr, const char *&xvalue, int &parse_error)
390 {
391  char *section, *entry, *value;
392  parse_error = parse_wvconf_request(wvconfstr.edit(),
393  section, entry, value);
394  if (!parse_error)
395  {
396  set(section, entry, value);
397  xvalue = get(section, entry, value);
398  }
399  else
400  xvalue = NULL;
401 }
402 
403 
404 // only set the value if it isn't already in the config file
405 void WvConf::maybeset(WvStringParm section, WvStringParm entry,
406  const char *value)
407 {
408  if (value && !get(section, entry, NULL))
409  set(section, entry, value);
410 }
411 
412 
413 WvConfigSection *WvConf::operator[] (WvStringParm section)
414 {
415  Iter i(*this);
416 
417  if (section)
418  for (i.rewind(); i.next(); )
419  {
420  if (strcasecmp(i().name, section) == 0)
421  return &i();
422  }
423 
424  return NULL;
425 }
426 
427 
428 void WvConf::delete_section(WvStringParm section)
429 {
430  WvConfigSection *s = (*this)[section];
431  if (s)
432  {
433  unlink(s);
434  dirty = true;
435  }
436 }
437 
438 
439 char *WvConf::parse_section(char *s)
440 {
441  char *q;
442 
443  if (s[0] != '[')
444  return (NULL);
445 
446  q = strchr(s, ']');
447  if (!q || q[1])
448  return (NULL);
449 
450  *q = 0;
451  return trim_string(s + 1);
452 }
453 
454 
455 char *WvConf::parse_value(char *s)
456 {
457  char *q;
458 
459  q = strchr(s, '=');
460  if (q == NULL)
461  return (NULL);
462 
463  *q++ = 0; // 's' points to option name, 'q' points to value
464 
465  return (trim_string(q));
466 }
467 
468 
469 void WvConf::save(WvStringParm _filename)
470 {
471  if (error || !_filename)
472  return;
473 
474  WvFile fp(_filename, O_WRONLY|O_CREAT|O_TRUNC, create_mode);
475 
476  if (!fp.isok())
477  {
478  log(WvLog::Error, "Can't write to config file %s: %s\n",
479  _filename, strerror(errno));
480  if (fp.geterr() != ENOENT)
481  error = true;
482  return;
483  }
484 
485  #ifdef _WIN32
486  //FIXME: Windows doesn't have a sticky bit so we can't use that to signal other processes that
487  // the file is being written to. Just be careful :).
488  #else
489  struct stat statbuf;
490  if (fstat(fp.getwfd(), &statbuf) == -1)
491  {
492  log(WvLog::Error, "Can't stat config file %s: %s\n",
493  _filename, strerror(errno));
494  error = true;
495  return;
496  }
497 
498  fchmod(fp.getwfd(), (statbuf.st_mode & 07777) | S_ISVTX);
499  #endif
500 
501  globalsection.dump(fp);
502 
503  Iter i(*this);
504  for (i.rewind(); i.next();)
505  {
506  WvConfigSection & sect = *i;
507  fp.print("\n[%s]\n", sect.name);
508  sect.dump(fp);
509  }
510 
511  #ifdef _WIN32
512  //FIXME: Windows doesn't have a sticky bit so we can't use that to signal other processes that
513  // the file is being written to. Just be careful :).
514  #else
515  fchmod(fp.getwfd(), statbuf.st_mode & 07777);
516  #endif
517 }
518 
519 
520 void WvConf::save()
521 {
522  save(filename);
523 }
524 
525 
526 // only save the config file if it's dirty
527 void WvConf::flush()
528 {
529  if (!dirty || error)
530  return;
531 
532  // save under default filename
533  save(filename);
534 
535  dirty = false;
536 }
537 
538 
539 void WvConf::add_callback(WvConfCallback callback, void *userdata,
540  WvStringParm section, WvStringParm entry,
541  void *cookie)
542 {
543  callbacks.append(new WvConfCallbackInfo(callback, userdata,
544  section, entry, cookie), true);
545 }
546 
547 
548 void WvConf::del_callback(WvStringParm section, WvStringParm entry,
549  void *cookie)
550 {
551  WvConfCallbackInfoList::Iter i(callbacks);
552 
553  for (i.rewind(); i.next(); )
554  {
555  if (i->cookie == cookie && i->section == section && i->entry == entry)
556  {
557  i.unlink();
558  return;
559  }
560  }
561 }
562 
563 
564 void WvConf::run_callbacks(WvStringParm section, WvStringParm entry,
565  WvStringParm oldvalue, WvStringParm newvalue)
566 {
567  WvConfCallbackInfoList::Iter i(callbacks);
568 
569  for (i.rewind(); i.next(); )
570  {
571  if (!i->section || !strcasecmp(i->section, section))
572  {
573  if (!i->entry || !strcasecmp(i->entry, entry))
574  i->callback(i->userdata, section, entry,
575  oldvalue, newvalue);
576  }
577  }
578 }
579 
580 
581 void WvConf::run_all_callbacks()
582 {
583  WvConfCallbackInfoList::Iter i(callbacks);
584 
585  for (i.rewind(); i.next(); )
586  i->callback(i->userdata, "", "", "", "");
587 }
WvString::edit
char * edit()
make the string editable, and return a non-const (char*)
Definition: wvstring.h:397
WvFile
WvFile implements a stream connected to a file or Unix device.
Definition: wvfile.h:28
trim_string
char * trim_string(char *string)
Trims whitespace from the beginning and end of the character string, including carriage return / line...
Definition: strutils.cc:59
WvStringTable
Definition: wvstringtable.h:17
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
get
Interface * get(IObject *aObj)
XPLC equivalent to dynamic_cast.
Definition: utils.h:184
WvLog
A WvLog stream accepts log messages from applications and forwards them to all registered WvLogRcv's.
Definition: wvlog.h:56
WvConfCallbackInfo
Definition: wvconf.h:78
WvConfigSection
Definition: wvconf.h:53
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27