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 static void printkey(WvStream &file, const UniConfKey &_key,
00030 WvStringParm _value);
00031
00032
00033
00034
00035 UniIniGen::UniIniGen(WvStringParm _filename, int _create_mode)
00036 : filename(_filename), create_mode(_create_mode), log(_filename)
00037 {
00038
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
00047
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()
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
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
00109 char *line = file.blocking_getline(-1);
00110 if (line)
00111 {
00112 buf.putstr(line);
00113 buf.put('\n');
00114 }
00115 }
00116
00117 WvString word;
00118 while (!(word = wvtcl_getword(buf,
00119 WVTCL_NASTY_NEWLINES,
00120 false)).isnull())
00121 {
00122
00123
00124 char *str = trim_string(word.edit());
00125 int len = strlen(str);
00126 if (len == 0) continue;
00127
00128 if (str[0] == '#')
00129 {
00130
00131
00132 continue;
00133 }
00134
00135 if (str[0] == '[' && str[len - 1] == ']')
00136 {
00137
00138 str[len - 1] = '\0';
00139 WvString name(wvtcl_unescape(trim_string(str + 1)));
00140 section = UniConfKey(name);
00141
00142 continue;
00143 }
00144
00145
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
00164
00165 continue;
00166 }
00167 }
00168
00169
00170 log(WvLog::Warning,
00171 "Ignoring malformed input line: \"%s\"\n", word);
00172 }
00173
00174 if (buf.used() && !file.isok())
00175 {
00176
00177
00178 size_t offset = buf.strchr('\n');
00179 assert(offset);
00180 WvString line1(trim_string(buf.getstr(offset).edit()));
00181 if (!!line1)
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
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
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
00226 delta(b->fullkey(), b->value());
00227 return false;
00228 }
00229 return true;
00230 }
00231 else
00232 {
00233
00234
00235 a->visit(UniConfValueTree::Visitor(this,
00236 &UniIniGen::notify_deleted), NULL, false, true);
00237 return false;
00238 }
00239 }
00240 else
00241 {
00242 assert(b);
00243
00244 delta(b->fullkey(), b->value());
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);
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
00306
00307 WvFile file(filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
00308 save(file, *root);
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
00342
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
00357
00358
00359
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;
00368
00369 for (cptr = s; *cptr; cptr++)
00370 {
00371 if (inescape)
00372 inescape = false;
00373 else if (!numbraces && strchr(sepchars, *cptr))
00374 return true;
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)
00385 return false;
00386 }
00387
00388 if (inescape || inspace)
00389 return true;
00390
00391 if (numbraces != 0)
00392 return true;
00393
00394
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
00409
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
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
00446
00447 static void save_sect(WvStream &file, UniConfValueTree &toplevel,
00448 UniConfValueTree §, bool &printedsection,
00449 bool recursive)
00450 {
00451 UniConfValueTree::Iter it(sect);
00452 for (it.rewind(); it.next(); )
00453 {
00454 UniConfValueTree &node = *it;
00455
00456
00457
00458
00459
00460
00461
00462
00463
00464
00465 if (!!node.value())
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
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
00485
00486 if (!&parent) return;
00487
00488 if (parent.fullkey() == root->fullkey())
00489 {
00490
00491
00492
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 }