00001
00002
00003
00004
00005
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
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
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
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()
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
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
00106 char *line = file.blocking_getline(-1);
00107 if (line)
00108 {
00109 buf.putstr(line);
00110 buf.put('\n');
00111 }
00112 }
00113
00114 WvString word;
00115 while (!(word = wvtcl_getword(buf,
00116 WVTCL_NASTY_NEWLINES,
00117 false)).isnull())
00118 {
00119
00120
00121 char *str = trim_string(word.edit());
00122 int len = strlen(str);
00123 if (len == 0) continue;
00124
00125 if (str[0] == '#')
00126 {
00127
00128
00129 continue;
00130 }
00131
00132 if (str[0] == '[' && str[len - 1] == ']')
00133 {
00134
00135 str[len - 1] = '\0';
00136 WvString name(wvtcl_unescape(trim_string(str + 1)));
00137 section = UniConfKey(name);
00138
00139 continue;
00140 }
00141
00142
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
00161
00162 continue;
00163 }
00164 }
00165
00166
00167 log(WvLog::Warning,
00168 "Ignoring malformed input line: \"%s\"\n", word);
00169 }
00170
00171 if (buf.used() && !file.isok())
00172 {
00173
00174
00175 size_t offset = buf.strchr('\n');
00176 assert(offset);
00177 WvString line1(trim_string(buf.getstr(offset).edit()));
00178 if (!!line1)
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
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, UniConfValueTree::Comparator
00200 (this, &UniIniGen::refreshcomparator), NULL);
00201
00202 delete oldtree;
00203 unhold_delta();
00204
00205 WVRELEASE(newgen);
00206
00207 UniTempGen::refresh();
00208 return true;
00209 }
00210
00211
00212
00213 bool UniIniGen::refreshcomparator(const UniConfValueTree *a,
00214 const UniConfValueTree *b, void *userdata)
00215 {
00216 if (a)
00217 {
00218 if (b)
00219 {
00220 if (a->value() != b->value())
00221 {
00222
00223 delta(b->fullkey(), b->value());
00224 return false;
00225 }
00226 return true;
00227 }
00228 else
00229 {
00230
00231
00232 a->visit(UniConfValueTree::Visitor(this,
00233 &UniIniGen::notify_deleted), NULL, false, true);
00234 return false;
00235 }
00236 }
00237 else
00238 {
00239 assert(b);
00240
00241 delta(b->fullkey(), b->value());
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);
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
00303
00304 WvFile file(filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
00305 save(file, *root);
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
00339
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
00354
00355
00356
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;
00365
00366 for (cptr = s; *cptr; cptr++)
00367 {
00368 if (inescape)
00369 inescape = false;
00370 else if (!numbraces && strchr(sepchars, *cptr))
00371 return true;
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)
00382 return false;
00383 }
00384
00385 if (inescape || inspace)
00386 return true;
00387
00388 if (numbraces != 0)
00389 return true;
00390
00391
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
00406
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
00430
00431 if (absolutely_needs_escape(_value, "\r\n"))
00432 value = wvtcl_escape(_value, WVTCL_NASTY_SPACES);
00433 else
00434 value = _value;
00435
00436
00437
00438
00439
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
00460
00461
00462
00463
00464
00465
00466
00467
00468 if (!!node.value())
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
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
00488
00489 if (!&parent) return;
00490
00491 if (parent.fullkey() == root->fullkey())
00492 {
00493
00494
00495
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 }