2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
5 * A generator for .ini files.
9 #include "unitempgen.h"
11 #include "wvmoniker.h"
12 #include "wvstringmask.h"
13 #include "wvtclstring.h"
15 #include "wvlinkerhack.h"
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
);
61 if (file
.isok() && fstat(file
.getrfd(), &statbuf
) == -1)
63 log(WvLog::Warning
, "Can't stat '%s': %s\n",
64 filename
, strerror(errno
));
68 if (file
.isok() && (statbuf
.st_mode
& S_ISVTX
))
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");
84 memcpy(&old_st
, &statbuf
, sizeof(statbuf
));
90 "Can't open '%s' for reading: %s\n"
91 "...starting with blank configuration.\n",
92 filename
, file
.errstr());
96 // loop over all Tcl words in the file
97 UniTempGen
*newgen
= new UniTempGen();
98 newgen
->set(UniConfKey::EMPTY
, WvString::empty
);
101 while (buf
.used() || file
.isok())
105 // read entire lines to ensure that we get whole values
106 char *line
= file
.blocking_getline(-1);
110 buf
.put('\n'); // this was auto-stripped by getline()
115 while (!(word
= wvtcl_getword(buf
,
116 WVTCL_NASTY_NEWLINES
,
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
127 // a comment line. FIXME: we drop it completely!
128 //log(WvLog::Debug5, "Comment: \"%s\"\n", str + 1);
132 if (str
[0] == '[' && str
[len
- 1] == ']')
136 WvString
name(wvtcl_unescape(trim_string(str
+ 1)));
137 section
= UniConfKey(name
);
138 //log(WvLog::Debug5, "Refresh section: \"%s\"\n", section);
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()));
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",
166 // if we get here, the line was tcl-decoded but not useful.
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
180 "XXX Ignoring malformed input line: \"%s\"\n", line1
);
187 "Error reading from config file: %s\n", file
.errstr());
192 // switch the trees and send notifications
194 UniConfValueTree
*oldtree
= root
;
195 UniConfValueTree
*newtree
= newgen
->root
;
199 oldtree
->compare(newtree
, wv::bind(&UniIniGen::refreshcomparator
, this,
207 UniTempGen::refresh();
212 // returns: true if a==b
213 bool UniIniGen::refreshcomparator(const UniConfValueTree
*a
,
214 const UniConfValueTree
*b
)
220 if (a
->value() != b
->value())
223 delta(b
->fullkey(), b
->value()); // CHANGED
231 // Issue notifications for every that is missing.
232 a
->visit(wv::bind(&UniIniGen::notify_deleted
, this, _1
, _2
),
237 else // a didn't exist
241 delta(b
->fullkey(), b
->value()); // ADDED
248 bool UniIniGen::commit_atomic(WvStringParm real_filename
)
252 if (lstat(real_filename
, &statbuf
) == -1)
258 if (!S_ISREG(statbuf
.st_mode
))
261 WvString
tmp_filename("%s.tmp%s", real_filename
, getpid());
262 WvFile
file(tmp_filename
, O_WRONLY
|O_TRUNC
|O_CREAT
, 0000);
266 log(WvLog::Warning
, "Can't write '%s': %s\n",
267 tmp_filename
, strerror(errno
));
268 unlink(tmp_filename
);
273 save(file
, *root
); // write the changes out to our temp file
275 mode_t theumask
= umask(0);
277 fchmod(file
.getwfd(), create_mode
& ~theumask
);
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
);
294 void UniIniGen::commit()
299 UniTempGen::commit();
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
309 log(WvLog::Warning
, "Can't write '%s': %s\n",
310 filename
, file
.errstr());
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
);
325 if (fstat(file
.getwfd(), &statbuf
) == -1)
327 log(WvLog::Warning
, "Can't write '%s' ('%s'): %s\n",
328 filename
, real_filename
, strerror(errno
));
332 fchmod(file
.getwfd(), (statbuf
.st_mode
& 07777) | S_ISVTX
);
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);
344 log(WvLog::Warning
, "Error writing '%s' ('%s'): %s\n",
345 filename
, real_filename
, file
.errstr());
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
)
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
++)
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
== '\\')
374 else if (*cptr
== '{')
376 else if (*cptr
== '}')
379 inspace
= isspace((unsigned char)*cptr
);
381 if (numbraces
< 0) // yikes! mismatched braces will need some help.
385 if (inescape
|| inspace
)
386 return true; // terminating backslash or whitespace... evil.
389 return true; // uneven number of braces, can't be good
391 // otherwise, I guess we're safe.
396 static void printsection(WvStream
&file
, const UniConfKey
&key
, UniIniGen::SaveCallback save_cb
)
399 static const WvStringMask
nasties("\r\n[]");
401 if (absolutely_needs_escape(key
, "\r\n[]"))
402 s
= wvtcl_escape(key
, nasties
);
405 // broken up for optimization, no temp wvstring created
406 //file.print("\n[%s]\n", s);
416 static void printkey(WvStream
&file
, const UniConfKey
&_key
,
417 WvStringParm _value
, UniIniGen::SaveCallback save_cb
)
420 static const WvStringMask
nasties("\r\n\t []=#");
422 if (absolutely_needs_escape(_key
, "\r\n[]=#\""))
423 key
= wvtcl_escape(_key
, nasties
);
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
);
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);
450 static void save_sect(WvStream
&file
, UniConfValueTree
&toplevel
,
451 UniConfValueTree
§
, 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())
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
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
);