WvStreams
uniinigen.cc
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"
16
17WV_LINK(UniIniGen);
18
19
20static IUniConfGen *creator(WvStringParm s, IObject*)
21{
22 return new UniIniGen(s);
23}
24
25WvMoniker<IUniConfGen> UniIniGenMoniker("ini", creator);
26
27
28/***** UniIniGen *****/
29
30UniIniGen::UniIniGen(WvStringParm _filename, int _create_mode, UniIniGen::SaveCallback _save_cb)
31 : filename(_filename), create_mode(_create_mode), log(_filename), save_cb(_save_cb)
32{
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));
36}
37
38
39void UniIniGen::set(const UniConfKey &key, WvStringParm value)
40{
41 UniTempGen::set(key, value);
42
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);
46
47}
48
49
50UniIniGen::~UniIniGen()
51{
52}
53
54
56{
57 WvFile file(filename, O_RDONLY);
58
59#ifndef _WIN32
60 struct stat statbuf;
61 if (file.isok() && fstat(file.getrfd(), &statbuf) == -1)
62 {
63 log(WvLog::Warning, "Can't stat '%s': %s\n",
64 filename, strerror(errno));
65 file.close();
66 }
67
68 if (file.isok() && (statbuf.st_mode & S_ISVTX))
69 {
70 file.close();
71 file.seterr(EAGAIN);
72 }
73
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)
80 {
81 log(WvLog::Debug3, "refresh: file hasn't changed; do nothing.\n");
82 return true;
83 }
84 memcpy(&old_st, &statbuf, sizeof(statbuf));
85#endif
86
87 if (!file.isok())
88 {
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;
94 }
95
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())
102 {
103 if (file.isok())
104 {
105 // read entire lines to ensure that we get whole values
106 char *line = file.blocking_getline(-1);
107 if (line)
108 {
109 buf.putstr(line);
110 buf.put('\n'); // this was auto-stripped by getline()
111 }
112 }
113
114 WvString word;
115 while (!(word = wvtcl_getword(buf,
116 WVTCL_NASTY_NEWLINES,
117 false)).isnull())
118 {
119 //log(WvLog::Info, "LINE: '%s'\n", word);
120
121 char *str = trim_string(word.edit());
122 int len = strlen(str);
123 if (len == 0) continue; // blank line
124
125 if (str[0] == '#')
126 {
127 // a comment line. FIXME: we drop it completely!
128 //log(WvLog::Debug5, "Comment: \"%s\"\n", str + 1);
129 continue;
130 }
131
132 if (str[0] == '[' && str[len - 1] == ']')
133 {
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;
140 }
141
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())
147 {
148 name = wvtcl_unescape(trim_string(name.edit()));
149
150 if (!!name)
151 {
152 UniConfKey key(name);
153 key.prepend(section);
154
155 WvString value = line.getstr();
156 assert(*value == '=');
157 value = wvtcl_unescape(trim_string(value.edit() + 1));
158 newgen->set(key, value.unique());
159
160 //log(WvLog::Debug5, "Refresh: (\"%s\", \"%s\")\n",
161 // key, value);
162 continue;
163 }
164 }
165
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);
169 }
170
171 if (buf.used() && !file.isok())
172 {
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);
181 }
182 }
183
184 if (file.geterr())
185 {
186 log(WvLog::Warning,
187 "Error reading from config file: %s\n", file.errstr());
188 WVRELEASE(newgen);
189 return false;
190 }
191
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));
201
202 delete oldtree;
203 unhold_delta();
204
205 WVRELEASE(newgen);
206
208 return true;
209}
210
211
212// returns: true if a==b
213bool UniIniGen::refreshcomparator(const UniConfValueTree *a,
214 const UniConfValueTree *b)
215{
216 if (a)
217 {
218 if (b)
219 {
220 if (a->value() != b->value())
221 {
222 // key changed
223 delta(b->fullkey(), b->value()); // CHANGED
224 return false;
225 }
226 return true;
227 }
228 else
229 {
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;
235 }
236 }
237 else // a didn't exist
238 {
239 assert(b);
240 // key added
241 delta(b->fullkey(), b->value()); // ADDED
242 return false;
243 }
244}
245
246
247#ifndef _WIN32
248bool UniIniGen::commit_atomic(WvStringParm real_filename)
249{
250 struct stat statbuf;
251
252 if (lstat(real_filename, &statbuf) == -1)
253 {
254 if (errno != ENOENT)
255 return false;
256 }
257 else
258 if (!S_ISREG(statbuf.st_mode))
259 return false;
260
261 WvString tmp_filename("%s.tmp%s", real_filename, getpid());
262 WvFile file(tmp_filename, O_WRONLY|O_TRUNC|O_CREAT, 0000);
263
264 if (file.geterr())
265 {
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;
271 }
272
273 save(file, *root); // write the changes out to our temp file
274
275 mode_t theumask = umask(0);
276 umask(theumask);
277 fchmod(file.getwfd(), create_mode & ~theumask);
278
279 file.close();
280
281 if (file.geterr() || rename(tmp_filename, real_filename) == -1)
282 {
283 log(WvLog::Warning, "Can't write '%s': %s\n",
284 filename, strerror(errno));
285 unlink(tmp_filename);
286 return false;
287 }
288
289 return true;
290}
291#endif
292
293
295{
296 if (!dirty)
297 return;
298
300
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())
308 {
309 log(WvLog::Warning, "Can't write '%s': %s\n",
310 filename, file.errstr());
311 return;
312 }
313#else
314 WvString real_filename(filename);
315 char resolved_path[PATH_MAX];
316
317 if (realpath(filename, resolved_path) != NULL)
318 real_filename = resolved_path;
319
320 if (!commit_atomic(real_filename))
321 {
322 WvFile file(real_filename, O_WRONLY|O_TRUNC|O_CREAT, create_mode);
323 struct stat statbuf;
324
325 if (fstat(file.getwfd(), &statbuf) == -1)
326 {
327 log(WvLog::Warning, "Can't write '%s' ('%s'): %s\n",
328 filename, real_filename, strerror(errno));
329 return;
330 }
331
332 fchmod(file.getwfd(), (statbuf.st_mode & 07777) | S_ISVTX);
333
334 save(file, *root);
335
336 if (!file.geterr())
337 {
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);
342 }
343 else
344 log(WvLog::Warning, "Error writing '%s' ('%s'): %s\n",
345 filename, real_filename, file.errstr());
346 }
347#endif
348
349 dirty = false;
350}
351
352
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).
357static bool absolutely_needs_escape(WvStringParm s, const char *sepchars)
358{
359 const char *cptr;
360 int numbraces = 0;
361 bool inescape = false, inspace = false;
362
363 if (isspace((unsigned char)*s))
364 return true; // leading whitespace needs escaping
365
366 for (cptr = s; *cptr; cptr++)
367 {
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--;
378
379 inspace = isspace((unsigned char)*cptr);
380
381 if (numbraces < 0) // yikes! mismatched braces will need some help.
382 return false;
383 }
384
385 if (inescape || inspace)
386 return true; // terminating backslash or whitespace... evil.
387
388 if (numbraces != 0)
389 return true; // uneven number of braces, can't be good
390
391 // otherwise, I guess we're safe.
392 return false;
393}
394
395
396static void printsection(WvStream &file, const UniConfKey &key, UniIniGen::SaveCallback save_cb)
397{
398 WvString s;
399 static const WvStringMask nasties("\r\n[]");
400
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");
410
411 if (!!save_cb)
412 save_cb();
413}
414
415
416static void printkey(WvStream &file, const UniConfKey &_key,
417 WvStringParm _value, UniIniGen::SaveCallback save_cb)
418{
419 WvString key, value;
420 static const WvStringMask nasties("\r\n\t []=#");
421
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;
428
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;
435
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");
444
445 if (!!save_cb)
446 save_cb();
447}
448
449
450static void save_sect(WvStream &file, UniConfValueTree &toplevel,
451 UniConfValueTree &sect, bool &printedsection,
452 bool recursive, UniIniGen::SaveCallback save_cb)
453{
454 UniConfValueTree::Iter it(sect);
455 for (it.rewind(); it.next(); )
456 {
457 UniConfValueTree &node = *it;
458
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.
464 //
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())
469 {
470 if (!printedsection)
471 {
472 printsection(file, toplevel.fullkey(), save_cb);
473 printedsection = true;
474 }
475 printkey(file, node.fullkey(&toplevel), node.value(), save_cb);
476 }
477
478 // print all children, if requested
479 if (recursive && node.haschildren())
480 save_sect(file, toplevel, node, printedsection, recursive, save_cb);
481 }
482}
483
484
485void UniIniGen::save(WvStream &file, UniConfValueTree &parent)
486{
487 // parent might be NULL, so it really should be a pointer, not
488 // a reference. Oh well...
489 if (!&parent) return;
490
491 if (parent.fullkey() == root->fullkey())
492 {
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);
498 }
499
500 bool printedsection = false;
501
502 save_sect(file, parent, parent, printedsection, false, save_cb);
503
504 UniConfValueTree::Iter it(parent);
505 for (it.rewind(); it.next(); )
506 {
507 UniConfValueTree &node = *it;
508
509 printedsection = false;
510 save_sect(file, node, node, printedsection, true, save_cb);
511 }
512}
The basic interface which is included by all other XPLC interfaces and objects.
Definition: IObject.h:65
An abstract data container that backs a UniConf tree.
Definition: uniconfgen.h:40
void hold_delta()
Pauses notifications until matched with a call to unhold_delta().
Definition: uniconfgen.cc:32
void unhold_delta()
Resumes notifications when each hold_delta() has been matched.
Definition: uniconfgen.cc:38
void delta(const UniConfKey &key, WvStringParm value)
Call this when a key's value or children have possibly changed.
Definition: uniconfgen.cc:77
Represents a UniConf key which is a path in a hierarchy structured much like the traditional Unix fil...
Definition: uniconfkey.h:39
void prepend(const UniConfKey &other)
Prepends a path to this path.
Definition: uniconfkey.cc:152
bool isempty() const
Returns true if this path has zero segments (also known as root).
Definition: uniconfkey.h:264
static UniConfKey EMPTY
Definition: uniconfkey.h:171
UniConfKey fullkey(const Sub *ancestor=NULL) const
Returns full path of this node relative to an ancestor.
Definition: uniconftree.h:55
void visit(const Visitor &visitor, void *userdata, bool preorder=true, bool postorder=false) const
Performs a traversal on this tree using the specified visitor function and traversal type(s).
Definition: uniconftree.h:108
bool compare(const Sub *other, const Comparator &comparator)
Compares this tree with another using the specified comparator function.
Definition: uniconftree.h:124
A plain UniConfTree that holds keys and values.
Definition: uniconftree.h:153
const WvString & value() const
Returns the value field.
Definition: uniconftree.h:163
const UniConfKey & key() const
Returns the key field.
Definition: unihashtree.h:40
bool haschildren() const
Returns true if the node has children.
Definition: unihashtree.cc:114
Loads and saves ".ini"-style files similar to those used by Windows, but adapted to represent keys an...
Definition: uniinigen.h:26
virtual void set(const UniConfKey &key, WvStringParm value)
Stores a string value for a key into the registry.
Definition: uniinigen.cc:39
UniIniGen(WvStringParm filename, int _create_mode=0666, SaveCallback _save_cb=SaveCallback())
Creates a generator which can load/modify/save a .ini file.
Definition: uniinigen.cc:30
virtual void commit()
Commits any changes.
Definition: uniinigen.cc:294
virtual bool refresh()
Refreshes information about a key recursively.
Definition: uniinigen.cc:55
A UniConf generator that stores keys in memory.
Definition: unitempgen.h:21
virtual bool refresh()
Refreshes information about a key recursively.
Definition: unitempgen.cc:191
virtual void commit()
Commits any changes.
Definition: unitempgen.cc:185
bool dirty
Definition: unitempgen.h:26
virtual void set(const UniConfKey &key, WvStringParm value)
Stores a string value for a key into the registry.
Definition: unitempgen.cc:57
UniConfValueTree * root
Definition: unitempgen.h:25
size_t used() const
Returns the number of elements in the buffer currently available for reading.
Definition: wvbufbase.h:92
size_t strchr(int ch)
Returns the number of characters that would have to be read to find the first instance of the charact...
WvString getstr()
Returns the entire buffer as a null-terminated WvString.
void putstr(WvStringParm str)
Copies a WvString into the buffer, excluding the null-terminator.
A raw memory read-only buffer backed by a constant WvString.
Definition: wvbuf.h:242
virtual int geterr() const
If isok() is false, return the system error number corresponding to the error, -1 for a special error...
Definition: wverror.h:48
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Definition: wvstring.h:94
bool isnull() const
returns true if this string is null
Definition: wvstring.h:290
int getrfd() const
Returns the Unix file descriptor for reading from this stream.
Definition: wvfdstream.h:63
virtual bool isok() const
return true if the stream is actually usable right now
Definition: wvfdstream.cc:134
int getwfd() const
Returns the Unix file descriptor for writing to this stream.
Definition: wvfdstream.h:70
virtual void close()
Closes the file descriptors.
Definition: wvfdstream.cc:117
WvFile implements a stream connected to a file or Unix device.
Definition: wvfile.h:29
A type-safe version of WvMonikerBase that lets you provide create functions for object types other th...
Definition: wvmoniker.h:62
Unified support for streams, that is, sequences of bytes that may or may not be ready for read/write ...
Definition: wvstream.h:25
char * blocking_getline(time_t wait_msec, int separator='\n', int readahead=1024)
This is a version of getline() that allows you to block for more data to arrive.
Definition: wvstream.cc:602
virtual void seterr(int _errnum)
Override seterr() from WvError so that it auto-closes the stream.
Definition: wvstream.cc:451
A class used to provide a masked lookup for characters in a string.
Definition: wvstringmask.h:19
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:330
WvString & unique()
make the buf and str pointers owned only by this WvString.
Definition: wvstring.cc:306
char * edit()
make the string editable, and return a non-const (char*)
Definition: wvstring.h:397
char * trim_string(char *string)
Trims whitespace from the beginning and end of the character string, including carriage return / line...
Definition: strutils.cc:59
Functions to handle "tcl-style" strings and lists.
WvString wvtcl_unescape(WvStringParm s)
tcl-unescape a string.
Definition: wvtclstring.cc:204
WvString wvtcl_escape(WvStringParm s, const WvStringMask &nasties=WVTCL_NASTY_SPACES)
tcl-escape a string.
Definition: wvtclstring.cc:128
WvString wvtcl_getword(WvBuf &buf, const WvStringMask &splitchars=WVTCL_SPLITCHARS, bool do_unescape=true)
Get a single tcl word from an input buffer, and return the rest of the buffer untouched.
Definition: wvtclstring.cc:359