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