Make WvStreams compile with gcc 4.4.
[wvstreams.git] / uniconf / uniinigen.cc
bloba919bde5c7e3cddb3c7e0b847272f0b3379b05be
1 /*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * A generator for .ini files.
6 */
7 #include "uniinigen.h"
8 #include "strutils.h"
9 #include "unitempgen.h"
10 #include "wvfile.h"
11 #include "wvmoniker.h"
12 #include "wvstringmask.h"
13 #include "wvtclstring.h"
14 #include <ctype.h>
15 #include "wvlinkerhack.h"
17 WV_LINK(UniIniGen);
20 static IUniConfGen *creator(WvStringParm s, IObject*)
22 return new UniIniGen(s);
25 WvMoniker<IUniConfGen> UniIniGenMoniker("ini", creator);
28 /***** UniIniGen *****/
30 UniIniGen::UniIniGen(WvStringParm _filename, int _create_mode, UniIniGen::SaveCallback _save_cb)
31 : filename(_filename), create_mode(_create_mode), log(_filename), save_cb(_save_cb)
33 // Create the root, since this generator can't handle it not existing.
34 UniTempGen::set(UniConfKey::EMPTY, WvString::empty);
35 memset(&old_st, 0, sizeof(old_st));
39 void UniIniGen::set(const UniConfKey &key, WvStringParm value)
41 UniTempGen::set(key, value);
43 // Re-create the root, since this generator can't handle it not existing.
44 if (value.isnull() && key.isempty())
45 UniTempGen::set(UniConfKey::EMPTY, WvString::empty);
50 UniIniGen::~UniIniGen()
55 bool UniIniGen::refresh()
57 WvFile file(filename, O_RDONLY);
59 #ifndef _WIN32
60 struct stat statbuf;
61 if (file.isok() && fstat(file.getrfd(), &statbuf) == -1)
63 log(WvLog::Warning, "Can't stat '%s': %s\n",
64 filename, strerror(errno));
65 file.close();
68 if (file.isok() && (statbuf.st_mode & S_ISVTX))
70 file.close();
71 file.seterr(EAGAIN);
74 if (file.isok() // guarantes statbuf is valid from above
75 && statbuf.st_ctime == old_st.st_ctime
76 && statbuf.st_dev == old_st.st_dev
77 && statbuf.st_ino == old_st.st_ino
78 && statbuf.st_blocks == old_st.st_blocks
79 && statbuf.st_size == old_st.st_size)
81 log(WvLog::Debug3, "refresh: file hasn't changed; do nothing.\n");
82 return true;
84 memcpy(&old_st, &statbuf, sizeof(statbuf));
85 #endif
87 if (!file.isok())
89 log(WvLog::Warning,
90 "Can't open '%s' for reading: %s\n"
91 "...starting with blank configuration.\n",
92 filename, file.errstr());
93 return false;
96 // loop over all Tcl words in the file
97 UniTempGen *newgen = new UniTempGen();
98 newgen->set(UniConfKey::EMPTY, WvString::empty);
99 UniConfKey section;
100 WvDynBuf buf;
101 while (buf.used() || file.isok())
103 if (file.isok())
105 // read entire lines to ensure that we get whole values
106 char *line = file.blocking_getline(-1);
107 if (line)
109 buf.putstr(line);
110 buf.put('\n'); // this was auto-stripped by getline()
114 WvString word;
115 while (!(word = wvtcl_getword(buf,
116 WVTCL_NASTY_NEWLINES,
117 false)).isnull())
119 //log(WvLog::Info, "LINE: '%s'\n", word);
121 char *str = trim_string(word.edit());
122 int len = strlen(str);
123 if (len == 0) continue; // blank line
125 if (str[0] == '#')
127 // a comment line. FIXME: we drop it completely!
128 //log(WvLog::Debug5, "Comment: \"%s\"\n", str + 1);
129 continue;
132 if (str[0] == '[' && str[len - 1] == ']')
134 // a section name
135 str[len - 1] = '\0';
136 WvString name(wvtcl_unescape(trim_string(str + 1)));
137 section = UniConfKey(name);
138 //log(WvLog::Debug5, "Refresh section: \"%s\"\n", section);
139 continue;
142 // we possibly have a key = value line
143 WvConstStringBuffer line(word);
144 static const WvStringMask nasty_equals("=");
145 WvString name = wvtcl_getword(line, nasty_equals, false);
146 if (!name.isnull() && line.used())
148 name = wvtcl_unescape(trim_string(name.edit()));
150 if (!!name)
152 UniConfKey key(name);
153 key.prepend(section);
155 WvString value = line.getstr();
156 assert(*value == '=');
157 value = wvtcl_unescape(trim_string(value.edit() + 1));
158 newgen->set(key, value.unique());
160 //log(WvLog::Debug5, "Refresh: (\"%s\", \"%s\")\n",
161 // key, value);
162 continue;
166 // if we get here, the line was tcl-decoded but not useful.
167 log(WvLog::Warning,
168 "Ignoring malformed input line: \"%s\"\n", word);
171 if (buf.used() && !file.isok())
173 // EOF and some of the data still hasn't been used. Weird.
174 // Let's remove a line of data and try again.
175 size_t offset = buf.strchr('\n');
176 assert(offset); // the last thing we put() is *always* a newline!
177 WvString line1(trim_string(buf.getstr(offset).edit()));
178 if (!!line1) // not just whitespace
179 log(WvLog::Warning,
180 "XXX Ignoring malformed input line: \"%s\"\n", line1);
184 if (file.geterr())
186 log(WvLog::Warning,
187 "Error reading from config file: %s\n", file.errstr());
188 WVRELEASE(newgen);
189 return false;
192 // switch the trees and send notifications
193 hold_delta();
194 UniConfValueTree *oldtree = root;
195 UniConfValueTree *newtree = newgen->root;
196 root = newtree;
197 newgen->root = NULL;
198 dirty = false;
199 oldtree->compare(newtree, wv::bind(&UniIniGen::refreshcomparator, this,
200 _1, _2));
202 delete oldtree;
203 unhold_delta();
205 WVRELEASE(newgen);
207 UniTempGen::refresh();
208 return true;
212 // returns: true if a==b
213 bool UniIniGen::refreshcomparator(const UniConfValueTree *a,
214 const UniConfValueTree *b)
216 if (a)
218 if (b)
220 if (a->value() != b->value())
222 // key changed
223 delta(b->fullkey(), b->value()); // CHANGED
224 return false;
226 return true;
228 else
230 // key removed
231 // Issue notifications for every that is missing.
232 a->visit(wv::bind(&UniIniGen::notify_deleted, this, _1, _2),
233 NULL, false, true);
234 return false;
237 else // a didn't exist
239 assert(b);
240 // key added
241 delta(b->fullkey(), b->value()); // ADDED
242 return false;
247 #ifndef _WIN32
248 bool UniIniGen::commit_atomic(WvStringParm real_filename)
250 struct stat statbuf;
252 if (lstat(real_filename, &statbuf) == -1)
254 if (errno != ENOENT)
255 return false;
257 else
258 if (!S_ISREG(statbuf.st_mode))
259 return false;
261 WvString tmp_filename("%s.tmp%s", real_filename, getpid());
262 WvFile file(tmp_filename, O_WRONLY|O_TRUNC|O_CREAT, 0000);
264 if (file.geterr())
266 log(WvLog::Warning, "Can't write '%s': %s\n",
267 tmp_filename, strerror(errno));
268 unlink(tmp_filename);
269 file.close();
270 return false;
273 save(file, *root); // write the changes out to our temp file
275 mode_t theumask = umask(0);
276 umask(theumask);
277 fchmod(file.getwfd(), create_mode & ~theumask);
279 file.close();
281 if (file.geterr() || rename(tmp_filename, real_filename) == -1)
283 log(WvLog::Warning, "Can't write '%s': %s\n",
284 filename, strerror(errno));
285 unlink(tmp_filename);
286 return false;
289 return true;
291 #endif
294 void UniIniGen::commit()
296 if (!dirty)
297 return;
299 UniTempGen::commit();
301 #ifdef _WIN32
302 // Windows doesn't support all that fancy stuff, just open the
303 // file and be done with it
304 WvFile file(filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
305 save(file, *root); // write the changes out to our file
306 file.close();
307 if (file.geterr())
309 log(WvLog::Warning, "Can't write '%s': %s\n",
310 filename, file.errstr());
311 return;
313 #else
314 WvString real_filename(filename);
315 char resolved_path[PATH_MAX];
317 if (realpath(filename, resolved_path) != NULL)
318 real_filename = resolved_path;
320 if (!commit_atomic(real_filename))
322 WvFile file(real_filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
323 struct stat statbuf;
325 if (fstat(file.getwfd(), &statbuf) == -1)
327 log(WvLog::Warning, "Can't write '%s' ('%s'): %s\n",
328 filename, real_filename, strerror(errno));
329 return;
332 fchmod(file.getwfd(), (statbuf.st_mode & 07777) | S_ISVTX);
334 save(file, *root);
336 if (!file.geterr())
338 /* We only reset the sticky bit if all went well, but before
339 * we close it, because we need the file descriptor. */
340 statbuf.st_mode = statbuf.st_mode & ~S_ISVTX;
341 fchmod(file.getwfd(), statbuf.st_mode & 07777);
343 else
344 log(WvLog::Warning, "Error writing '%s' ('%s'): %s\n",
345 filename, real_filename, file.errstr());
347 #endif
349 dirty = false;
353 // may return false for strings that wvtcl_escape would escape anyway; this
354 // may not escape tcl-invalid strings, but that's on purpose so we can keep
355 // old-style .ini file compatibility (and wvtcl_getword() and friends can
356 // still parse them anyway).
357 static bool absolutely_needs_escape(WvStringParm s, const char *sepchars)
359 const char *cptr;
360 int numbraces = 0;
361 bool inescape = false, inspace = false;
363 if (isspace((unsigned char)*s))
364 return true; // leading whitespace needs escaping
366 for (cptr = s; *cptr; cptr++)
368 if (inescape)
369 inescape = false; // fine
370 else if (!numbraces && strchr(sepchars, *cptr))
371 return true; // one of the magic characters, and not escaped
372 else if (*cptr == '\\')
373 inescape = true;
374 else if (*cptr == '{')
375 numbraces++;
376 else if (*cptr == '}')
377 numbraces--;
379 inspace = isspace((unsigned char)*cptr);
381 if (numbraces < 0) // yikes! mismatched braces will need some help.
382 return false;
385 if (inescape || inspace)
386 return true; // terminating backslash or whitespace... evil.
388 if (numbraces != 0)
389 return true; // uneven number of braces, can't be good
391 // otherwise, I guess we're safe.
392 return false;
396 static void printsection(WvStream &file, const UniConfKey &key, UniIniGen::SaveCallback save_cb)
398 WvString s;
399 static const WvStringMask nasties("\r\n[]");
401 if (absolutely_needs_escape(key, "\r\n[]"))
402 s = wvtcl_escape(key, nasties);
403 else
404 s = key;
405 // broken up for optimization, no temp wvstring created
406 //file.print("\n[%s]\n", s);
407 file.print("\n[");
408 file.print(s);
409 file.print("]\n");
411 if (!!save_cb)
412 save_cb();
416 static void printkey(WvStream &file, const UniConfKey &_key,
417 WvStringParm _value, UniIniGen::SaveCallback save_cb)
419 WvString key, value;
420 static const WvStringMask nasties("\r\n\t []=#");
422 if (absolutely_needs_escape(_key, "\r\n[]=#\""))
423 key = wvtcl_escape(_key, nasties);
424 else if (_key == "")
425 key = "/";
426 else
427 key = _key;
429 // value is more relaxed, since we don't use wvtcl_getword after we grab
430 // the "key=" part of each line
431 if (absolutely_needs_escape(_value, "\r\n"))
432 value = wvtcl_escape(_value, WVTCL_NASTY_SPACES);
433 else
434 value = _value;
436 // need to escape []#= in key only to distinguish a key/value
437 // pair from a section name or comment and to delimit the value
438 // broken up for optimization, no temp wvstring created
439 //file.print("%s = %s\n", key, value);
440 file.print(key);
441 file.print(" = ");
442 file.print(value);
443 file.print("\n");
445 if (!!save_cb)
446 save_cb();
450 static void save_sect(WvStream &file, UniConfValueTree &toplevel,
451 UniConfValueTree &sect, bool &printedsection,
452 bool recursive, UniIniGen::SaveCallback save_cb)
454 UniConfValueTree::Iter it(sect);
455 for (it.rewind(); it.next(); )
457 UniConfValueTree &node = *it;
459 // FIXME: we never print empty-string ("") keys, for compatibility
460 // with WvConf. Example: set x/y = 1; delete x/y; now x = "", because
461 // it couldn't be NULL while x/y existed, and nobody auto-deleted it
462 // when x/y went away. Therefore we would try to write x = "" to the
463 // config file, but that's not what WvConf would do.
465 // The correct fix would be to auto-delete x if the only reason it
466 // exists is for x/y. But since that's hard, we'll just *never*
467 // write lines for "" entries. Icky, but it works.
468 if (!!node.value())// || !node.haschildren())
470 if (!printedsection)
472 printsection(file, toplevel.fullkey(), save_cb);
473 printedsection = true;
475 printkey(file, node.fullkey(&toplevel), node.value(), save_cb);
478 // print all children, if requested
479 if (recursive && node.haschildren())
480 save_sect(file, toplevel, node, printedsection, recursive, save_cb);
485 void UniIniGen::save(WvStream &file, UniConfValueTree &parent)
487 // parent might be NULL, so it really should be a pointer, not
488 // a reference. Oh well...
489 if (!&parent) return;
491 if (parent.fullkey() == root->fullkey())
493 // the root itself is a special case, since it's not in a section,
494 // and it's never NULL (so we don't need to write it if it's just
495 // blank)
496 if (!!parent.value())
497 printkey(file, parent.key(), parent.value(), save_cb);
500 bool printedsection = false;
502 save_sect(file, parent, parent, printedsection, false, save_cb);
504 UniConfValueTree::Iter it(parent);
505 for (it.rewind(); it.next(); )
507 UniConfValueTree &node = *it;
509 printedsection = false;
510 save_sect(file, node, node, printedsection, true, save_cb);