WvStreams
wvconf.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *
00005  * Implementation of the WvConfigFile class.
00006  *
00007  * Created:     Sept 12 1997            D. Coombs
00008  *
00009  */
00010 #include "wvconf.h"
00011 #include "wvfile.h"
00012 #include "wvstringtable.h"
00013 #include <string.h>
00014 #include <sys/stat.h>
00015 
00016 
00017 void WvConf::setbool(void *userdata,
00018                      WvStringParm sect, WvStringParm ent,
00019                      WvStringParm oldval, WvStringParm newval)
00020 {
00021     if (!*(bool *)userdata)
00022     {
00023         WvLog log("Config Event", WvLog::Debug);
00024         if(sect == "Tunnel Vision" && ent == "Magic Password")
00025             log("Changed:[%s]%s\n",sect, ent);
00026         else
00027             log("Changed: [%s]%s = '%s' -> '%s'\n", sect, ent, oldval, newval);
00028     }
00029     
00030     *(bool *)userdata = true;
00031 }
00032 
00033 void WvConf::addname(void *userdata,
00034                      WvStringParm sect, WvStringParm ent,
00035                      WvStringParm oldval, WvStringParm newval)
00036 {
00037     (*(WvStringList *)userdata).append(new WvString(ent), true);
00038 }
00039 
00040 
00041 void WvConf::addfile(void *userdata,
00042                      WvStringParm sect, WvStringParm ent,
00043                      WvStringParm oldval, WvStringParm newval)
00044 {
00045     WvFile tmp(WvString("/home/%s/%s", ent, *(WvString *)userdata), 
00046                O_WRONLY | O_CREAT | O_TRUNC, 0600);
00047     if(tmp.isok())
00048     {
00049         if(!!newval)
00050             tmp.print("%s\n", newval);
00051         else
00052             tmp.print("%s\n", ent);
00053     }
00054 }
00055 
00056 WvConf::WvConf(WvStringParm _filename, int _create_mode)
00057         : filename(_filename), log(filename), globalsection("")
00058 {
00059     create_mode = _create_mode;
00060     dirty = error = loaded_once = false;
00061     wvauthd = NULL;
00062     load_file();
00063 }
00064 
00065 
00066 int WvConf::check_for_bool_string(const char *s)
00067 {
00068     if (strcasecmp(s, "off") == 0
00069      || strcasecmp(s, "false") == 0
00070      || strncasecmp(s, "no", 2) == 0)   // also handles "none"
00071         return (0);
00072 
00073     if (strcasecmp(s, "on") == 0
00074      || strcasecmp(s, "true") == 0
00075      || strcasecmp(s, "yes") == 0)
00076         return (1);
00077 
00078     // not a special bool case, so just return the number
00079     return (atoi(s));
00080 }
00081 
00082 // parse the WvConf string "request"; pointers to the found section,
00083 // entry, and value fields are stored in *section, *entry, and *value
00084 // respectively, and request[] is modified.
00085 //
00086 // For example, the string:
00087 //         [silly]billy=willy
00088 // is parsed into:
00089 //         section="silly"; entry="billy"; value="willy";
00090 //
00091 // Returns 0 on success, -1 if the command is missing the '[', -2 if the
00092 // string is missing a ']', or -3 if the section or entry is blank.  If a
00093 // "value" is not found (ie. there is no equal sign outside the [] brackets)
00094 // this does not qualify as an error, but *value is set to NULL.
00095 //
00096 int WvConf::parse_wvconf_request(char *request, char *&section,
00097                                  char *&entry, char *&value)
00098 {
00099     //printf("parsing %s\n", request);
00100     entry = value = NULL;
00101     
00102     section = strchr(request, '[');
00103     if (!section)
00104         return -1;
00105 
00106     section++;
00107     
00108     entry = strchr(section, ']');
00109     if (!entry)
00110         return -2;
00111 
00112     *entry++ = 0;
00113     
00114     value = strchr(entry, '=');
00115     if (value)
00116     {
00117         *value++ = 0;
00118         value = trim_string(value);
00119     }
00120     
00121     //printf("section: %s\nentry: %s\n", section, entry);
00122     section = trim_string(section);
00123     entry = trim_string(entry);
00124     
00125     if (!*section)
00126         return -3;
00127     
00128     return 0;
00129 }
00130 
00131 
00132 // This "int" version of get is smart enough to interpret words like on/off,
00133 // true/false, and yes/no.
00134 int WvConf::getint(WvStringParm section, WvStringParm entry, int def_val)
00135 {
00136     WvString def_str(def_val);
00137     return check_for_bool_string(get(section, entry, def_str));
00138 }
00139 
00140 
00141 // This "int" version of fuzzy_get is smart enough to interpret words like 
00142 // on/off, true/false, and yes/no.
00143 int WvConf::fuzzy_getint(WvStringList &section, WvStringList &entry,
00144                          int def_val)
00145 {
00146     WvString def_str(def_val);
00147     return check_for_bool_string(fuzzy_get(section, entry, def_str));
00148 }
00149 
00150 
00151 // This "int" version of fuzzy_get is smart enough to interpret words like 
00152 // on/off, true/false, and yes/no.
00153 int WvConf::fuzzy_getint(WvStringList &section, WvStringParm entry,
00154                          int def_val)
00155 {
00156     WvString def_str(def_val);
00157     return check_for_bool_string(fuzzy_get(section, entry, def_str));
00158 }
00159 
00160 
00161 void WvConf::setint(WvStringParm section, WvStringParm entry, int value)
00162 {
00163     WvString def_str(value);
00164     set(section, entry, def_str);
00165 }
00166 
00167 
00168 // only set the value if it isn't already in the config file
00169 void WvConf::maybesetint(WvStringParm section, WvStringParm entry,
00170                          int value)
00171 {
00172     if (!get(section, entry, NULL))
00173         setint(section, entry, value);
00174 }
00175 
00176  
00177 void WvConf::load_file(WvStringParm filename)
00178 {
00179     const char *p;
00180     char *from_file;
00181     WvConfigSection *sect = &globalsection;
00182     bool quick_mode = false;
00183 
00184     WvFile file(filename, O_RDONLY);
00185 
00186     #ifdef _WIN32
00187     //FIXME: Windows doesn't have a sticky bit so we can't use that to signal other processes that
00188     //  the file is being written to. Just be careful :).
00189     #else
00190     // check the sticky bit and fail if set
00191     struct stat statbuf;
00192     if (file.isok() && fstat(file.getrfd(), &statbuf) == -1)
00193     {
00194         log(WvLog::Warning, "Can't stat config file %s\n", filename);
00195         file.close();
00196     }
00197 
00198     if (file.isok() && (statbuf.st_mode & S_ISVTX))
00199     {
00200         file.close();
00201         file.seterr(EAGAIN);
00202     }
00203     #endif
00204 
00205     if (!file.isok())
00206     {
00207         // Could not open for read.
00208         // ...actually, this warning is mainly just annoying.
00209         //log(loaded_once ? WvLog::Debug1 : WvLog::Warning,
00210         //    "Can't read config file %s: %s\n", filename, file.errstr());
00211         if (file.geterr() != ENOENT && !loaded_once)
00212             error = true;
00213         return;
00214     }
00215 
00216     while ((from_file = trim_string(file.getline())) != NULL)
00217     {
00218 
00219         if ((p = parse_section(from_file)) != NULL)
00220         {
00221             quick_mode = false;
00222             
00223             // a new section?
00224             if (!p[0])          // blank name: global section
00225                 sect = &globalsection;
00226             else
00227             {
00228                 sect = (*this)[p];
00229                 if (!sect)
00230                 {
00231                     sect = new WvConfigSection(p);
00232                     append(sect, true);
00233                     quick_mode = true;
00234                 }
00235             }
00236         }
00237         else
00238         {
00239             // it must be an element for the current section *sect.
00240             p = parse_value(from_file);
00241             if (!p)
00242                 p = "";         // allow empty entries
00243 
00244             from_file = trim_string(from_file);
00245             if (from_file[0])   // nonblank option name
00246             {
00247                 if (quick_mode)
00248                     sect->quick_set(from_file, p);
00249                 else
00250                     sect->set(from_file, p);
00251             }
00252         }
00253     }
00254     
00255     run_all_callbacks();
00256 
00257     loaded_once = true;
00258 }
00259 
00260 
00261 WvConf::~WvConf()
00262 {
00263     // We don't really have to do anything here.  sections's destructor
00264     // will go through and delete all its entries, so we should be fine.
00265 
00266     flush();
00267 }
00268 
00269 
00270 const char *WvConf::get(WvStringParm section, WvStringParm entry,
00271                         const char *def_val)
00272 {
00273     WvStringTable cache(5);
00274     WvConfigSection *s;
00275     
00276     for(s = (*this)[section];
00277         s && !cache[s->name];
00278         s = (*s)["Inherits"] ? (*this)[(*s)["Inherits"]->value] : NULL)
00279     {
00280         const char *ret = s->get(entry);
00281         if (ret) return ret;
00282         cache.add(&s->name, false);
00283     }
00284 
00285     return globalsection.get(entry, def_val);
00286 }
00287 
00288 
00289 // Gets an entry, given a string in the form [section]entry=value.  Returns
00290 // the value or NULL if not found.  The parameter parse_error is set to the
00291 // return value of parse_wvconf_request.
00292 WvString WvConf::getraw(WvString wvconfstr, int &parse_error)
00293 {
00294     char *section, *entry, *value;
00295     parse_error = parse_wvconf_request(wvconfstr.edit(),
00296                                        section, entry, value);
00297 
00298     if (parse_error)
00299         return WvString();
00300 
00301     return get(section, entry, value);
00302 }
00303 
00304 
00305 const char *WvConf::fuzzy_get(WvStringList &sections, WvStringList &entries,
00306                               const char *def_val)
00307 {
00308     WvStringList::Iter i(sections), i2(entries);
00309     WvStringTable cache(5);
00310     WvConfigSection *s;
00311 
00312     for (i.rewind(); i.next(); )
00313     {
00314         for (i2.rewind(); i2.next();)
00315         {
00316             for(s = (*this)[*i];
00317                 s && !cache[s->name];
00318                 s = (*s)["Inherits"] ? (*this)[(*s)["Inherits"]->value] : NULL)
00319             {
00320                 const char *ret = s->get(*i2);
00321                 if (ret) return ret;
00322                 cache.add(&s->name, false);
00323             }
00324         }
00325     }
00326     
00327     return def_val;
00328 }
00329 
00330 
00331 const char *WvConf::fuzzy_get(WvStringList &sections, WvStringParm entry,
00332                               const char *def_val)
00333 {
00334     WvStringList::Iter i(sections);
00335     WvStringTable cache(5);
00336     WvConfigSection *s;
00337 
00338     for (i.rewind(); i.next(); )
00339     {
00340         for(s = (*this)[*i];
00341             s && !cache[s->name];
00342             s = (*s)["Inherits"] ? (*this)[(*s)["Inherits"]->value] : NULL)
00343         {
00344             const char *ret = s->get(entry);
00345             if (ret) return ret;
00346             cache.add(&s->name, false);
00347         }
00348     }
00349 
00350     return def_val;
00351 }
00352 
00353 
00354 void WvConf::set(WvStringParm section, WvStringParm entry,
00355                  const char *value)
00356 {
00357     WvConfigSection *s = (*this)[section];
00358     
00359     // section does not exist yet
00360     if (!s)
00361     {
00362         if (!value || !value[0])
00363             return; // no section, no entry, no problem!
00364         
00365         s = new WvConfigSection(section);
00366         append(s, true);
00367     }
00368     
00369     const char *oldval = s->get(entry, "");
00370     if (!value) value = "";
00371     if (strcmp(oldval, value)) // case sensitive
00372     {
00373         run_callbacks(section, entry, oldval, value);
00374 
00375         /* fprintf(stderr, "cfg.set: set [%s]%s = %s\n",
00376                 (const char *)section, (const char *)entry,
00377                 (const char *)value ?: "!!!"); */
00378     
00379         s->set(entry, value);
00380         dirty = true;
00381     }
00382 }
00383 
00384 
00385 // Takes a string in the form [section]entry=value and sets it.  Returns an
00386 // error code as defined in parse_wvconf_request.  The value parameter is
00387 // also set to the value (useful in rcommand, when we display the value after
00388 // it has been set).
00389 void WvConf::setraw(WvString wvconfstr, const char *&xvalue, int &parse_error)
00390 {
00391     char *section, *entry, *value;
00392     parse_error = parse_wvconf_request(wvconfstr.edit(),
00393                                        section, entry, value);
00394     if (!parse_error)
00395     {
00396         set(section, entry, value);
00397         xvalue = get(section, entry, value);
00398     }
00399     else
00400         xvalue = NULL;
00401 }
00402 
00403 
00404 // only set the value if it isn't already in the config file
00405 void WvConf::maybeset(WvStringParm section, WvStringParm entry,
00406                       const char *value)
00407 {
00408     if (value && !get(section, entry, NULL))
00409         set(section, entry, value);
00410 }
00411 
00412 
00413 WvConfigSection *WvConf::operator[] (WvStringParm section)
00414 {
00415     Iter i(*this);
00416 
00417     if (section)
00418         for (i.rewind(); i.next(); )
00419         {
00420             if (strcasecmp(i().name, section) == 0)
00421                 return &i();
00422         }
00423 
00424     return NULL;
00425 }
00426 
00427 
00428 void WvConf::delete_section(WvStringParm section)
00429 {
00430     WvConfigSection *s = (*this)[section];
00431     if (s)
00432     {
00433         unlink(s);
00434         dirty = true;
00435     }
00436 }
00437 
00438 
00439 char *WvConf::parse_section(char *s)
00440 {
00441     char *q;
00442 
00443     if (s[0] != '[')
00444         return (NULL);
00445 
00446     q = strchr(s, ']');
00447     if (!q || q[1])
00448         return (NULL);
00449 
00450     *q = 0;
00451     return trim_string(s + 1);
00452 }
00453 
00454 
00455 char *WvConf::parse_value(char *s)
00456 {
00457     char *q;
00458 
00459     q = strchr(s, '=');
00460     if (q == NULL)
00461         return (NULL);
00462 
00463     *q++ = 0;                   // 's' points to option name, 'q' points to value
00464 
00465     return (trim_string(q));
00466 }
00467 
00468 
00469 void WvConf::save(WvStringParm _filename)
00470 {
00471     if (error || !_filename)
00472         return;
00473     
00474     WvFile fp(_filename, O_WRONLY|O_CREAT|O_TRUNC, create_mode);
00475 
00476     if (!fp.isok())
00477     {
00478         log(WvLog::Error, "Can't write to config file %s: %s\n",
00479             _filename, strerror(errno));
00480         if (fp.geterr() != ENOENT)
00481             error = true;
00482         return;
00483     }
00484 
00485     #ifdef _WIN32
00486     //FIXME: Windows doesn't have a sticky bit so we can't use that to signal other processes that
00487     //  the file is being written to. Just be careful :).
00488     #else
00489     struct stat statbuf;
00490     if (fstat(fp.getwfd(), &statbuf) == -1)
00491     {
00492         log(WvLog::Error, "Can't stat config file %s: %s\n",
00493             _filename, strerror(errno));
00494         error = true;
00495         return;
00496     }
00497 
00498     fchmod(fp.getwfd(), (statbuf.st_mode & 07777) | S_ISVTX);
00499     #endif
00500 
00501     globalsection.dump(fp);
00502     
00503     Iter i(*this);
00504     for (i.rewind(); i.next();)
00505     {
00506         WvConfigSection & sect = *i;
00507         fp.print("\n[%s]\n", sect.name);
00508         sect.dump(fp);
00509     }
00510 
00511     #ifdef _WIN32
00512     //FIXME: Windows doesn't have a sticky bit so we can't use that to signal other processes that
00513     //  the file is being written to. Just be careful :).
00514     #else
00515     fchmod(fp.getwfd(), statbuf.st_mode & 07777);
00516     #endif
00517 }
00518 
00519 
00520 void WvConf::save()
00521 {
00522     save(filename);
00523 }
00524 
00525 
00526 // only save the config file if it's dirty
00527 void WvConf::flush()
00528 {
00529     if (!dirty || error)
00530         return;
00531     
00532     // save under default filename
00533     save(filename);
00534     
00535     dirty = false;
00536 }
00537 
00538 
00539 void WvConf::add_callback(WvConfCallback callback, void *userdata,
00540                           WvStringParm section, WvStringParm entry,
00541                           void *cookie)
00542 {
00543     callbacks.append(new WvConfCallbackInfo(callback, userdata,
00544                                             section, entry, cookie), true);
00545 }
00546 
00547 
00548 void WvConf::del_callback(WvStringParm section, WvStringParm entry,
00549                           void *cookie)
00550 {
00551     WvConfCallbackInfoList::Iter i(callbacks);
00552     
00553     for (i.rewind(); i.next(); )
00554     {
00555         if (i->cookie == cookie && i->section == section && i->entry == entry)
00556         {
00557             i.unlink();
00558             return;
00559         }
00560     }
00561 }
00562 
00563 
00564 void WvConf::run_callbacks(WvStringParm section, WvStringParm entry,
00565                            WvStringParm oldvalue, WvStringParm newvalue)
00566 {
00567     WvConfCallbackInfoList::Iter i(callbacks);
00568     
00569     for (i.rewind(); i.next(); )
00570     {
00571         if (!i->section || !strcasecmp(i->section, section))
00572         {
00573             if (!i->entry || !strcasecmp(i->entry, entry))
00574                 i->callback(i->userdata, section, entry,
00575                             oldvalue, newvalue);
00576         }
00577     }
00578 }
00579 
00580 
00581 void WvConf::run_all_callbacks()
00582 {
00583     WvConfCallbackInfoList::Iter i(callbacks);
00584 
00585     for (i.rewind(); i.next(); )
00586         i->callback(i->userdata, "", "", "", "");
00587 }