uniinigen.cc

00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * A generator for .ini files.
00006  */
00007 #include "uniinigen.h"
00008 #include "strutils.h"
00009 #include "unitempgen.h"
00010 #include "wvfile.h"
00011 #include "wvmoniker.h"
00012 #include "wvstringmask.h"
00013 #include "wvtclstring.h"
00014 #include <ctype.h>
00015 #include "wvlinkerhack.h"
00016 
00017 WV_LINK(UniIniGen);
00018 
00019 
00020 static IUniConfGen *creator(WvStringParm s)
00021 {
00022     return new UniIniGen(s);
00023 }
00024 
00025 WvMoniker<IUniConfGen> UniIniGenMoniker("ini", creator);
00026 
00027 
00028 // forward declarations
00029 static void printkey(WvStream &file, const UniConfKey &_key,
00030                      WvStringParm _value);
00031 
00032 
00033 /***** UniIniGen *****/
00034 
00035 UniIniGen::UniIniGen(WvStringParm _filename, int _create_mode)
00036     : filename(_filename), create_mode(_create_mode), log(_filename)
00037 {
00038     // Create the root, since this generator can't handle it not existing.
00039     UniTempGen::set(UniConfKey::EMPTY, WvString::empty);
00040     memset(&old_st, 0, sizeof(old_st));
00041 }
00042 
00043 
00044 void UniIniGen::set(const UniConfKey &key, WvStringParm value)
00045 {
00046     // Don't allow people to delete the root, since this generator can't
00047     // handle it not existing.
00048     if (!(value.isnull() && key.isempty()))
00049         UniTempGen::set(key, value);
00050 }
00051 
00052 
00053 UniIniGen::~UniIniGen()
00054 {
00055 }
00056 
00057 
00058 bool UniIniGen::refresh()
00059 {
00060     WvFile file(filename, O_RDONLY);
00061 
00062 #ifndef _WIN32
00063     struct stat statbuf;
00064     if (file.isok() && fstat(file.getrfd(), &statbuf) == -1)
00065     {
00066         log(WvLog::Warning, "Can't stat '%s': %s\n",
00067             filename, strerror(errno));
00068         file.close();
00069     }
00070 
00071     if (file.isok() && (statbuf.st_mode & S_ISVTX))
00072     {
00073         file.close();
00074         file.seterr(EAGAIN);
00075     }
00076     
00077     if (file.isok() // guarantes statbuf is valid from above
00078         && statbuf.st_ctime == old_st.st_ctime
00079         && statbuf.st_dev == old_st.st_dev
00080         && statbuf.st_ino == old_st.st_ino
00081         && statbuf.st_blocks == old_st.st_blocks
00082         && statbuf.st_size == old_st.st_size)
00083     {
00084         log(WvLog::Debug3, "refresh: file hasn't changed; do nothing.\n");
00085         return true;
00086     }
00087     memcpy(&old_st, &statbuf, sizeof(statbuf));
00088 #endif
00089 
00090     if (!file.isok())
00091     {
00092         log(WvLog::Warning, 
00093             "Can't open '%s' for reading: %s\n"
00094             "...starting with blank configuration.\n",
00095             filename, file.errstr());
00096         return false;
00097     }
00098     
00099     // loop over all Tcl words in the file
00100     UniTempGen *newgen = new UniTempGen();
00101     newgen->set(UniConfKey::EMPTY, WvString::empty);
00102     UniConfKey section;
00103     WvDynBuf buf;
00104     while (buf.used() || file.isok())
00105     {
00106         if (file.isok())
00107         {
00108             // read entire lines to ensure that we get whole values
00109             char *line = file.blocking_getline(-1);
00110             if (line)
00111             {
00112                 buf.putstr(line);
00113                 buf.put('\n'); // this was auto-stripped by getline()
00114             }
00115         }
00116 
00117         WvString word;
00118         while (!(word = wvtcl_getword(buf,
00119                                       WVTCL_NASTY_NEWLINES,
00120                                       false)).isnull())
00121         {
00122             //log(WvLog::Info, "LINE: '%s'\n", word);
00123             
00124             char *str = trim_string(word.edit());
00125             int len = strlen(str);
00126             if (len == 0) continue; // blank line
00127             
00128             if (str[0] == '#')
00129             {
00130                 // a comment line.  FIXME: we drop it completely!
00131                 //log(WvLog::Debug5, "Comment: \"%s\"\n", str + 1);
00132                 continue;
00133             }
00134             
00135             if (str[0] == '[' && str[len - 1] == ']')
00136             {
00137                 // a section name
00138                 str[len - 1] = '\0';
00139                 WvString name(wvtcl_unescape(trim_string(str + 1)));
00140                 section = UniConfKey(name);
00141                 //log(WvLog::Debug5, "Refresh section: \"%s\"\n", section);
00142                 continue;
00143             }
00144             
00145             // we possibly have a key = value line
00146             WvConstStringBuffer line(word);
00147             static const WvStringMask nasty_equals("=");
00148             WvString name = wvtcl_getword(line, nasty_equals, false);
00149             if (!name.isnull() && line.used())
00150             {
00151                 name = wvtcl_unescape(trim_string(name.edit()));
00152                 
00153                 if (!!name)
00154                 {
00155                     UniConfKey key(name);
00156                     key.prepend(section);
00157                     
00158                     WvString value = line.getstr();
00159                     assert(*value == '=');
00160                     value = wvtcl_unescape(trim_string(value.edit() + 1));
00161                     newgen->set(key, value.unique());
00162 
00163                     //log(WvLog::Debug5, "Refresh: (\"%s\", \"%s\")\n",
00164                     //    key, value);
00165                     continue;
00166                 }
00167             }
00168             
00169             // if we get here, the line was tcl-decoded but not useful.
00170             log(WvLog::Warning,
00171                 "Ignoring malformed input line: \"%s\"\n", word);
00172         }
00173         
00174         if (buf.used() && !file.isok())
00175         {
00176             // EOF and some of the data still hasn't been used.  Weird.
00177             // Let's remove a line of data and try again.
00178             size_t offset = buf.strchr('\n');
00179             assert(offset); // the last thing we put() is *always* a newline!
00180             WvString line1(trim_string(buf.getstr(offset).edit()));
00181             if (!!line1) // not just whitespace
00182                 log(WvLog::Warning,
00183                     "XXX Ignoring malformed input line: \"%s\"\n", line1);
00184         }
00185     }
00186 
00187     if (file.geterr())
00188     {
00189         log(WvLog::Warning, 
00190             "Error reading from config file: %s\n", file.errstr());
00191         WVRELEASE(newgen);
00192         return false;
00193     }
00194 
00195     // switch the trees and send notifications
00196     hold_delta();
00197     UniConfValueTree *oldtree = root;
00198     UniConfValueTree *newtree = newgen->root;
00199     root = newtree;
00200     newgen->root = NULL;
00201     dirty = false;
00202     oldtree->compare(newtree, UniConfValueTree::Comparator
00203             (this, &UniIniGen::refreshcomparator), NULL);
00204     
00205     delete oldtree;
00206     unhold_delta();
00207 
00208     WVRELEASE(newgen);
00209 
00210     UniTempGen::refresh();
00211     return true;
00212 }
00213 
00214 
00215 // returns: true if a==b
00216 bool UniIniGen::refreshcomparator(const UniConfValueTree *a,
00217     const UniConfValueTree *b, void *userdata)
00218 {
00219     if (a)
00220     {
00221         if (b)
00222         {
00223             if (a->value() != b->value())
00224             {
00225                 // key changed
00226                 delta(b->fullkey(), b->value()); // CHANGED
00227                 return false;
00228             }
00229             return true;
00230         }
00231         else
00232         {
00233             // key removed
00234             // Issue notifications for every that is missing.
00235             a->visit(UniConfValueTree::Visitor(this,
00236                 &UniIniGen::notify_deleted), NULL, false, true);
00237             return false;
00238         }
00239     }
00240     else // a didn't exist
00241     {
00242         assert(b);
00243         // key added
00244         delta(b->fullkey(), b->value()); // ADDED
00245         return false;
00246     }
00247 }
00248 
00249 
00250 #ifndef _WIN32
00251 bool UniIniGen::commit_atomic(WvStringParm real_filename)
00252 {
00253     struct stat statbuf;
00254 
00255     if (lstat(real_filename, &statbuf) == -1)
00256     {
00257         if (errno != ENOENT)
00258             return false;
00259     }
00260     else
00261         if (!S_ISREG(statbuf.st_mode))
00262             return false;
00263 
00264     WvString tmp_filename("%s.tmp%s", real_filename, getpid());
00265     WvFile file(tmp_filename, O_WRONLY|O_TRUNC|O_CREAT, 0000);
00266 
00267     if (file.geterr())
00268     {
00269         log(WvLog::Warning, "Can't write '%s': %s\n",
00270             tmp_filename, strerror(errno));
00271         unlink(tmp_filename);
00272         file.close();
00273         return false;
00274     }
00275 
00276     save(file, *root); // write the changes out to our temp file
00277 
00278     mode_t theumask = umask(0);
00279     umask(theumask);
00280     fchmod(file.getwfd(), create_mode & ~theumask);
00281 
00282     file.close();
00283 
00284     if (file.geterr() || rename(tmp_filename, real_filename) == -1)
00285     {
00286         log(WvLog::Warning, "Can't write '%s': %s\n",
00287             filename, strerror(errno));
00288         unlink(tmp_filename);
00289         return false;
00290     }
00291 
00292     return true;
00293 }
00294 #endif
00295 
00296 
00297 void UniIniGen::commit()
00298 {
00299     if (!dirty)
00300         return;
00301 
00302     UniTempGen::commit();
00303 
00304 #ifdef _WIN32
00305     // Windows doesn't support all that fancy stuff, just open the
00306     // file and be done with it
00307     WvFile file(filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
00308     save(file, *root); // write the changes out to our file
00309     file.close();
00310     if (file.geterr())
00311     {
00312         log(WvLog::Warning, "Can't write '%s': %s\n",
00313             filename, file.errstr());
00314         return;
00315     }
00316 #else
00317     WvString real_filename(filename);
00318     char resolved_path[PATH_MAX];
00319 
00320     if (realpath(filename, resolved_path) != NULL)
00321         real_filename = resolved_path;
00322 
00323     if (!commit_atomic(real_filename))
00324     {
00325         WvFile file(real_filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
00326         struct stat statbuf;
00327 
00328         if (fstat(file.getwfd(), &statbuf) == -1)
00329         {
00330             log(WvLog::Warning, "Can't write '%s' ('%s'): %s\n",
00331                 filename, real_filename, strerror(errno));
00332             return;
00333         }
00334 
00335         fchmod(file.getwfd(), (statbuf.st_mode & 07777) | S_ISVTX);
00336 
00337         save(file, *root);
00338     
00339         if (!file.geterr())
00340         {
00341             /* We only reset the sticky bit if all went well, but before
00342              * we close it, because we need the file descriptor. */
00343             statbuf.st_mode = statbuf.st_mode & ~S_ISVTX;
00344             fchmod(file.getwfd(), statbuf.st_mode & 07777);
00345         }
00346         else
00347             log(WvLog::Warning, "Error writing '%s' ('%s'): %s\n",
00348                 filename, real_filename, file.errstr());
00349     }
00350 #endif
00351 
00352     dirty = false;
00353 }
00354 
00355 
00356 // may return false for strings that wvtcl_escape would escape anyway; this
00357 // may not escape tcl-invalid strings, but that's on purpose so we can keep
00358 // old-style .ini file compatibility (and wvtcl_getword() and friends can
00359 // still parse them anyway).
00360 static bool absolutely_needs_escape(WvStringParm s, const char *sepchars)
00361 {
00362     const char *cptr;
00363     int numbraces = 0;
00364     bool inescape = false, inspace = false;
00365     
00366     if (isspace((unsigned char)*s))
00367         return true; // leading whitespace needs escaping
00368     
00369     for (cptr = s; *cptr; cptr++)
00370     {
00371         if (inescape)
00372             inescape = false; // fine
00373         else if (!numbraces && strchr(sepchars, *cptr))
00374             return true; // one of the magic characters, and not escaped
00375         else if (*cptr == '\\')
00376             inescape = true;
00377         else if (*cptr == '{')
00378             numbraces++;
00379         else if (*cptr == '}')
00380             numbraces--;
00381         
00382         inspace = isspace((unsigned char)*cptr);
00383         
00384         if (numbraces < 0) // yikes!  mismatched braces will need some help.
00385             return false; 
00386     }
00387     
00388     if (inescape || inspace)
00389         return true; // terminating backslash or whitespace... evil.
00390     
00391     if (numbraces != 0)
00392         return true; // uneven number of braces, can't be good
00393 
00394     // otherwise, I guess we're safe.
00395     return false;
00396 }
00397 
00398 
00399 static void printsection(WvStream &file, const UniConfKey &key)
00400 {
00401     WvString s;
00402     static const WvStringMask nasties("\r\n[]");
00403 
00404     if (absolutely_needs_escape(key, "\r\n[]"))
00405         s = wvtcl_escape(key, nasties);
00406     else
00407         s = key;
00408     // broken up for optimization, no temp wvstring created
00409     //file.print("\n[%s]\n", s);
00410     file.print("\n[");
00411     file.print(s);
00412     file.print("]\n");
00413 }
00414 
00415 
00416 static void printkey(WvStream &file, const UniConfKey &_key,
00417                      WvStringParm _value)
00418 {
00419     WvString key, value;
00420     static const WvStringMask nasties("\r\n\t []=#");
00421 
00422     if (absolutely_needs_escape(_key, "\r\n[]=#\""))
00423         key = wvtcl_escape(_key, nasties);
00424     else if (_key == "")
00425         key = "/";
00426     else
00427         key = _key;
00428     
00429     // value is more relaxed, since we don't use wvtcl_getword after we grab
00430     // the "key=" part of each line
00431     if (absolutely_needs_escape(_value, "\r\n"))
00432         value = wvtcl_escape(_value, WVTCL_NASTY_SPACES);
00433     else
00434         value = _value;
00435     
00436     // need to escape []#= in key only to distinguish a key/value
00437     // pair from a section name or comment and to delimit the value
00438     // broken up for optimization, no temp wvstring created
00439     //file.print("%s = %s\n", key, value);
00440     file.print(key);
00441     file.print(" = ");
00442     file.print(value);
00443     file.print("\n");
00444 }
00445 
00446 
00447 static void save_sect(WvStream &file, UniConfValueTree &toplevel,
00448                       UniConfValueTree &sect, bool &printedsection,
00449                       bool recursive)
00450 {
00451     UniConfValueTree::Iter it(sect);
00452     for (it.rewind(); it.next(); )
00453     {
00454         UniConfValueTree &node = *it;
00455         
00456         // FIXME: we never print empty-string ("") keys, for compatibility
00457         // with WvConf.  Example: set x/y = 1; delete x/y; now x = "", because
00458         // it couldn't be NULL while x/y existed, and nobody auto-deleted it
00459         // when x/y went away.  Therefore we would try to write x = "" to the
00460         // config file, but that's not what WvConf would do.
00461         // 
00462         // The correct fix would be to auto-delete x if the only reason it
00463         // exists is for x/y.  But since that's hard, we'll just *never*
00464         // write lines for "" entries.  Icky, but it works.
00465         if (!!node.value())// || !node.haschildren())
00466         {
00467             if (!printedsection)
00468             {
00469                 printsection(file, toplevel.fullkey());
00470                 printedsection = true;
00471             }
00472             printkey(file, node.fullkey(&toplevel), node.value());
00473         }
00474 
00475         // print all children, if requested
00476         if (recursive && node.haschildren())
00477             save_sect(file, toplevel, node, printedsection, recursive);
00478     }
00479 }
00480 
00481 
00482 void UniIniGen::save(WvStream &file, UniConfValueTree &parent)
00483 {
00484     // parent might be NULL, so it really should be a pointer, not
00485     // a reference.  Oh well...
00486     if (!&parent) return;
00487     
00488     if (parent.fullkey() == root->fullkey())
00489     {
00490         // the root itself is a special case, since it's not in a section,
00491         // and it's never NULL (so we don't need to write it if it's just
00492         // blank)
00493         if (!!parent.value())
00494             printkey(file, parent.key(), parent.value());
00495     }
00496 
00497     bool printedsection = false;
00498     
00499     save_sect(file, parent, parent, printedsection, false);
00500     
00501     UniConfValueTree::Iter it(parent);
00502     for (it.rewind(); it.next(); )
00503     {
00504         UniConfValueTree &node = *it;
00505         
00506         printedsection = false;
00507         save_sect(file, node, node, printedsection, true);
00508     }
00509 }

Generated on Mon Feb 5 10:54:28 2007 for WvStreams by  doxygen 1.5.1