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
00042
00043 if (!(value.isnull() && key.isempty()))
00044 UniTempGen::set(key, value);
00045 }
00046
00047
00048 UniIniGen::~UniIniGen()
00049 {
00050 }
00051
00052
00053 bool UniIniGen::refresh()
00054 {
00055 WvFile file(filename, O_RDONLY);
00056
00057 #ifndef _WIN32
00058 struct stat statbuf;
00059 if (file.isok() && fstat(file.getrfd(), &statbuf) == -1)
00060 {
00061 log(WvLog::Warning, "Can't stat '%s': %s\n",
00062 filename, strerror(errno));
00063 file.close();
00064 }
00065
00066 if (file.isok() && (statbuf.st_mode & S_ISVTX))
00067 {
00068 file.close();
00069 file.seterr(EAGAIN);
00070 }
00071
00072 if (file.isok()
00073 && statbuf.st_ctime == old_st.st_ctime
00074 && statbuf.st_dev == old_st.st_dev
00075 && statbuf.st_ino == old_st.st_ino
00076 && statbuf.st_blocks == old_st.st_blocks
00077 && statbuf.st_size == old_st.st_size)
00078 {
00079 log(WvLog::Debug3, "refresh: file hasn't changed; do nothing.\n");
00080 return true;
00081 }
00082 memcpy(&old_st, &statbuf, sizeof(statbuf));
00083 #endif
00084
00085 if (!file.isok())
00086 {
00087 log(WvLog::Warning,
00088 "Can't open '%s' for reading: %s\n"
00089 "...starting with blank configuration.\n",
00090 filename, file.errstr());
00091 return false;
00092 }
00093
00094
00095 UniTempGen *newgen = new UniTempGen();
00096 newgen->set(UniConfKey::EMPTY, WvString::empty);
00097 UniConfKey section;
00098 WvDynBuf buf;
00099 while (buf.used() || file.isok())
00100 {
00101 if (file.isok())
00102 {
00103
00104 char *line = file.blocking_getline(-1);
00105 if (line)
00106 {
00107 buf.putstr(line);
00108 buf.put('\n');
00109 }
00110 }
00111
00112 WvString word;
00113 while (!(word = wvtcl_getword(buf,
00114 WVTCL_NASTY_NEWLINES,
00115 false)).isnull())
00116 {
00117
00118
00119 char *str = trim_string(word.edit());
00120 int len = strlen(str);
00121 if (len == 0) continue;
00122
00123 if (str[0] == '#')
00124 {
00125
00126
00127 continue;
00128 }
00129
00130 if (str[0] == '[' && str[len - 1] == ']')
00131 {
00132
00133 str[len - 1] = '\0';
00134 WvString name(wvtcl_unescape(trim_string(str + 1)));
00135 section = UniConfKey(name);
00136
00137 continue;
00138 }
00139
00140
00141 WvConstStringBuffer line(word);
00142 static const WvStringMask nasty_equals("=");
00143 WvString name = wvtcl_getword(line, nasty_equals, false);
00144 if (!name.isnull() && line.used())
00145 {
00146 name = wvtcl_unescape(trim_string(name.edit()));
00147
00148 if (!!name)
00149 {
00150 UniConfKey key(name);
00151 key.prepend(section);
00152
00153 WvString value = line.getstr();
00154 assert(*value == '=');
00155 value = wvtcl_unescape(trim_string(value.edit() + 1));
00156 newgen->set(key, value.unique());
00157
00158
00159
00160 continue;
00161 }
00162 }
00163
00164
00165 log(WvLog::Warning,
00166 "Ignoring malformed input line: \"%s\"\n", word);
00167 }
00168
00169 if (buf.used() && !file.isok())
00170 {
00171
00172
00173 size_t offset = buf.strchr('\n');
00174 assert(offset);
00175 WvString line1(trim_string(buf.getstr(offset).edit()));
00176 if (!!line1)
00177 log(WvLog::Warning,
00178 "XXX Ignoring malformed input line: \"%s\"\n", line1);
00179 }
00180 }
00181
00182 if (file.geterr())
00183 {
00184 log(WvLog::Warning,
00185 "Error reading from config file: %s\n", file.errstr());
00186 WVRELEASE(newgen);
00187 return false;
00188 }
00189
00190
00191 hold_delta();
00192 UniConfValueTree *oldtree = root;
00193 UniConfValueTree *newtree = newgen->root;
00194 root = newtree;
00195 newgen->root = NULL;
00196 dirty = false;
00197 oldtree->compare(newtree, UniConfValueTree::Comparator
00198 (this, &UniIniGen::refreshcomparator), NULL);
00199
00200 delete oldtree;
00201 unhold_delta();
00202
00203 WVRELEASE(newgen);
00204
00205 UniTempGen::refresh();
00206 return true;
00207 }
00208
00209
00210
00211 bool UniIniGen::refreshcomparator(const UniConfValueTree *a,
00212 const UniConfValueTree *b, void *userdata)
00213 {
00214 if (a)
00215 {
00216 if (b)
00217 {
00218 if (a->value() != b->value())
00219 {
00220
00221 delta(b->fullkey(), b->value());
00222 return false;
00223 }
00224 return true;
00225 }
00226 else
00227 {
00228
00229
00230 a->visit(UniConfValueTree::Visitor(this,
00231 &UniIniGen::notify_deleted), NULL, false, true);
00232 return false;
00233 }
00234 }
00235 else
00236 {
00237 assert(b);
00238
00239 delta(b->fullkey(), b->value());
00240 return false;
00241 }
00242 }
00243
00244
00245 #ifndef _WIN32
00246 bool UniIniGen::commit_atomic(WvStringParm real_filename)
00247 {
00248 struct stat statbuf;
00249
00250 if (lstat(real_filename, &statbuf) == -1)
00251 {
00252 if (errno != ENOENT)
00253 return false;
00254 }
00255 else
00256 if (!S_ISREG(statbuf.st_mode))
00257 return false;
00258
00259 WvString tmp_filename("%s.tmp%s", real_filename, getpid());
00260 WvFile file(tmp_filename, O_WRONLY|O_TRUNC|O_CREAT, 0000);
00261
00262 if (file.geterr())
00263 {
00264 log(WvLog::Warning, "Can't write '%s': %s\n",
00265 tmp_filename, strerror(errno));
00266 unlink(tmp_filename);
00267 file.close();
00268 return false;
00269 }
00270
00271 save(file, *root);
00272
00273 mode_t theumask = umask(0);
00274 umask(theumask);
00275 fchmod(file.getwfd(), create_mode & ~theumask);
00276
00277 file.close();
00278
00279 if (file.geterr() || rename(tmp_filename, real_filename) == -1)
00280 {
00281 log(WvLog::Warning, "Can't write '%s': %s\n",
00282 filename, strerror(errno));
00283 unlink(tmp_filename);
00284 return false;
00285 }
00286
00287 return true;
00288 }
00289 #endif
00290
00291
00292 void UniIniGen::commit()
00293 {
00294 if (!dirty)
00295 return;
00296
00297 UniTempGen::commit();
00298
00299 #ifdef _WIN32
00300
00301
00302 WvFile file(filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
00303 save(file, *root);
00304 file.close();
00305 if (file.geterr())
00306 {
00307 log(WvLog::Warning, "Can't write '%s': %s\n",
00308 filename, file.errstr());
00309 return;
00310 }
00311 #else
00312 WvString real_filename(filename);
00313 char resolved_path[PATH_MAX];
00314
00315 if (realpath(filename, resolved_path) != NULL)
00316 real_filename = resolved_path;
00317
00318 if (!commit_atomic(real_filename))
00319 {
00320 WvFile file(real_filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
00321 struct stat statbuf;
00322
00323 if (fstat(file.getwfd(), &statbuf) == -1)
00324 {
00325 log(WvLog::Warning, "Can't write '%s' ('%s'): %s\n",
00326 filename, real_filename, strerror(errno));
00327 return;
00328 }
00329
00330 fchmod(file.getwfd(), (statbuf.st_mode & 07777) | S_ISVTX);
00331
00332 save(file, *root);
00333
00334 if (!file.geterr())
00335 {
00336
00337
00338 statbuf.st_mode = statbuf.st_mode & ~S_ISVTX;
00339 fchmod(file.getwfd(), statbuf.st_mode & 07777);
00340 }
00341 else
00342 log(WvLog::Warning, "Error writing '%s' ('%s'): %s\n",
00343 filename, real_filename, file.errstr());
00344 }
00345 #endif
00346
00347 dirty = false;
00348 }
00349
00350
00351
00352
00353
00354
00355 static bool absolutely_needs_escape(WvStringParm s, const char *sepchars)
00356 {
00357 const char *cptr;
00358 int numbraces = 0;
00359 bool inescape = false, inspace = false;
00360
00361 if (isspace((unsigned char)*s))
00362 return true;
00363
00364 for (cptr = s; *cptr; cptr++)
00365 {
00366 if (inescape)
00367 inescape = false;
00368 else if (!numbraces && strchr(sepchars, *cptr))
00369 return true;
00370 else if (*cptr == '\\')
00371 inescape = true;
00372 else if (*cptr == '{')
00373 numbraces++;
00374 else if (*cptr == '}')
00375 numbraces--;
00376
00377 inspace = isspace((unsigned char)*cptr);
00378
00379 if (numbraces < 0)
00380 return false;
00381 }
00382
00383 if (inescape || inspace)
00384 return true;
00385
00386 if (numbraces != 0)
00387 return true;
00388
00389
00390 return false;
00391 }
00392
00393
00394 static void printsection(WvStream &file, const UniConfKey &key, UniIniGen::SaveCallback save_cb)
00395 {
00396 WvString s;
00397 static const WvStringMask nasties("\r\n[]");
00398
00399 if (absolutely_needs_escape(key, "\r\n[]"))
00400 s = wvtcl_escape(key, nasties);
00401 else
00402 s = key;
00403
00404
00405 file.print("\n[");
00406 file.print(s);
00407 file.print("]\n");
00408
00409 if (!!save_cb)
00410 save_cb();
00411 }
00412
00413
00414 static void printkey(WvStream &file, const UniConfKey &_key,
00415 WvStringParm _value, UniIniGen::SaveCallback save_cb)
00416 {
00417 WvString key, value;
00418 static const WvStringMask nasties("\r\n\t []=#");
00419
00420 if (absolutely_needs_escape(_key, "\r\n[]=#\""))
00421 key = wvtcl_escape(_key, nasties);
00422 else if (_key == "")
00423 key = "/";
00424 else
00425 key = _key;
00426
00427
00428
00429 if (absolutely_needs_escape(_value, "\r\n"))
00430 value = wvtcl_escape(_value, WVTCL_NASTY_SPACES);
00431 else
00432 value = _value;
00433
00434
00435
00436
00437
00438 file.print(key);
00439 file.print(" = ");
00440 file.print(value);
00441 file.print("\n");
00442
00443 if (!!save_cb)
00444 save_cb();
00445 }
00446
00447
00448 static void save_sect(WvStream &file, UniConfValueTree &toplevel,
00449 UniConfValueTree §, bool &printedsection,
00450 bool recursive, UniIniGen::SaveCallback save_cb)
00451 {
00452 UniConfValueTree::Iter it(sect);
00453 for (it.rewind(); it.next(); )
00454 {
00455 UniConfValueTree &node = *it;
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465
00466 if (!!node.value())
00467 {
00468 if (!printedsection)
00469 {
00470 printsection(file, toplevel.fullkey(), save_cb);
00471 printedsection = true;
00472 }
00473 printkey(file, node.fullkey(&toplevel), node.value(), save_cb);
00474 }
00475
00476
00477 if (recursive && node.haschildren())
00478 save_sect(file, toplevel, node, printedsection, recursive, save_cb);
00479 }
00480 }
00481
00482
00483 void UniIniGen::save(WvStream &file, UniConfValueTree &parent)
00484 {
00485
00486
00487 if (!&parent) return;
00488
00489 if (parent.fullkey() == root->fullkey())
00490 {
00491
00492
00493
00494 if (!!parent.value())
00495 printkey(file, parent.key(), parent.value(), save_cb);
00496 }
00497
00498 bool printedsection = false;
00499
00500 save_sect(file, parent, parent, printedsection, false, save_cb);
00501
00502 UniConfValueTree::Iter it(parent);
00503 for (it.rewind(); it.next(); )
00504 {
00505 UniConfValueTree &node = *it;
00506
00507 printedsection = false;
00508 save_sect(file, node, node, printedsection, true, save_cb);
00509 }
00510 }